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