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 NavigationBarFragment 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 void setStatusBar(StatusBar statusBar) { 89 mStatusBar = statusBar; 90 } 91 setNavigationBar(NavigationBarFragment navigationBar)92 void setNavigationBar(NavigationBarFragment 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 touchAutoHide()161 void touchAutoHide() { 162 // update transient bar auto hide 163 if ((hasStatusBar() && mStatusBar.getStatusBarMode() == MODE_SEMI_TRANSPARENT) 164 || hasNavigationBar() && mNavigationBar.isSemiTransparent()) { 165 scheduleAutoHide(); 166 } else { 167 cancelAutoHide(); 168 } 169 } 170 getCheckBarModesRunnable()171 private Runnable getCheckBarModesRunnable() { 172 if (hasStatusBar()) { 173 return () -> mStatusBar.checkBarModes(); 174 } else if (hasNavigationBar()) { 175 return () -> mNavigationBar.checkNavBarModes(); 176 } else { 177 return null; 178 } 179 } 180 cancelAutoHide()181 private void cancelAutoHide() { 182 mAutoHideSuspended = false; 183 mHandler.removeCallbacks(mAutoHide); 184 } 185 scheduleAutoHide()186 private void scheduleAutoHide() { 187 cancelAutoHide(); 188 mHandler.postDelayed(mAutoHide, AUTOHIDE_TIMEOUT_MS); 189 } 190 checkUserAutoHide(MotionEvent event)191 void checkUserAutoHide(MotionEvent event) { 192 boolean shouldAutoHide = 193 (mSystemUiVisibility & getTransientMask()) != 0 // a transient bar is revealed. 194 && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar. 195 && event.getX() == 0 && event.getY() == 0; 196 if (hasStatusBar()) { 197 // a touch outside both bars 198 shouldAutoHide &= !mRemoteInputManager.getController().isRemoteInputActive(); 199 } 200 if (shouldAutoHide) { 201 userAutoHide(); 202 } 203 } 204 userAutoHide()205 private void userAutoHide() { 206 cancelAutoHide(); 207 mHandler.postDelayed(mAutoHide, 350); // longer than app gesture -> flag clear 208 } 209 getTransientMask()210 private int getTransientMask() { 211 int mask = 0; 212 if (hasStatusBar()) { 213 mask |= View.STATUS_BAR_TRANSIENT; 214 } 215 if (hasNavigationBar()) { 216 mask |= View.NAVIGATION_BAR_TRANSIENT; 217 } 218 return mask; 219 } 220 hasNavigationBar()221 boolean hasNavigationBar() { 222 return mNavigationBar != null; 223 } 224 225 @VisibleForTesting hasStatusBar()226 boolean hasStatusBar() { 227 return mStatusBar != null; 228 } 229 } 230