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 com.android.systemui.Dependency.MAIN_HANDLER_NAME;
20 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
21 
22 import android.content.Context;
23 import android.graphics.Rect;
24 import android.os.Handler;
25 import android.os.RemoteException;
26 import android.util.Log;
27 import android.view.IWindowManager;
28 import android.view.MotionEvent;
29 import android.view.View;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.systemui.Dependency;
33 import com.android.systemui.SysUiServiceProvider;
34 import com.android.systemui.statusbar.CommandQueue;
35 import com.android.systemui.statusbar.NotificationRemoteInputManager;
36 
37 import javax.inject.Inject;
38 import javax.inject.Named;
39 
40 /** A controller to control all auto-hide things. */
41 public class AutoHideController implements CommandQueue.Callbacks {
42     private static final String TAG = "AutoHideController";
43 
44     private final IWindowManager mWindowManagerService;
45 
46     private final Handler mHandler;
47     private final NotificationRemoteInputManager mRemoteInputManager;
48     private final CommandQueue mCommandQueue;
49     private StatusBar mStatusBar;
50     private AutoHideElement mNavigationBar;
51 
52     @VisibleForTesting
53     int mDisplayId;
54     @VisibleForTesting
55     int mSystemUiVisibility;
56     // last value sent to window manager
57     private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE;
58 
59     private boolean mAutoHideSuspended;
60 
61     private static final long AUTOHIDE_TIMEOUT_MS = 2250;
62 
63     private final Runnable mAutoHide = () -> {
64         int requested = mSystemUiVisibility & ~getTransientMask();
65         if (mSystemUiVisibility != requested) {
66             notifySystemUiVisibilityChanged(requested);
67         }
68     };
69 
70     @Inject
AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler)71     public AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler) {
72         mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class);
73         mCommandQueue.addCallback(this);
74         mHandler = handler;
75         mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
76         mWindowManagerService = Dependency.get(IWindowManager.class);
77 
78         mDisplayId = context.getDisplayId();
79     }
80 
81     @Override
onDisplayRemoved(int displayId)82     public void onDisplayRemoved(int displayId) {
83         if (displayId == mDisplayId) {
84             mCommandQueue.removeCallback(this);
85         }
86     }
87 
setStatusBar(StatusBar statusBar)88     public void setStatusBar(StatusBar statusBar) {
89         mStatusBar = statusBar;
90     }
91 
setNavigationBar(AutoHideElement navigationBar)92     public void setNavigationBar(AutoHideElement navigationBar) {
93         mNavigationBar = navigationBar;
94     }
95 
96     @Override
setSystemUiVisibility(int displayId, int vis, int fullscreenStackVis, int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds, boolean navbarColorManagedByIme)97     public void setSystemUiVisibility(int displayId, int vis, int fullscreenStackVis,
98             int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds,
99             boolean navbarColorManagedByIme) {
100         if (displayId != mDisplayId) {
101             return;
102         }
103         int oldVal = mSystemUiVisibility;
104         int newVal = (oldVal & ~mask) | (vis & mask);
105         int diff = newVal ^ oldVal;
106 
107         if (diff != 0) {
108             mSystemUiVisibility = newVal;
109 
110             // ready to unhide
111             if (hasStatusBar() && (vis & View.STATUS_BAR_UNHIDE) != 0) {
112                 mSystemUiVisibility &= ~View.STATUS_BAR_UNHIDE;
113             }
114 
115             if (hasNavigationBar() && (vis & View.NAVIGATION_BAR_UNHIDE) != 0) {
116                 mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE;
117             }
118 
119             // Re-send setSystemUiVisibility to update un-hide status.
120             if (mSystemUiVisibility != newVal) {
121                 mCommandQueue.setSystemUiVisibility(mDisplayId, mSystemUiVisibility,
122                         fullscreenStackVis, dockedStackVis, mask, fullscreenStackBounds,
123                         dockedStackBounds, navbarColorManagedByIme);
124             }
125 
126             notifySystemUiVisibilityChanged(mSystemUiVisibility);
127         }
128     }
129 
130     @VisibleForTesting
notifySystemUiVisibilityChanged(int vis)131     void notifySystemUiVisibilityChanged(int vis) {
132         try {
133             if (mLastDispatchedSystemUiVisibility != vis) {
134                 mWindowManagerService.statusBarVisibilityChanged(mDisplayId, vis);
135                 mLastDispatchedSystemUiVisibility = vis;
136             }
137         } catch (RemoteException ex) {
138             Log.w(TAG, "Cannot get WindowManager");
139         }
140     }
141 
resumeSuspendedAutoHide()142     void resumeSuspendedAutoHide() {
143         if (mAutoHideSuspended) {
144             scheduleAutoHide();
145             Runnable checkBarModesRunnable = getCheckBarModesRunnable();
146             if (checkBarModesRunnable != null) {
147                 mHandler.postDelayed(checkBarModesRunnable, 500); // longer than home -> launcher
148             }
149         }
150     }
151 
suspendAutoHide()152     void suspendAutoHide() {
153         mHandler.removeCallbacks(mAutoHide);
154         Runnable checkBarModesRunnable = getCheckBarModesRunnable();
155         if (checkBarModesRunnable != null) {
156             mHandler.removeCallbacks(checkBarModesRunnable);
157         }
158         mAutoHideSuspended = (mSystemUiVisibility & getTransientMask()) != 0;
159     }
160 
161     /** Schedule auto hide if necessary otherwise cancel any pending runnables. */
touchAutoHide()162     public void touchAutoHide() {
163         // update transient bar auto hide
164         if ((hasStatusBar() && mStatusBar.getStatusBarMode() == MODE_SEMI_TRANSPARENT)
165                 || hasNavigationBar() && mNavigationBar.isSemiTransparent()) {
166             scheduleAutoHide();
167         } else {
168             cancelAutoHide();
169         }
170     }
171 
getCheckBarModesRunnable()172     private Runnable getCheckBarModesRunnable() {
173         if (hasStatusBar()) {
174             return () -> mStatusBar.checkBarModes();
175         } else if (hasNavigationBar()) {
176             return () -> mNavigationBar.synchronizeState();
177         } else {
178             return null;
179         }
180     }
181 
182     /** Remove any scheduled auto hide runnables. */
cancelAutoHide()183     public void cancelAutoHide() {
184         mAutoHideSuspended = false;
185         mHandler.removeCallbacks(mAutoHide);
186     }
187 
scheduleAutoHide()188     private void scheduleAutoHide() {
189         cancelAutoHide();
190         mHandler.postDelayed(mAutoHide, AUTOHIDE_TIMEOUT_MS);
191     }
192 
checkUserAutoHide(MotionEvent event)193     void checkUserAutoHide(MotionEvent event) {
194         boolean shouldAutoHide =
195                 (mSystemUiVisibility & getTransientMask()) != 0  // a transient bar is revealed.
196                 && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar.
197                 && event.getX() == 0 && event.getY() == 0;
198         if (hasStatusBar()) {
199             // a touch outside both bars
200             shouldAutoHide &= !mRemoteInputManager.getController().isRemoteInputActive();
201         }
202         if (shouldAutoHide) {
203             userAutoHide();
204         }
205     }
206 
207     /** Schedule auto hide. */
userAutoHide()208     public void userAutoHide() {
209         cancelAutoHide();
210         mHandler.postDelayed(mAutoHide, 350); // longer than app gesture -> flag clear
211     }
212 
getTransientMask()213     private int getTransientMask() {
214         int mask = 0;
215         if (hasStatusBar()) {
216             mask |= View.STATUS_BAR_TRANSIENT;
217         }
218         if (hasNavigationBar()) {
219             mask |= View.NAVIGATION_BAR_TRANSIENT;
220         }
221         return mask;
222     }
223 
hasNavigationBar()224     boolean hasNavigationBar() {
225         return mNavigationBar != null;
226     }
227 
228     @VisibleForTesting
hasStatusBar()229     boolean hasStatusBar() {
230         return mStatusBar != null;
231     }
232 }
233