1 /* 2 * Copyright (C) 2020 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.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; 20 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; 21 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; 22 23 import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG; 24 import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; 25 26 import android.content.Context; 27 import android.content.pm.ActivityInfo; 28 import android.content.res.Configuration; 29 import android.hardware.display.DisplayManager; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.RemoteException; 33 import android.os.Trace; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.util.Log; 37 import android.util.SparseArray; 38 import android.view.Display; 39 import android.view.IWindowManager; 40 import android.view.View; 41 import android.view.WindowManagerGlobal; 42 43 import androidx.annotation.NonNull; 44 import androidx.annotation.Nullable; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.internal.statusbar.RegisterStatusBarResult; 48 import com.android.settingslib.applications.InterestingConfigChanges; 49 import com.android.systemui.Dumpable; 50 import com.android.systemui.dagger.SysUISingleton; 51 import com.android.systemui.dagger.qualifiers.Main; 52 import com.android.systemui.dump.DumpManager; 53 import com.android.systemui.flags.FeatureFlags; 54 import com.android.systemui.flags.Flags; 55 import com.android.systemui.model.SysUiState; 56 import com.android.systemui.recents.OverviewProxyService; 57 import com.android.systemui.settings.DisplayTracker; 58 import com.android.systemui.shared.system.QuickStepContract; 59 import com.android.systemui.shared.system.TaskStackChangeListeners; 60 import com.android.systemui.statusbar.CommandQueue; 61 import com.android.systemui.statusbar.CommandQueue.Callbacks; 62 import com.android.systemui.statusbar.phone.AutoHideController; 63 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; 64 import com.android.systemui.statusbar.phone.LightBarController; 65 import com.android.systemui.statusbar.policy.ConfigurationController; 66 import com.android.systemui.util.settings.SecureSettings; 67 import com.android.wm.shell.back.BackAnimation; 68 import com.android.wm.shell.pip.Pip; 69 70 import java.io.PrintWriter; 71 import java.util.Optional; 72 73 import javax.inject.Inject; 74 75 /** A controller to handle navigation bars. */ 76 @SysUISingleton 77 public class NavigationBarController implements 78 Callbacks, 79 ConfigurationController.ConfigurationListener, 80 NavigationModeController.ModeChangedListener, 81 Dumpable { 82 83 private static final String TAG = NavigationBarController.class.getSimpleName(); 84 85 private final Context mContext; 86 private final Handler mHandler; 87 private final NavigationBarComponent.Factory mNavigationBarComponentFactory; 88 private FeatureFlags mFeatureFlags; 89 private final SecureSettings mSecureSettings; 90 private final DisplayTracker mDisplayTracker; 91 private final DisplayManager mDisplayManager; 92 private final TaskbarDelegate mTaskbarDelegate; 93 private final NavBarHelper mNavBarHelper; 94 private int mNavMode; 95 @VisibleForTesting boolean mIsLargeScreen; 96 97 /** A displayId - nav bar maps. */ 98 @VisibleForTesting 99 SparseArray<NavigationBar> mNavigationBars = new SparseArray<>(); 100 101 // Tracks config changes that will actually recreate the nav bar 102 private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( 103 ActivityInfo.CONFIG_FONT_SCALE 104 | ActivityInfo.CONFIG_UI_MODE); 105 106 @Inject NavigationBarController(Context context, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, SysUiState sysUiFlagsContainer, CommandQueue commandQueue, @Main Handler mainHandler, ConfigurationController configurationController, NavBarHelper navBarHelper, TaskbarDelegate taskbarDelegate, NavigationBarComponent.Factory navigationBarComponentFactory, DumpManager dumpManager, AutoHideController autoHideController, LightBarController lightBarController, TaskStackChangeListeners taskStackChangeListeners, Optional<Pip> pipOptional, Optional<BackAnimation> backAnimation, FeatureFlags featureFlags, SecureSettings secureSettings, DisplayTracker displayTracker)107 public NavigationBarController(Context context, 108 OverviewProxyService overviewProxyService, 109 NavigationModeController navigationModeController, 110 SysUiState sysUiFlagsContainer, 111 CommandQueue commandQueue, 112 @Main Handler mainHandler, 113 ConfigurationController configurationController, 114 NavBarHelper navBarHelper, 115 TaskbarDelegate taskbarDelegate, 116 NavigationBarComponent.Factory navigationBarComponentFactory, 117 DumpManager dumpManager, 118 AutoHideController autoHideController, 119 LightBarController lightBarController, 120 TaskStackChangeListeners taskStackChangeListeners, 121 Optional<Pip> pipOptional, 122 Optional<BackAnimation> backAnimation, 123 FeatureFlags featureFlags, 124 SecureSettings secureSettings, 125 DisplayTracker displayTracker) { 126 mContext = context; 127 mHandler = mainHandler; 128 mNavigationBarComponentFactory = navigationBarComponentFactory; 129 mFeatureFlags = featureFlags; 130 mSecureSettings = secureSettings; 131 mDisplayTracker = displayTracker; 132 mDisplayManager = mContext.getSystemService(DisplayManager.class); 133 commandQueue.addCallback(this); 134 configurationController.addCallback(this); 135 mConfigChanges.applyNewConfig(mContext.getResources()); 136 mNavMode = navigationModeController.addListener(this); 137 mNavBarHelper = navBarHelper; 138 mTaskbarDelegate = taskbarDelegate; 139 mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, 140 navBarHelper, navigationModeController, sysUiFlagsContainer, 141 dumpManager, autoHideController, lightBarController, pipOptional, 142 backAnimation.orElse(null), taskStackChangeListeners); 143 mIsLargeScreen = isLargeScreen(mContext); 144 dumpManager.registerDumpable(this); 145 } 146 147 @Override onConfigChanged(Configuration newConfig)148 public void onConfigChanged(Configuration newConfig) { 149 boolean isOldConfigLargeScreen = mIsLargeScreen; 150 mIsLargeScreen = isLargeScreen(mContext); 151 boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources()); 152 boolean largeScreenChanged = mIsLargeScreen != isOldConfigLargeScreen; 153 // TODO(b/243765256): Disable this logging once b/243765256 is fixed. 154 Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig 155 + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized() 156 + " willApplyConfigToNavbars=" + willApplyConfig 157 + " navBarCount=" + mNavigationBars.size()); 158 if (mTaskbarDelegate.isInitialized()) { 159 mTaskbarDelegate.onConfigurationChanged(newConfig); 160 } 161 // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded 162 if (largeScreenChanged && updateNavbarForTaskbar()) { 163 return; 164 } 165 166 if (willApplyConfig) { 167 for (int i = 0; i < mNavigationBars.size(); i++) { 168 recreateNavigationBar(mNavigationBars.keyAt(i)); 169 } 170 } else { 171 for (int i = 0; i < mNavigationBars.size(); i++) { 172 mNavigationBars.valueAt(i).onConfigurationChanged(newConfig); 173 } 174 } 175 } 176 177 @Override onNavigationModeChanged(int mode)178 public void onNavigationModeChanged(int mode) { 179 if (mNavMode == mode) { 180 return; 181 } 182 final int oldMode = mNavMode; 183 mNavMode = mode; 184 updateAccessibilityButtonModeIfNeeded(); 185 186 mHandler.post(() -> { 187 // create/destroy nav bar based on nav mode only in unfolded state 188 if (oldMode != mNavMode) { 189 updateNavbarForTaskbar(); 190 } 191 for (int i = 0; i < mNavigationBars.size(); i++) { 192 NavigationBar navBar = mNavigationBars.valueAt(i); 193 if (navBar == null) { 194 continue; 195 } 196 navBar.getView().updateStates(); 197 } 198 }); 199 } 200 updateAccessibilityButtonModeIfNeeded()201 private void updateAccessibilityButtonModeIfNeeded() { 202 final int mode = mSecureSettings.getIntForUser( 203 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 204 ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT); 205 206 // ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU is compatible under gestural or non-gestural 207 // mode, so we don't need to update it. 208 if (mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { 209 return; 210 } 211 212 // ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR is incompatible under gestural mode. Need to 213 // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE. 214 if (QuickStepContract.isGesturalMode(mNavMode) 215 && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) { 216 mSecureSettings.putIntForUser( 217 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE, 218 UserHandle.USER_CURRENT); 219 // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to 220 // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR. 221 } else if (!QuickStepContract.isGesturalMode(mNavMode) 222 && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) { 223 mSecureSettings.putIntForUser( 224 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 225 ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT); 226 } 227 } 228 shouldCreateNavBarAndTaskBar(int displayId)229 private boolean shouldCreateNavBarAndTaskBar(int displayId) { 230 final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); 231 232 try { 233 return wms.hasNavigationBar(displayId); 234 } catch (RemoteException e) { 235 // Cannot get wms, just return false with warning message. 236 Log.w(TAG, "Cannot get WindowManager."); 237 return false; 238 } 239 } 240 241 /** @see #initializeTaskbarIfNecessary() */ updateNavbarForTaskbar()242 private boolean updateNavbarForTaskbar() { 243 boolean taskbarShown = initializeTaskbarIfNecessary(); 244 if (!taskbarShown && mNavigationBars.get(mContext.getDisplayId()) == null) { 245 createNavigationBar(mContext.getDisplay(), null, null); 246 } 247 return taskbarShown; 248 } 249 250 /** @return {@code true} if taskbar is enabled, false otherwise */ initializeTaskbarIfNecessary()251 private boolean initializeTaskbarIfNecessary() { 252 // Enable for large screens or (phone AND flag is set); assuming phone = !mIsLargeScreen 253 boolean taskbarEnabled = (mIsLargeScreen || mFeatureFlags.isEnabled( 254 Flags.HIDE_NAVBAR_WINDOW)) && shouldCreateNavBarAndTaskBar(mContext.getDisplayId()); 255 256 if (taskbarEnabled) { 257 Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary"); 258 final int displayId = mContext.getDisplayId(); 259 // Hint to NavBarHelper if we are replacing an existing bar to skip extra work 260 mNavBarHelper.setTogglingNavbarTaskbar(mNavigationBars.contains(displayId)); 261 // Remove navigation bar when taskbar is showing 262 removeNavigationBar(displayId); 263 mTaskbarDelegate.init(displayId); 264 mNavBarHelper.setTogglingNavbarTaskbar(false); 265 Trace.endSection(); 266 267 } else { 268 mTaskbarDelegate.destroy(); 269 } 270 return taskbarEnabled; 271 } 272 273 @Override onDisplayRemoved(int displayId)274 public void onDisplayRemoved(int displayId) { 275 removeNavigationBar(displayId); 276 } 277 278 @Override onDisplayReady(int displayId)279 public void onDisplayReady(int displayId) { 280 Display display = mDisplayManager.getDisplay(displayId); 281 mIsLargeScreen = isLargeScreen(mContext); 282 createNavigationBar(display, null /* savedState */, null /* result */); 283 } 284 285 @Override setNavigationBarLumaSamplingEnabled(int displayId, boolean enable)286 public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) { 287 final NavigationBar navigationBar = getNavigationBar(displayId); 288 if (navigationBar != null) { 289 navigationBar.setNavigationBarLumaSamplingEnabled(enable); 290 } 291 } 292 293 /** 294 * Recreates the navigation bar for the given display. 295 */ recreateNavigationBar(int displayId)296 private void recreateNavigationBar(int displayId) { 297 // TODO: Improve this flow so that we don't need to create a new nav bar but just 298 // the view 299 Bundle savedState = new Bundle(); 300 NavigationBar bar = mNavigationBars.get(displayId); 301 if (bar != null) { 302 bar.onSaveInstanceState(savedState); 303 } 304 removeNavigationBar(displayId); 305 createNavigationBar(mDisplayManager.getDisplay(displayId), savedState, null /* result */); 306 } 307 308 // TODO(b/117478341): I use {@code includeDefaultDisplay} to make this method compatible to 309 // CarStatusBar because they have their own nav bar. Think about a better way for it. 310 /** 311 * Creates navigation bars when car/status bar initializes. 312 * 313 * @param includeDefaultDisplay {@code true} to create navigation bar on default display. 314 */ createNavigationBars(final boolean includeDefaultDisplay, RegisterStatusBarResult result)315 public void createNavigationBars(final boolean includeDefaultDisplay, 316 RegisterStatusBarResult result) { 317 updateAccessibilityButtonModeIfNeeded(); 318 319 // Don't need to create nav bar on the default display if we initialize TaskBar. 320 final boolean shouldCreateDefaultNavbar = includeDefaultDisplay 321 && !initializeTaskbarIfNecessary(); 322 Display[] displays = mDisplayTracker.getAllDisplays(); 323 for (Display display : displays) { 324 if (shouldCreateDefaultNavbar 325 || display.getDisplayId() != mDisplayTracker.getDefaultDisplayId()) { 326 createNavigationBar(display, null /* savedState */, result); 327 } 328 } 329 } 330 331 /** 332 * Adds a navigation bar on default display or an external display if the display supports 333 * system decorations. 334 * 335 * @param display the display to add navigation bar on. 336 */ 337 @VisibleForTesting createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result)338 void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) { 339 if (display == null) { 340 return; 341 } 342 343 final int displayId = display.getDisplayId(); 344 final boolean isOnDefaultDisplay = displayId == mDisplayTracker.getDefaultDisplayId(); 345 346 if (!shouldCreateNavBarAndTaskBar(displayId)) { 347 return; 348 } 349 350 // We may show TaskBar on the default display for large screen device. Don't need to create 351 // navigation bar for this case. 352 if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) { 353 return; 354 } 355 356 final Context context = isOnDefaultDisplay 357 ? mContext 358 : mContext.createDisplayContext(display); 359 NavigationBarComponent component = mNavigationBarComponentFactory.create( 360 context, savedState); 361 NavigationBar navBar = component.getNavigationBar(); 362 navBar.init(); 363 mNavigationBars.put(displayId, navBar); 364 365 navBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 366 @Override 367 public void onViewAttachedToWindow(View v) { 368 if (result != null) { 369 navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken, 370 result.mImeWindowVis, result.mImeBackDisposition, 371 result.mShowImeSwitcher); 372 } 373 } 374 375 @Override 376 public void onViewDetachedFromWindow(View v) { 377 v.removeOnAttachStateChangeListener(this); 378 } 379 }); 380 } 381 removeNavigationBar(int displayId)382 void removeNavigationBar(int displayId) { 383 NavigationBar navBar = mNavigationBars.get(displayId); 384 if (navBar != null) { 385 navBar.destroyView(); 386 mNavigationBars.remove(displayId); 387 } 388 } 389 390 /** @see NavigationBar#checkNavBarModes() */ checkNavBarModes(int displayId)391 public void checkNavBarModes(int displayId) { 392 NavigationBar navBar = mNavigationBars.get(displayId); 393 if (navBar != null) { 394 navBar.checkNavBarModes(); 395 } 396 } 397 398 /** @see NavigationBar#finishBarAnimations() */ finishBarAnimations(int displayId)399 public void finishBarAnimations(int displayId) { 400 NavigationBar navBar = mNavigationBars.get(displayId); 401 if (navBar != null) { 402 navBar.finishBarAnimations(); 403 } 404 } 405 406 /** @see NavigationBar#touchAutoDim() */ touchAutoDim(int displayId)407 public void touchAutoDim(int displayId) { 408 NavigationBar navBar = mNavigationBars.get(displayId); 409 if (navBar != null) { 410 navBar.touchAutoDim(); 411 } 412 } 413 414 /** @see NavigationBar#transitionTo(int, boolean) */ transitionTo(int displayId, @TransitionMode int barMode, boolean animate)415 public void transitionTo(int displayId, @TransitionMode int barMode, boolean animate) { 416 NavigationBar navBar = mNavigationBars.get(displayId); 417 if (navBar != null) { 418 navBar.transitionTo(barMode, animate); 419 } 420 } 421 422 /** @see NavigationBar#disableAnimationsDuringHide(long) */ disableAnimationsDuringHide(int displayId, long delay)423 public void disableAnimationsDuringHide(int displayId, long delay) { 424 NavigationBar navBar = mNavigationBars.get(displayId); 425 if (navBar != null) { 426 navBar.disableAnimationsDuringHide(delay); 427 } 428 } 429 430 /** @return {@link NavigationBarView} on the default display. */ getDefaultNavigationBarView()431 public @Nullable NavigationBarView getDefaultNavigationBarView() { 432 return getNavigationBarView(mDisplayTracker.getDefaultDisplayId()); 433 } 434 435 /** 436 * @param displayId the ID of display which Navigation bar is on 437 * @return {@link NavigationBarView} on the display with {@code displayId}. 438 * {@code null} if no navigation bar on that display. 439 */ getNavigationBarView(int displayId)440 public @Nullable NavigationBarView getNavigationBarView(int displayId) { 441 NavigationBar navBar = getNavigationBar(displayId); 442 return (navBar == null) ? null : navBar.getView(); 443 } 444 getNavigationBar(int displayId)445 private @Nullable NavigationBar getNavigationBar(int displayId) { 446 return mNavigationBars.get(displayId); 447 } 448 showPinningEnterExitToast(int displayId, boolean entering)449 public void showPinningEnterExitToast(int displayId, boolean entering) { 450 final NavigationBarView navBarView = getNavigationBarView(displayId); 451 if (navBarView != null) { 452 navBarView.showPinningEnterExitToast(entering); 453 } else if (displayId == mDisplayTracker.getDefaultDisplayId() 454 && mTaskbarDelegate.isInitialized()) { 455 mTaskbarDelegate.showPinningEnterExitToast(entering); 456 } 457 } 458 showPinningEscapeToast(int displayId)459 public void showPinningEscapeToast(int displayId) { 460 final NavigationBarView navBarView = getNavigationBarView(displayId); 461 if (navBarView != null) { 462 navBarView.showPinningEscapeToast(); 463 } else if (displayId == mDisplayTracker.getDefaultDisplayId() 464 && mTaskbarDelegate.isInitialized()) { 465 mTaskbarDelegate.showPinningEscapeToast(); 466 } 467 } 468 isOverviewEnabled(int displayId)469 public boolean isOverviewEnabled(int displayId) { 470 final NavigationBarView navBarView = getNavigationBarView(displayId); 471 if (navBarView != null) { 472 return navBarView.isOverviewEnabled(); 473 } else { 474 return mTaskbarDelegate.isOverviewEnabled(); 475 } 476 } 477 478 /** @return {@link NavigationBar} on the default display. */ 479 @Nullable getDefaultNavigationBar()480 public NavigationBar getDefaultNavigationBar() { 481 return mNavigationBars.get(mDisplayTracker.getDefaultDisplayId()); 482 } 483 484 @Override dump(@onNull PrintWriter pw, @NonNull String[] args)485 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { 486 pw.println("mIsLargeScreen=" + mIsLargeScreen); 487 pw.println("mNavMode=" + mNavMode); 488 for (int i = 0; i < mNavigationBars.size(); i++) { 489 if (i > 0) { 490 pw.println(); 491 } 492 mNavigationBars.valueAt(i).dump(pw); 493 } 494 } 495 } 496