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 | ActivityInfo.CONFIG_SCREEN_LAYOUT 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 229 /** @see #initializeTaskbarIfNecessary() */ updateNavbarForTaskbar()230 private boolean updateNavbarForTaskbar() { 231 boolean taskbarShown = initializeTaskbarIfNecessary(); 232 if (!taskbarShown && mNavigationBars.get(mContext.getDisplayId()) == null) { 233 createNavigationBar(mContext.getDisplay(), null, null); 234 } 235 return taskbarShown; 236 } 237 238 /** @return {@code true} if taskbar is enabled, false otherwise */ initializeTaskbarIfNecessary()239 private boolean initializeTaskbarIfNecessary() { 240 // Enable for large screens or (phone AND flag is set); assuming phone = !mIsLargeScreen 241 boolean taskbarEnabled = mIsLargeScreen || mFeatureFlags.isEnabled( 242 Flags.HIDE_NAVBAR_WINDOW); 243 244 if (taskbarEnabled) { 245 Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary"); 246 final int displayId = mContext.getDisplayId(); 247 // Hint to NavBarHelper if we are replacing an existing bar to skip extra work 248 mNavBarHelper.setTogglingNavbarTaskbar(mNavigationBars.contains(displayId)); 249 // Remove navigation bar when taskbar is showing 250 removeNavigationBar(displayId); 251 mTaskbarDelegate.init(displayId); 252 mNavBarHelper.setTogglingNavbarTaskbar(false); 253 Trace.endSection(); 254 255 } else { 256 mTaskbarDelegate.destroy(); 257 } 258 return taskbarEnabled; 259 } 260 261 @Override onDisplayRemoved(int displayId)262 public void onDisplayRemoved(int displayId) { 263 removeNavigationBar(displayId); 264 } 265 266 @Override onDisplayReady(int displayId)267 public void onDisplayReady(int displayId) { 268 Display display = mDisplayManager.getDisplay(displayId); 269 mIsLargeScreen = isLargeScreen(mContext); 270 createNavigationBar(display, null /* savedState */, null /* result */); 271 } 272 273 @Override setNavigationBarLumaSamplingEnabled(int displayId, boolean enable)274 public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) { 275 final NavigationBar navigationBar = getNavigationBar(displayId); 276 if (navigationBar != null) { 277 navigationBar.setNavigationBarLumaSamplingEnabled(enable); 278 } 279 } 280 281 /** 282 * Recreates the navigation bar for the given display. 283 */ recreateNavigationBar(int displayId)284 private void recreateNavigationBar(int displayId) { 285 // TODO: Improve this flow so that we don't need to create a new nav bar but just 286 // the view 287 Bundle savedState = new Bundle(); 288 NavigationBar bar = mNavigationBars.get(displayId); 289 if (bar != null) { 290 bar.onSaveInstanceState(savedState); 291 } 292 removeNavigationBar(displayId); 293 createNavigationBar(mDisplayManager.getDisplay(displayId), savedState, null /* result */); 294 } 295 296 // TODO(b/117478341): I use {@code includeDefaultDisplay} to make this method compatible to 297 // CarStatusBar because they have their own nav bar. Think about a better way for it. 298 /** 299 * Creates navigation bars when car/status bar initializes. 300 * 301 * @param includeDefaultDisplay {@code true} to create navigation bar on default display. 302 */ createNavigationBars(final boolean includeDefaultDisplay, RegisterStatusBarResult result)303 public void createNavigationBars(final boolean includeDefaultDisplay, 304 RegisterStatusBarResult result) { 305 updateAccessibilityButtonModeIfNeeded(); 306 307 // Don't need to create nav bar on the default display if we initialize TaskBar. 308 final boolean shouldCreateDefaultNavbar = includeDefaultDisplay 309 && !initializeTaskbarIfNecessary(); 310 Display[] displays = mDisplayTracker.getAllDisplays(); 311 for (Display display : displays) { 312 if (shouldCreateDefaultNavbar 313 || display.getDisplayId() != mDisplayTracker.getDefaultDisplayId()) { 314 createNavigationBar(display, null /* savedState */, result); 315 } 316 } 317 } 318 319 /** 320 * Adds a navigation bar on default display or an external display if the display supports 321 * system decorations. 322 * 323 * @param display the display to add navigation bar on. 324 */ 325 @VisibleForTesting createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result)326 void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) { 327 if (display == null) { 328 return; 329 } 330 331 final int displayId = display.getDisplayId(); 332 final boolean isOnDefaultDisplay = displayId == mDisplayTracker.getDefaultDisplayId(); 333 334 // We may show TaskBar on the default display for large screen device. Don't need to create 335 // navigation bar for this case. 336 if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) { 337 return; 338 } 339 340 final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); 341 342 try { 343 if (!wms.hasNavigationBar(displayId)) { 344 return; 345 } 346 } catch (RemoteException e) { 347 // Cannot get wms, just return with warning message. 348 Log.w(TAG, "Cannot get WindowManager."); 349 return; 350 } 351 final Context context = isOnDefaultDisplay 352 ? mContext 353 : mContext.createDisplayContext(display); 354 NavigationBarComponent component = mNavigationBarComponentFactory.create( 355 context, savedState); 356 NavigationBar navBar = component.getNavigationBar(); 357 navBar.init(); 358 mNavigationBars.put(displayId, navBar); 359 360 navBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 361 @Override 362 public void onViewAttachedToWindow(View v) { 363 if (result != null) { 364 navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken, 365 result.mImeWindowVis, result.mImeBackDisposition, 366 result.mShowImeSwitcher); 367 } 368 } 369 370 @Override 371 public void onViewDetachedFromWindow(View v) { 372 v.removeOnAttachStateChangeListener(this); 373 } 374 }); 375 } 376 removeNavigationBar(int displayId)377 void removeNavigationBar(int displayId) { 378 NavigationBar navBar = mNavigationBars.get(displayId); 379 if (navBar != null) { 380 navBar.destroyView(); 381 mNavigationBars.remove(displayId); 382 } 383 } 384 385 /** @see NavigationBar#checkNavBarModes() */ checkNavBarModes(int displayId)386 public void checkNavBarModes(int displayId) { 387 NavigationBar navBar = mNavigationBars.get(displayId); 388 if (navBar != null) { 389 navBar.checkNavBarModes(); 390 } 391 } 392 393 /** @see NavigationBar#finishBarAnimations() */ finishBarAnimations(int displayId)394 public void finishBarAnimations(int displayId) { 395 NavigationBar navBar = mNavigationBars.get(displayId); 396 if (navBar != null) { 397 navBar.finishBarAnimations(); 398 } 399 } 400 401 /** @see NavigationBar#touchAutoDim() */ touchAutoDim(int displayId)402 public void touchAutoDim(int displayId) { 403 NavigationBar navBar = mNavigationBars.get(displayId); 404 if (navBar != null) { 405 navBar.touchAutoDim(); 406 } 407 } 408 409 /** @see NavigationBar#transitionTo(int, boolean) */ transitionTo(int displayId, @TransitionMode int barMode, boolean animate)410 public void transitionTo(int displayId, @TransitionMode int barMode, boolean animate) { 411 NavigationBar navBar = mNavigationBars.get(displayId); 412 if (navBar != null) { 413 navBar.transitionTo(barMode, animate); 414 } 415 } 416 417 /** @see NavigationBar#disableAnimationsDuringHide(long) */ disableAnimationsDuringHide(int displayId, long delay)418 public void disableAnimationsDuringHide(int displayId, long delay) { 419 NavigationBar navBar = mNavigationBars.get(displayId); 420 if (navBar != null) { 421 navBar.disableAnimationsDuringHide(delay); 422 } 423 } 424 425 /** @return {@link NavigationBarView} on the default display. */ getDefaultNavigationBarView()426 public @Nullable NavigationBarView getDefaultNavigationBarView() { 427 return getNavigationBarView(mDisplayTracker.getDefaultDisplayId()); 428 } 429 430 /** 431 * @param displayId the ID of display which Navigation bar is on 432 * @return {@link NavigationBarView} on the display with {@code displayId}. 433 * {@code null} if no navigation bar on that display. 434 */ getNavigationBarView(int displayId)435 public @Nullable NavigationBarView getNavigationBarView(int displayId) { 436 NavigationBar navBar = getNavigationBar(displayId); 437 return (navBar == null) ? null : navBar.getView(); 438 } 439 getNavigationBar(int displayId)440 private @Nullable NavigationBar getNavigationBar(int displayId) { 441 return mNavigationBars.get(displayId); 442 } 443 showPinningEnterExitToast(int displayId, boolean entering)444 public void showPinningEnterExitToast(int displayId, boolean entering) { 445 final NavigationBarView navBarView = getNavigationBarView(displayId); 446 if (navBarView != null) { 447 navBarView.showPinningEnterExitToast(entering); 448 } else if (displayId == mDisplayTracker.getDefaultDisplayId() 449 && mTaskbarDelegate.isInitialized()) { 450 mTaskbarDelegate.showPinningEnterExitToast(entering); 451 } 452 } 453 showPinningEscapeToast(int displayId)454 public void showPinningEscapeToast(int displayId) { 455 final NavigationBarView navBarView = getNavigationBarView(displayId); 456 if (navBarView != null) { 457 navBarView.showPinningEscapeToast(); 458 } else if (displayId == mDisplayTracker.getDefaultDisplayId() 459 && mTaskbarDelegate.isInitialized()) { 460 mTaskbarDelegate.showPinningEscapeToast(); 461 } 462 } 463 isOverviewEnabled(int displayId)464 public boolean isOverviewEnabled(int displayId) { 465 final NavigationBarView navBarView = getNavigationBarView(displayId); 466 if (navBarView != null) { 467 return navBarView.isOverviewEnabled(); 468 } else { 469 return mTaskbarDelegate.isOverviewEnabled(); 470 } 471 } 472 473 /** @return {@link NavigationBar} on the default display. */ 474 @Nullable getDefaultNavigationBar()475 public NavigationBar getDefaultNavigationBar() { 476 return mNavigationBars.get(mDisplayTracker.getDefaultDisplayId()); 477 } 478 479 @Override dump(@onNull PrintWriter pw, @NonNull String[] args)480 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { 481 pw.println("mIsLargeScreen=" + mIsLargeScreen); 482 pw.println("mNavMode=" + mNavMode); 483 for (int i = 0; i < mNavigationBars.size(); i++) { 484 if (i > 0) { 485 pw.println(); 486 } 487 mNavigationBars.valueAt(i).dump(pw); 488 } 489 } 490 } 491