1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.phone;
18 
19 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
20 
21 import android.content.Context;
22 import android.os.Handler;
23 import android.os.RemoteException;
24 import android.util.Log;
25 import android.view.IWindowManager;
26 import android.view.MotionEvent;
27 import android.view.accessibility.AccessibilityManager;
28 
29 import androidx.annotation.NonNull;
30 
31 import com.android.systemui.dagger.SysUISingleton;
32 import com.android.systemui.dagger.qualifiers.Main;
33 import com.android.systemui.dump.DumpManager;
34 import com.android.systemui.statusbar.AutoHideUiElement;
35 
36 import java.io.PrintWriter;
37 
38 import javax.inject.Inject;
39 
40 /** A controller to control all auto-hide things. Also see {@link AutoHideUiElement}. */
41 @SysUISingleton
42 public class AutoHideController {
43     private static final String TAG = "AutoHideController";
44     private static final int AUTO_HIDE_TIMEOUT_MS = 2250;
45     private static final int USER_AUTO_HIDE_TIMEOUT_MS = 350;
46 
47     private final AccessibilityManager mAccessibilityManager;
48     private final IWindowManager mWindowManagerService;
49     private final Handler mHandler;
50 
51     private AutoHideUiElement mStatusBar;
52     /** For tablets, this will represent the Taskbar */
53     private AutoHideUiElement mNavigationBar;
54     private int mDisplayId;
55 
56     private boolean mAutoHideSuspended;
57 
58     private final Runnable mAutoHide = () -> {
59         if (isAnyTransientBarShown()) {
60             hideTransientBars();
61         }
62     };
63 
64     @Inject
AutoHideController(Context context, @Main Handler handler, IWindowManager iWindowManager)65     public AutoHideController(Context context,
66             @Main Handler handler,
67             IWindowManager iWindowManager) {
68         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
69         mHandler = handler;
70         mWindowManagerService = iWindowManager;
71         mDisplayId = context.getDisplayId();
72     }
73 
74     /**
75      * Sets a {@link AutoHideUiElement} status bar that should be controlled by the
76      * {@link AutoHideController}.
77      */
setStatusBar(AutoHideUiElement element)78     public void setStatusBar(AutoHideUiElement element) {
79         mStatusBar = element;
80     }
81 
82     /**
83      * Sets a {@link AutoHideUiElement} navigation bar that should be controlled by the
84      * {@link AutoHideController}.
85      */
setNavigationBar(AutoHideUiElement element)86     public void setNavigationBar(AutoHideUiElement element) {
87         mNavigationBar = element;
88     }
89 
hideTransientBars()90     private void hideTransientBars() {
91         try {
92             mWindowManagerService.hideTransientBars(mDisplayId);
93         } catch (RemoteException ex) {
94             Log.w(TAG, "Cannot get WindowManager");
95         }
96 
97         if (mStatusBar != null) {
98             mStatusBar.hide();
99         }
100 
101         if (mNavigationBar != null) {
102             mNavigationBar.hide();
103         }
104     }
105 
resumeSuspendedAutoHide()106     public void resumeSuspendedAutoHide() {
107         if (mAutoHideSuspended) {
108             scheduleAutoHide();
109             Runnable checkBarModesRunnable = getCheckBarModesRunnable();
110             if (checkBarModesRunnable != null) {
111                 mHandler.postDelayed(checkBarModesRunnable, 500); // longer than home -> launcher
112             }
113         }
114     }
115 
suspendAutoHide()116     public void suspendAutoHide() {
117         mHandler.removeCallbacks(mAutoHide);
118         Runnable checkBarModesRunnable = getCheckBarModesRunnable();
119         if (checkBarModesRunnable != null) {
120             mHandler.removeCallbacks(checkBarModesRunnable);
121         }
122         mAutoHideSuspended = isAnyTransientBarShown();
123     }
124 
125     /** Schedules or cancels auto hide behavior based on current system bar state. */
touchAutoHide()126     public void touchAutoHide() {
127         // update transient bar auto hide
128         if (isAnyTransientBarShown()) {
129             scheduleAutoHide();
130         } else {
131             cancelAutoHide();
132         }
133     }
134 
getCheckBarModesRunnable()135     private Runnable getCheckBarModesRunnable() {
136         if (mStatusBar != null) {
137             return () -> mStatusBar.synchronizeState();
138         } else if (mNavigationBar != null) {
139             return () -> mNavigationBar.synchronizeState();
140         } else {
141             return null;
142         }
143     }
144 
cancelAutoHide()145     private void cancelAutoHide() {
146         mAutoHideSuspended = false;
147         mHandler.removeCallbacks(mAutoHide);
148     }
149 
scheduleAutoHide()150     private void scheduleAutoHide() {
151         cancelAutoHide();
152         mHandler.postDelayed(mAutoHide, getAutoHideTimeout());
153     }
154 
getAutoHideTimeout()155     private int getAutoHideTimeout() {
156         return mAccessibilityManager.getRecommendedTimeoutMillis(AUTO_HIDE_TIMEOUT_MS,
157                 FLAG_CONTENT_CONTROLS);
158     }
159 
checkUserAutoHide(MotionEvent event)160     public void checkUserAutoHide(MotionEvent event) {
161         boolean shouldHide = isAnyTransientBarShown()
162                 && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar.
163                 && event.getX() == 0 && event.getY() == 0;
164 
165         if (mStatusBar != null) {
166             shouldHide &= mStatusBar.shouldHideOnTouch();
167         }
168         if (mNavigationBar != null) {
169             shouldHide &= mNavigationBar.shouldHideOnTouch();
170         }
171 
172         if (shouldHide) {
173             userAutoHide();
174         }
175     }
176 
userAutoHide()177     private void userAutoHide() {
178         cancelAutoHide();
179         // longer than app gesture -> flag clear
180         mHandler.postDelayed(mAutoHide, getUserAutoHideTimeout());
181     }
182 
getUserAutoHideTimeout()183     private int getUserAutoHideTimeout() {
184         return mAccessibilityManager.getRecommendedTimeoutMillis(USER_AUTO_HIDE_TIMEOUT_MS,
185                 FLAG_CONTENT_CONTROLS);
186     }
187 
isAnyTransientBarShown()188     private boolean isAnyTransientBarShown() {
189         if (mStatusBar != null && mStatusBar.isVisible()) {
190             return true;
191         }
192 
193         if (mNavigationBar != null && mNavigationBar.isVisible()) {
194             return true;
195         }
196 
197         return false;
198     }
199 
dump(@onNull PrintWriter pw)200     public void dump(@NonNull PrintWriter pw) {
201         pw.println("AutoHideController:");
202         pw.println("\tmAutoHideSuspended=" + mAutoHideSuspended);
203         pw.println("\tisAnyTransientBarShown=" + isAnyTransientBarShown());
204         pw.println("\thasPendingAutoHide=" + mHandler.hasCallbacks(mAutoHide));
205         pw.println("\tgetAutoHideTimeout=" + getAutoHideTimeout());
206         pw.println("\tgetUserAutoHideTimeout=" + getUserAutoHideTimeout());
207     }
208 
209     /**
210      * Injectable factory for creating a {@link AutoHideController}.
211      */
212     public static class Factory {
213         private final Handler mHandler;
214         private final IWindowManager mIWindowManager;
215 
216         @Inject
Factory(@ain Handler handler, IWindowManager iWindowManager)217         public Factory(@Main Handler handler, IWindowManager iWindowManager) {
218             mHandler = handler;
219             mIWindowManager = iWindowManager;
220         }
221 
222         /** Create an {@link AutoHideController} */
create(Context context)223         public AutoHideController create(Context context) {
224             return new AutoHideController(context, mHandler, mIWindowManager);
225         }
226     }
227 }
228