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.view.Display.DEFAULT_DISPLAY; 20 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; 21 22 import android.content.Context; 23 import android.content.pm.ActivityInfo; 24 import android.content.res.Configuration; 25 import android.content.res.Resources; 26 import android.hardware.display.DisplayManager; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.RemoteException; 30 import android.os.SystemProperties; 31 import android.util.DisplayMetrics; 32 import android.util.Log; 33 import android.util.SparseArray; 34 import android.view.Display; 35 import android.view.IWindowManager; 36 import android.view.View; 37 import android.view.WindowManager; 38 import android.view.WindowManagerGlobal; 39 import android.view.accessibility.AccessibilityManager; 40 41 import androidx.annotation.NonNull; 42 import androidx.annotation.Nullable; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.logging.MetricsLogger; 46 import com.android.internal.logging.UiEventLogger; 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.accessibility.AccessibilityButtonModeObserver; 51 import com.android.systemui.accessibility.SystemActions; 52 import com.android.systemui.assist.AssistManager; 53 import com.android.systemui.broadcast.BroadcastDispatcher; 54 import com.android.systemui.dagger.SysUISingleton; 55 import com.android.systemui.dagger.qualifiers.Main; 56 import com.android.systemui.model.SysUiState; 57 import com.android.systemui.plugins.statusbar.StatusBarStateController; 58 import com.android.systemui.recents.OverviewProxyService; 59 import com.android.systemui.recents.Recents; 60 import com.android.systemui.settings.UserTracker; 61 import com.android.systemui.statusbar.CommandQueue; 62 import com.android.systemui.statusbar.CommandQueue.Callbacks; 63 import com.android.systemui.statusbar.NotificationRemoteInputManager; 64 import com.android.systemui.statusbar.NotificationShadeDepthController; 65 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; 66 import com.android.systemui.statusbar.phone.ShadeController; 67 import com.android.systemui.statusbar.phone.StatusBar; 68 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; 69 import com.android.systemui.statusbar.policy.ConfigurationController; 70 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 71 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; 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 78 import javax.inject.Inject; 79 80 import dagger.Lazy; 81 82 83 /** A controller to handle navigation bars. */ 84 @SysUISingleton 85 public class NavigationBarController implements Callbacks, 86 ConfigurationController.ConfigurationListener, 87 NavigationModeController.ModeChangedListener, Dumpable { 88 89 private static final float TABLET_MIN_DPS = 600; 90 91 private static final String TAG = NavigationBarController.class.getSimpleName(); 92 93 private final Context mContext; 94 private final WindowManager mWindowManager; 95 private final Lazy<AssistManager> mAssistManagerLazy; 96 private final AccessibilityManager mAccessibilityManager; 97 private final AccessibilityManagerWrapper mAccessibilityManagerWrapper; 98 private final DeviceProvisionedController mDeviceProvisionedController; 99 private final MetricsLogger mMetricsLogger; 100 private final OverviewProxyService mOverviewProxyService; 101 private final NavigationModeController mNavigationModeController; 102 private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; 103 private final StatusBarStateController mStatusBarStateController; 104 private final SysUiState mSysUiFlagsContainer; 105 private final BroadcastDispatcher mBroadcastDispatcher; 106 private final CommandQueue mCommandQueue; 107 private final Optional<Pip> mPipOptional; 108 private final Optional<LegacySplitScreen> mSplitScreenOptional; 109 private final Optional<Recents> mRecentsOptional; 110 private final Lazy<StatusBar> mStatusBarLazy; 111 private final ShadeController mShadeController; 112 private final NotificationRemoteInputManager mNotificationRemoteInputManager; 113 private final SystemActions mSystemActions; 114 private final UiEventLogger mUiEventLogger; 115 private final Handler mHandler; 116 private final DisplayManager mDisplayManager; 117 private final NavigationBarOverlayController mNavBarOverlayController; 118 private final TaskbarDelegate mTaskbarDelegate; 119 private final NotificationShadeDepthController mNotificationShadeDepthController; 120 private int mNavMode; 121 private boolean mIsTablet; 122 private final UserTracker mUserTracker; 123 124 /** A displayId - nav bar maps. */ 125 @VisibleForTesting 126 SparseArray<NavigationBar> mNavigationBars = new SparseArray<>(); 127 128 // Tracks config changes that will actually recreate the nav bar 129 private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( 130 ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_SCREEN_LAYOUT 131 | ActivityInfo.CONFIG_UI_MODE); 132 133 @Inject NavigationBarController(Context context, WindowManager windowManager, Lazy<AssistManager> assistManagerLazy, AccessibilityManager accessibilityManager, AccessibilityManagerWrapper accessibilityManagerWrapper, DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, AccessibilityButtonModeObserver accessibilityButtonModeObserver, StatusBarStateController statusBarStateController, SysUiState sysUiFlagsContainer, BroadcastDispatcher broadcastDispatcher, CommandQueue commandQueue, Optional<Pip> pipOptional, Optional<LegacySplitScreen> splitScreenOptional, Optional<Recents> recentsOptional, Lazy<StatusBar> statusBarLazy, ShadeController shadeController, NotificationRemoteInputManager notificationRemoteInputManager, NotificationShadeDepthController notificationShadeDepthController, SystemActions systemActions, @Main Handler mainHandler, UiEventLogger uiEventLogger, NavigationBarOverlayController navBarOverlayController, ConfigurationController configurationController, UserTracker userTracker)134 public NavigationBarController(Context context, 135 WindowManager windowManager, 136 Lazy<AssistManager> assistManagerLazy, 137 AccessibilityManager accessibilityManager, 138 AccessibilityManagerWrapper accessibilityManagerWrapper, 139 DeviceProvisionedController deviceProvisionedController, 140 MetricsLogger metricsLogger, 141 OverviewProxyService overviewProxyService, 142 NavigationModeController navigationModeController, 143 AccessibilityButtonModeObserver accessibilityButtonModeObserver, 144 StatusBarStateController statusBarStateController, 145 SysUiState sysUiFlagsContainer, 146 BroadcastDispatcher broadcastDispatcher, 147 CommandQueue commandQueue, 148 Optional<Pip> pipOptional, 149 Optional<LegacySplitScreen> splitScreenOptional, 150 Optional<Recents> recentsOptional, 151 Lazy<StatusBar> statusBarLazy, 152 ShadeController shadeController, 153 NotificationRemoteInputManager notificationRemoteInputManager, 154 NotificationShadeDepthController notificationShadeDepthController, 155 SystemActions systemActions, 156 @Main Handler mainHandler, 157 UiEventLogger uiEventLogger, 158 NavigationBarOverlayController navBarOverlayController, 159 ConfigurationController configurationController, 160 UserTracker userTracker) { 161 mContext = context; 162 mWindowManager = windowManager; 163 mAssistManagerLazy = assistManagerLazy; 164 mAccessibilityManager = accessibilityManager; 165 mAccessibilityManagerWrapper = accessibilityManagerWrapper; 166 mDeviceProvisionedController = deviceProvisionedController; 167 mMetricsLogger = metricsLogger; 168 mOverviewProxyService = overviewProxyService; 169 mNavigationModeController = navigationModeController; 170 mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; 171 mStatusBarStateController = statusBarStateController; 172 mSysUiFlagsContainer = sysUiFlagsContainer; 173 mBroadcastDispatcher = broadcastDispatcher; 174 mCommandQueue = commandQueue; 175 mPipOptional = pipOptional; 176 mSplitScreenOptional = splitScreenOptional; 177 mRecentsOptional = recentsOptional; 178 mStatusBarLazy = statusBarLazy; 179 mShadeController = shadeController; 180 mNotificationRemoteInputManager = notificationRemoteInputManager; 181 mNotificationShadeDepthController = notificationShadeDepthController; 182 mSystemActions = systemActions; 183 mUiEventLogger = uiEventLogger; 184 mHandler = mainHandler; 185 mDisplayManager = mContext.getSystemService(DisplayManager.class); 186 commandQueue.addCallback(this); 187 configurationController.addCallback(this); 188 mConfigChanges.applyNewConfig(mContext.getResources()); 189 mNavBarOverlayController = navBarOverlayController; 190 mNavMode = mNavigationModeController.addListener(this); 191 mNavigationModeController.addListener(this); 192 mTaskbarDelegate = new TaskbarDelegate(mOverviewProxyService); 193 mIsTablet = isTablet(mContext.getResources().getConfiguration()); 194 mUserTracker = userTracker; 195 } 196 197 @Override onConfigChanged(Configuration newConfig)198 public void onConfigChanged(Configuration newConfig) { 199 boolean isOldConfigTablet = mIsTablet; 200 mIsTablet = isTablet(newConfig); 201 boolean largeScreenChanged = mIsTablet != isOldConfigTablet; 202 // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded 203 if (largeScreenChanged && updateNavbarForTaskbar()) { 204 return; 205 } 206 207 if (mConfigChanges.applyNewConfig(mContext.getResources())) { 208 for (int i = 0; i < mNavigationBars.size(); i++) { 209 recreateNavigationBar(mNavigationBars.keyAt(i)); 210 } 211 } else { 212 for (int i = 0; i < mNavigationBars.size(); i++) { 213 mNavigationBars.valueAt(i).onConfigurationChanged(newConfig); 214 } 215 } 216 } 217 218 @Override onNavigationModeChanged(int mode)219 public void onNavigationModeChanged(int mode) { 220 if (mNavMode == mode) { 221 return; 222 } 223 final int oldMode = mNavMode; 224 mNavMode = mode; 225 mHandler.post(() -> { 226 // create/destroy nav bar based on nav mode only in unfolded state 227 if (oldMode != mNavMode) { 228 updateNavbarForTaskbar(); 229 } 230 for (int i = 0; i < mNavigationBars.size(); i++) { 231 NavigationBar navBar = mNavigationBars.valueAt(i); 232 if (navBar == null) { 233 continue; 234 } 235 navBar.getView().updateStates(); 236 } 237 }); 238 } 239 240 /** 241 * @return {@code true} if navbar was added/removed, false otherwise 242 */ updateNavbarForTaskbar()243 public boolean updateNavbarForTaskbar() { 244 if (!isThreeButtonTaskbarFlagEnabled()) { 245 return false; 246 } 247 248 if (mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON) { 249 // Remove navigation bar when taskbar is showing, currently only for 3 button mode 250 removeNavigationBar(mContext.getDisplayId()); 251 mCommandQueue.addCallback(mTaskbarDelegate); 252 } else if (mNavigationBars.get(mContext.getDisplayId()) == null) { 253 // Add navigation bar after taskbar goes away 254 createNavigationBar(mContext.getDisplay(), null, null); 255 mCommandQueue.removeCallback(mTaskbarDelegate); 256 } 257 258 return true; 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 mIsTablet = isTablet(mContext.getResources().getConfiguration()); 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 NavigationBarView navigationBarView = getNavigationBarView(displayId); 276 if (navigationBarView != null) { 277 navigationBarView.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 if (updateNavbarForTaskbar()) { 306 return; 307 } 308 309 Display[] displays = mDisplayManager.getDisplays(); 310 for (Display display : displays) { 311 if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) { 312 createNavigationBar(display, null /* savedState */, result); 313 } 314 } 315 } 316 317 /** 318 * Adds a navigation bar on default display or an external display if the display supports 319 * system decorations. 320 * 321 * @param display the display to add navigation bar on. 322 */ 323 @VisibleForTesting createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result)324 void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) { 325 if (display == null) { 326 return; 327 } 328 329 if (isThreeButtonTaskbarEnabled()) { 330 return; 331 } 332 333 final int displayId = display.getDisplayId(); 334 final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY; 335 final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); 336 337 try { 338 if (!wms.hasNavigationBar(displayId)) { 339 return; 340 } 341 } catch (RemoteException e) { 342 // Cannot get wms, just return with warning message. 343 Log.w(TAG, "Cannot get WindowManager."); 344 return; 345 } 346 final Context context = isOnDefaultDisplay 347 ? mContext 348 : mContext.createDisplayContext(display); 349 NavigationBar navBar = new NavigationBar(context, 350 mWindowManager, 351 mAssistManagerLazy, 352 mAccessibilityManager, 353 mAccessibilityManagerWrapper, 354 mDeviceProvisionedController, 355 mMetricsLogger, 356 mOverviewProxyService, 357 mNavigationModeController, 358 mAccessibilityButtonModeObserver, 359 mStatusBarStateController, 360 mSysUiFlagsContainer, 361 mBroadcastDispatcher, 362 mCommandQueue, 363 mPipOptional, 364 mSplitScreenOptional, 365 mRecentsOptional, 366 mStatusBarLazy, 367 mShadeController, 368 mNotificationRemoteInputManager, 369 mNotificationShadeDepthController, 370 mSystemActions, 371 mHandler, 372 mNavBarOverlayController, 373 mUiEventLogger, 374 mUserTracker); 375 mNavigationBars.put(displayId, navBar); 376 377 View navigationBarView = navBar.createView(savedState); 378 navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 379 @Override 380 public void onViewAttachedToWindow(View v) { 381 if (result != null) { 382 navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken, 383 result.mImeWindowVis, result.mImeBackDisposition, 384 result.mShowImeSwitcher); 385 } 386 } 387 388 @Override 389 public void onViewDetachedFromWindow(View v) { 390 v.removeOnAttachStateChangeListener(this); 391 } 392 }); 393 } 394 removeNavigationBar(int displayId)395 void removeNavigationBar(int displayId) { 396 NavigationBar navBar = mNavigationBars.get(displayId); 397 if (navBar != null) { 398 navBar.setAutoHideController(/* autoHideController */ null); 399 navBar.destroyView(); 400 mNavigationBars.remove(displayId); 401 } 402 } 403 404 /** @see NavigationBar#checkNavBarModes() */ checkNavBarModes(int displayId)405 public void checkNavBarModes(int displayId) { 406 NavigationBar navBar = mNavigationBars.get(displayId); 407 if (navBar != null) { 408 navBar.checkNavBarModes(); 409 } 410 } 411 412 /** @see NavigationBar#finishBarAnimations() */ finishBarAnimations(int displayId)413 public void finishBarAnimations(int displayId) { 414 NavigationBar navBar = mNavigationBars.get(displayId); 415 if (navBar != null) { 416 navBar.finishBarAnimations(); 417 } 418 } 419 420 /** @see NavigationBar#touchAutoDim() */ touchAutoDim(int displayId)421 public void touchAutoDim(int displayId) { 422 NavigationBar navBar = mNavigationBars.get(displayId); 423 if (navBar != null) { 424 navBar.touchAutoDim(); 425 } 426 } 427 428 /** @see NavigationBar#transitionTo(int, boolean) */ transitionTo(int displayId, @TransitionMode int barMode, boolean animate)429 public void transitionTo(int displayId, @TransitionMode int barMode, boolean animate) { 430 NavigationBar navBar = mNavigationBars.get(displayId); 431 if (navBar != null) { 432 navBar.transitionTo(barMode, animate); 433 } 434 } 435 436 /** @see NavigationBar#disableAnimationsDuringHide(long) */ disableAnimationsDuringHide(int displayId, long delay)437 public void disableAnimationsDuringHide(int displayId, long delay) { 438 NavigationBar navBar = mNavigationBars.get(displayId); 439 if (navBar != null) { 440 navBar.disableAnimationsDuringHide(delay); 441 } 442 } 443 444 /** @return {@link NavigationBarView} on the default display. */ getDefaultNavigationBarView()445 public @Nullable NavigationBarView getDefaultNavigationBarView() { 446 return getNavigationBarView(DEFAULT_DISPLAY); 447 } 448 449 /** 450 * @param displayId the ID of display which Navigation bar is on 451 * @return {@link NavigationBarView} on the display with {@code displayId}. 452 * {@code null} if no navigation bar on that display. 453 */ getNavigationBarView(int displayId)454 public @Nullable NavigationBarView getNavigationBarView(int displayId) { 455 NavigationBar navBar = mNavigationBars.get(displayId); 456 return (navBar == null) ? null : navBar.getView(); 457 } 458 459 /** @return {@link NavigationBar} on the default display. */ 460 @Nullable getDefaultNavigationBar()461 public NavigationBar getDefaultNavigationBar() { 462 return mNavigationBars.get(DEFAULT_DISPLAY); 463 } 464 isThreeButtonTaskbarEnabled()465 private boolean isThreeButtonTaskbarEnabled() { 466 return mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON && 467 isThreeButtonTaskbarFlagEnabled(); 468 } 469 isThreeButtonTaskbarFlagEnabled()470 private boolean isThreeButtonTaskbarFlagEnabled() { 471 return SystemProperties.getBoolean("persist.debug.taskbar_three_button", false); 472 } 473 isTablet(Configuration newConfig)474 private boolean isTablet(Configuration newConfig) { 475 float density = Resources.getSystem().getDisplayMetrics().density; 476 int size = Math.min((int) (density * newConfig.screenWidthDp), 477 (int) (density* newConfig.screenHeightDp)); 478 DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); 479 float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT; 480 return (size / densityRatio) >= TABLET_MIN_DPS; 481 } 482 483 @Override dump(@onNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args)484 public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { 485 for (int i = 0; i < mNavigationBars.size(); i++) { 486 if (i > 0) { 487 pw.println(); 488 } 489 mNavigationBars.valueAt(i).dump(pw); 490 } 491 } 492 } 493