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