1 /* 2 * Copyright 2021 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.navigationbar; 18 19 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; 20 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; 21 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; 22 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; 23 import static android.view.InsetsState.containsType; 24 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; 25 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 26 27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; 28 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; 29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY; 30 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED; 31 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; 32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; 33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; 34 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; 35 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; 36 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; 37 38 import android.app.StatusBarManager; 39 import android.app.StatusBarManager.WindowVisibleState; 40 import android.content.ComponentCallbacks; 41 import android.content.Context; 42 import android.content.res.Configuration; 43 import android.graphics.Rect; 44 import android.hardware.display.DisplayManager; 45 import android.inputmethodservice.InputMethodService; 46 import android.os.IBinder; 47 import android.os.RemoteException; 48 import android.util.Log; 49 import android.view.Display; 50 import android.view.InsetsVisibilities; 51 import android.view.View; 52 import android.view.WindowInsetsController.Behavior; 53 54 import androidx.annotation.NonNull; 55 56 import com.android.internal.view.AppearanceRegion; 57 import com.android.systemui.Dependency; 58 import com.android.systemui.Dumpable; 59 import com.android.systemui.dump.DumpManager; 60 import com.android.systemui.model.SysUiState; 61 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; 62 import com.android.systemui.recents.OverviewProxyService; 63 import com.android.systemui.shared.recents.utilities.Utilities; 64 import com.android.systemui.shared.system.ActivityManagerWrapper; 65 import com.android.systemui.shared.system.QuickStepContract; 66 import com.android.systemui.statusbar.AutoHideUiElement; 67 import com.android.systemui.statusbar.CommandQueue; 68 import com.android.systemui.statusbar.phone.AutoHideController; 69 import com.android.systemui.statusbar.phone.BarTransitions; 70 import com.android.systemui.statusbar.phone.LightBarController; 71 import com.android.systemui.statusbar.phone.LightBarTransitionsController; 72 import com.android.wm.shell.pip.Pip; 73 74 import java.io.FileDescriptor; 75 import java.io.PrintWriter; 76 import java.util.Optional; 77 import java.util.function.Consumer; 78 79 import javax.inject.Inject; 80 import javax.inject.Singleton; 81 82 @Singleton 83 public class TaskbarDelegate implements CommandQueue.Callbacks, 84 OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener, 85 ComponentCallbacks, Dumpable { 86 private static final String TAG = TaskbarDelegate.class.getSimpleName(); 87 88 private final EdgeBackGestureHandler mEdgeBackGestureHandler; 89 private boolean mInitialized; 90 private CommandQueue mCommandQueue; 91 private OverviewProxyService mOverviewProxyService; 92 private NavBarHelper mNavBarHelper; 93 private NavigationModeController mNavigationModeController; 94 private SysUiState mSysUiState; 95 private AutoHideController mAutoHideController; 96 private LightBarController mLightBarController; 97 private LightBarTransitionsController mLightBarTransitionsController; 98 private Optional<Pip> mPipOptional; 99 private int mDisplayId; 100 private int mNavigationIconHints; 101 private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater = 102 new NavBarHelper.NavbarTaskbarStateUpdater() { 103 @Override 104 public void updateAccessibilityServicesState() { 105 updateSysuiFlags(); 106 } 107 108 @Override 109 public void updateAssistantAvailable(boolean available) { 110 updateAssistantAvailability(available); 111 } 112 }; 113 private int mDisabledFlags; 114 private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING; 115 private @Behavior int mBehavior; 116 private final Context mContext; 117 private final DisplayManager mDisplayManager; 118 private Context mWindowContext; 119 private ScreenPinningNotify mScreenPinningNotify; 120 private int mNavigationMode; 121 private final Consumer<Rect> mPipListener; 122 123 /** 124 * Tracks the system calls for when taskbar should transiently show or hide so we can return 125 * this value in {@link AutoHideUiElement#isVisible()} below. 126 * 127 * This also gets set by {@link #onTaskbarAutohideSuspend(boolean)} to force show the transient 128 * taskbar if launcher has requested to suspend auto-hide behavior. 129 */ 130 private boolean mTaskbarTransientShowing; 131 private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() { 132 @Override 133 public void synchronizeState() { 134 } 135 136 @Override 137 public boolean isVisible() { 138 return mTaskbarTransientShowing; 139 } 140 141 @Override 142 public void hide() { 143 } 144 }; 145 146 @Inject TaskbarDelegate(Context context)147 public TaskbarDelegate(Context context) { 148 mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class) 149 .create(context); 150 mContext = context; 151 mDisplayManager = mContext.getSystemService(DisplayManager.class); 152 mPipListener = mEdgeBackGestureHandler::setPipStashExclusionBounds; 153 } 154 setDependencies(CommandQueue commandQueue, OverviewProxyService overviewProxyService, NavBarHelper navBarHelper, NavigationModeController navigationModeController, SysUiState sysUiState, DumpManager dumpManager, AutoHideController autoHideController, LightBarController lightBarController, Optional<Pip> pipOptional)155 public void setDependencies(CommandQueue commandQueue, 156 OverviewProxyService overviewProxyService, 157 NavBarHelper navBarHelper, 158 NavigationModeController navigationModeController, 159 SysUiState sysUiState, DumpManager dumpManager, 160 AutoHideController autoHideController, 161 LightBarController lightBarController, 162 Optional<Pip> pipOptional) { 163 // TODO: adding this in the ctor results in a dagger dependency cycle :( 164 mCommandQueue = commandQueue; 165 mOverviewProxyService = overviewProxyService; 166 mNavBarHelper = navBarHelper; 167 mNavigationModeController = navigationModeController; 168 mSysUiState = sysUiState; 169 dumpManager.registerDumpable(this); 170 mAutoHideController = autoHideController; 171 mLightBarController = lightBarController; 172 mLightBarTransitionsController = createLightBarTransitionsController(); 173 mPipOptional = pipOptional; 174 } 175 176 // Separated into a method to keep setDependencies() clean/readable. createLightBarTransitionsController()177 private LightBarTransitionsController createLightBarTransitionsController() { 178 return new LightBarTransitionsController(mContext, 179 new LightBarTransitionsController.DarkIntensityApplier() { 180 @Override 181 public void applyDarkIntensity(float darkIntensity) { 182 mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity); 183 } 184 185 @Override 186 public int getTintAnimationDuration() { 187 return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION; 188 } 189 }, mCommandQueue) { 190 @Override 191 public boolean supportsIconTintForNavMode(int navigationMode) { 192 // Always tint taskbar nav buttons (region sampling handles gesture bar separately). 193 return true; 194 } 195 }; 196 } 197 198 public void init(int displayId) { 199 if (mInitialized) { 200 return; 201 } 202 mDisplayId = displayId; 203 mCommandQueue.addCallback(this); 204 mOverviewProxyService.addCallback(this); 205 mEdgeBackGestureHandler.onNavigationModeChanged( 206 mNavigationModeController.addListener(this)); 207 mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 208 mNavBarHelper.init(); 209 mEdgeBackGestureHandler.onNavBarAttached(); 210 // Initialize component callback 211 Display display = mDisplayManager.getDisplay(displayId); 212 mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null); 213 mWindowContext.registerComponentCallbacks(this); 214 mScreenPinningNotify = new ScreenPinningNotify(mWindowContext); 215 // Set initial state for any listeners 216 updateSysuiFlags(); 217 mAutoHideController.setNavigationBar(mAutoHideUiElement); 218 mLightBarController.setNavigationBar(mLightBarTransitionsController); 219 mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener); 220 mInitialized = true; 221 } 222 223 public void destroy() { 224 if (!mInitialized) { 225 return; 226 } 227 mCommandQueue.removeCallback(this); 228 mOverviewProxyService.removeCallback(this); 229 mNavigationModeController.removeListener(this); 230 mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); 231 mNavBarHelper.destroy(); 232 mEdgeBackGestureHandler.onNavBarDetached(); 233 mScreenPinningNotify = null; 234 if (mWindowContext != null) { 235 mWindowContext.unregisterComponentCallbacks(this); 236 mWindowContext = null; 237 } 238 mAutoHideController.setNavigationBar(null); 239 mLightBarTransitionsController.destroy(mContext); 240 mLightBarController.setNavigationBar(null); 241 mPipOptional.ifPresent(this::removePipExclusionBoundsChangeListener); 242 mInitialized = false; 243 } 244 245 void addPipExclusionBoundsChangeListener(Pip pip) { 246 pip.addPipExclusionBoundsChangeListener(mPipListener); 247 } 248 249 void removePipExclusionBoundsChangeListener(Pip pip) { 250 pip.removePipExclusionBoundsChangeListener(mPipListener); 251 } 252 253 /** 254 * Returns {@code true} if this taskBar is {@link #init(int)}. Returns {@code false} if this 255 * taskbar has not yet been {@link #init(int)} or has been {@link #destroy()}. 256 */ 257 public boolean isInitialized() { 258 return mInitialized; 259 } 260 261 private void updateSysuiFlags() { 262 int a11yFlags = mNavBarHelper.getA11yButtonState(); 263 boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; 264 boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; 265 266 mSysUiState.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable) 267 .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable) 268 .setFlag(SYSUI_STATE_IME_SHOWING, 269 (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0) 270 .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING, 271 (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0) 272 .setFlag(SYSUI_STATE_OVERVIEW_DISABLED, 273 (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0) 274 .setFlag(SYSUI_STATE_HOME_DISABLED, 275 (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0) 276 .setFlag(SYSUI_STATE_BACK_DISABLED, 277 (mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) 278 .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible()) 279 .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, 280 allowSystemGestureIgnoringBarVisibility()) 281 .setFlag(SYSUI_STATE_SCREEN_PINNING, 282 ActivityManagerWrapper.getInstance().isScreenPinningActive()) 283 .commitUpdate(mDisplayId); 284 } 285 286 private void updateAssistantAvailability(boolean assistantAvailable) { 287 if (mOverviewProxyService.getProxy() == null) { 288 return; 289 } 290 291 try { 292 mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable); 293 } catch (RemoteException e) { 294 Log.e(TAG, "onAssistantAvailable() failed, available: " + assistantAvailable, e); 295 } 296 } 297 298 @Override 299 public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, 300 boolean showImeSwitcher) { 301 boolean imeShown = mNavBarHelper.isImeShown(vis); 302 if (!imeShown) { 303 // Count imperceptible changes as visible so we transition taskbar out quickly. 304 imeShown = (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0; 305 } 306 showImeSwitcher = imeShown && showImeSwitcher; 307 int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition, 308 imeShown, showImeSwitcher); 309 if (hints != mNavigationIconHints) { 310 mNavigationIconHints = hints; 311 updateSysuiFlags(); 312 } 313 } 314 315 @Override 316 public void setWindowState(int displayId, int window, int state) { 317 if (displayId == mDisplayId 318 && window == StatusBarManager.WINDOW_NAVIGATION_BAR 319 && mTaskBarWindowState != state) { 320 mTaskBarWindowState = state; 321 updateSysuiFlags(); 322 } 323 } 324 325 @Override 326 public void onRotationProposal(int rotation, boolean isValid) { 327 mOverviewProxyService.onRotationProposal(rotation, isValid); 328 } 329 330 @Override 331 public void disable(int displayId, int state1, int state2, boolean animate) { 332 mDisabledFlags = state1; 333 updateSysuiFlags(); 334 mOverviewProxyService.disable(displayId, state1, state2, animate); 335 } 336 337 @Override 338 public void onSystemBarAttributesChanged(int displayId, int appearance, 339 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior, 340 InsetsVisibilities requestedVisibilities, String packageName) { 341 mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior); 342 if (mLightBarController != null && displayId == mDisplayId) { 343 mLightBarController.onNavigationBarAppearanceChanged(appearance, false/*nbModeChanged*/, 344 BarTransitions.MODE_TRANSPARENT /*navigationBarMode*/, navbarColorManagedByIme); 345 } 346 if (mBehavior != behavior) { 347 mBehavior = behavior; 348 updateSysuiFlags(); 349 } 350 } 351 352 @Override 353 public void showTransient(int displayId, int[] types, boolean isGestureOnSystemBar) { 354 if (displayId != mDisplayId) { 355 return; 356 } 357 if (!containsType(types, ITYPE_NAVIGATION_BAR)) { 358 return; 359 } 360 mTaskbarTransientShowing = true; 361 } 362 363 @Override 364 public void abortTransient(int displayId, int[] types) { 365 if (displayId != mDisplayId) { 366 return; 367 } 368 if (!containsType(types, ITYPE_NAVIGATION_BAR)) { 369 return; 370 } 371 mTaskbarTransientShowing = false; 372 } 373 374 @Override 375 public void onTaskbarAutohideSuspend(boolean suspend) { 376 mTaskbarTransientShowing = suspend; 377 if (suspend) { 378 mAutoHideController.suspendAutoHide(); 379 } else { 380 mAutoHideController.resumeSuspendedAutoHide(); 381 } 382 } 383 384 @Override 385 public void onNavigationModeChanged(int mode) { 386 mNavigationMode = mode; 387 mEdgeBackGestureHandler.onNavigationModeChanged(mode); 388 } 389 390 private boolean isWindowVisible() { 391 return mTaskBarWindowState == WINDOW_STATE_SHOWING; 392 } 393 394 private boolean allowSystemGestureIgnoringBarVisibility() { 395 return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; 396 } 397 398 @Override 399 public void onConfigurationChanged(Configuration configuration) { 400 mEdgeBackGestureHandler.onConfigurationChanged(configuration); 401 } 402 403 @Override 404 public void onLowMemory() {} 405 406 @Override 407 public void showPinningEnterExitToast(boolean entering) { 408 updateSysuiFlags(); 409 if (mScreenPinningNotify == null) { 410 return; 411 } 412 if (entering) { 413 mScreenPinningNotify.showPinningStartToast(); 414 } else { 415 mScreenPinningNotify.showPinningExitToast(); 416 } 417 } 418 419 @Override 420 public void showPinningEscapeToast() { 421 updateSysuiFlags(); 422 if (mScreenPinningNotify == null) { 423 return; 424 } 425 mScreenPinningNotify.showEscapeToast(QuickStepContract.isGesturalMode(mNavigationMode), 426 !QuickStepContract.isGesturalMode(mNavigationMode)); 427 } 428 429 @Override 430 public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { 431 pw.println("TaskbarDelegate (displayId=" + mDisplayId + "):"); 432 pw.println(" mNavigationIconHints=" + mNavigationIconHints); 433 pw.println(" mNavigationMode=" + mNavigationMode); 434 pw.println(" mDisabledFlags=" + mDisabledFlags); 435 pw.println(" mTaskBarWindowState=" + mTaskBarWindowState); 436 pw.println(" mBehavior=" + mBehavior); 437 pw.println(" mTaskbarTransientShowing=" + mTaskbarTransientShowing); 438 mEdgeBackGestureHandler.dump(pw); 439 } 440 } 441