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.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; 20 21 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; 22 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; 23 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; 24 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; 25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; 26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED; 27 import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode; 28 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; 29 import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay; 30 31 import android.animation.LayoutTransition; 32 import android.animation.LayoutTransition.TransitionListener; 33 import android.animation.ObjectAnimator; 34 import android.animation.PropertyValuesHolder; 35 import android.animation.TimeInterpolator; 36 import android.animation.ValueAnimator; 37 import android.annotation.DrawableRes; 38 import android.annotation.Nullable; 39 import android.app.StatusBarManager; 40 import android.content.Context; 41 import android.content.res.Configuration; 42 import android.graphics.Canvas; 43 import android.graphics.Point; 44 import android.graphics.Rect; 45 import android.graphics.Region; 46 import android.graphics.Region.Op; 47 import android.os.Bundle; 48 import android.util.AttributeSet; 49 import android.util.Log; 50 import android.util.SparseArray; 51 import android.view.ContextThemeWrapper; 52 import android.view.Display; 53 import android.view.MotionEvent; 54 import android.view.Surface; 55 import android.view.View; 56 import android.view.ViewGroup; 57 import android.view.ViewTreeObserver.InternalInsetsInfo; 58 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; 59 import android.view.WindowInsets; 60 import android.view.WindowInsetsController.Behavior; 61 import android.view.WindowManager; 62 import android.view.accessibility.AccessibilityNodeInfo; 63 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 64 import android.widget.FrameLayout; 65 66 import com.android.internal.annotations.VisibleForTesting; 67 import com.android.settingslib.Utils; 68 import com.android.systemui.Dependency; 69 import com.android.systemui.R; 70 import com.android.systemui.animation.Interpolators; 71 import com.android.systemui.model.SysUiState; 72 import com.android.systemui.navigationbar.buttons.ButtonDispatcher; 73 import com.android.systemui.navigationbar.buttons.ContextualButton; 74 import com.android.systemui.navigationbar.buttons.ContextualButtonGroup; 75 import com.android.systemui.navigationbar.buttons.DeadZone; 76 import com.android.systemui.navigationbar.buttons.KeyButtonDrawable; 77 import com.android.systemui.navigationbar.buttons.NearestTouchFrame; 78 import com.android.systemui.navigationbar.buttons.RotationContextButton; 79 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; 80 import com.android.systemui.navigationbar.gestural.FloatingRotationButton; 81 import com.android.systemui.navigationbar.gestural.RegionSamplingHelper; 82 import com.android.systemui.recents.OverviewProxyService; 83 import com.android.systemui.recents.Recents; 84 import com.android.systemui.shared.system.ActivityManagerWrapper; 85 import com.android.systemui.shared.system.QuickStepContract; 86 import com.android.systemui.shared.system.SysUiStatsLog; 87 import com.android.systemui.shared.system.WindowManagerWrapper; 88 import com.android.systemui.statusbar.CommandQueue; 89 import com.android.systemui.statusbar.phone.AutoHideController; 90 import com.android.systemui.statusbar.phone.LightBarTransitionsController; 91 import com.android.systemui.statusbar.phone.NotificationPanelViewController; 92 import com.android.systemui.statusbar.phone.StatusBar; 93 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; 94 import com.android.wm.shell.pip.Pip; 95 96 import java.io.PrintWriter; 97 import java.util.HashMap; 98 import java.util.Map; 99 import java.util.function.Consumer; 100 101 public class NavigationBarView extends FrameLayout implements 102 NavigationModeController.ModeChangedListener { 103 final static boolean DEBUG = false; 104 final static String TAG = "StatusBar/NavBarView"; 105 106 // slippery nav bar when everything is disabled, e.g. during setup 107 final static boolean SLIPPERY_WHEN_DISABLED = true; 108 109 final static boolean ALTERNATE_CAR_MODE_UI = false; 110 private final RegionSamplingHelper mRegionSamplingHelper; 111 private final int mNavColorSampleMargin; 112 private final SysUiState mSysUiFlagContainer; 113 114 View mCurrentView = null; 115 private View mVertical; 116 private View mHorizontal; 117 118 /** Indicates that navigation bar is vertical. */ 119 private boolean mIsVertical; 120 private int mCurrentRotation = -1; 121 122 boolean mLongClickableAccessibilityButton; 123 int mDisabledFlags = 0; 124 int mNavigationIconHints = 0; 125 private int mNavBarMode; 126 127 private final Region mTmpRegion = new Region(); 128 private final int[] mTmpPosition = new int[2]; 129 private Rect mTmpBounds = new Rect(); 130 private Map<View, Rect> mButtonFullTouchableRegions = new HashMap<>(); 131 132 private KeyButtonDrawable mBackIcon; 133 private KeyButtonDrawable mHomeDefaultIcon; 134 private KeyButtonDrawable mRecentIcon; 135 private KeyButtonDrawable mDockedIcon; 136 private Context mLightContext; 137 private int mLightIconColor; 138 private int mDarkIconColor; 139 140 private EdgeBackGestureHandler mEdgeBackGestureHandler; 141 private final DeadZone mDeadZone; 142 private boolean mDeadZoneConsuming = false; 143 private final NavigationBarTransitions mBarTransitions; 144 private final OverviewProxyService mOverviewProxyService; 145 private AutoHideController mAutoHideController; 146 147 // performs manual animation in sync with layout transitions 148 private final NavTransitionListener mTransitionListener = new NavTransitionListener(); 149 150 private OnVerticalChangedListener mOnVerticalChangedListener; 151 private boolean mLayoutTransitionsEnabled = true; 152 private boolean mWakeAndUnlocking; 153 private boolean mUseCarModeUi = false; 154 private boolean mInCarMode = false; 155 private boolean mDockedStackExists; 156 private boolean mImeVisible; 157 private boolean mScreenOn = true; 158 159 private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>(); 160 private final ContextualButtonGroup mContextualButtonGroup; 161 private Configuration mConfiguration; 162 private Configuration mTmpLastConfiguration; 163 164 private NavigationBarInflaterView mNavigationInflaterView; 165 private NotificationPanelViewController mPanelView; 166 private RotationContextButton mRotationContextButton; 167 private FloatingRotationButton mFloatingRotationButton; 168 private RotationButtonController mRotationButtonController; 169 private NavigationBarOverlayController mNavBarOverlayController; 170 171 /** 172 * Helper that is responsible for showing the right toast when a disallowed activity operation 173 * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in 174 * fully locked mode we only show that unlocking is blocked. 175 */ 176 private ScreenPinningNotify mScreenPinningNotify; 177 private Rect mSamplingBounds = new Rect(); 178 /** 179 * When quickswitching between apps of different orientations, we draw a secondary home handle 180 * in the position of the first app's orientation. This rect represents the region of that 181 * home handle so we can apply the correct light/dark luma on that. 182 * @see {@link NavigationBar#mOrientationHandle} 183 */ 184 @Nullable 185 private Rect mOrientedHandleSamplingRegion; 186 187 private class NavTransitionListener implements TransitionListener { 188 private boolean mBackTransitioning; 189 private boolean mHomeAppearing; 190 private long mStartDelay; 191 private long mDuration; 192 private TimeInterpolator mInterpolator; 193 194 @Override startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)195 public void startTransition(LayoutTransition transition, ViewGroup container, 196 View view, int transitionType) { 197 if (view.getId() == R.id.back) { 198 mBackTransitioning = true; 199 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { 200 mHomeAppearing = true; 201 mStartDelay = transition.getStartDelay(transitionType); 202 mDuration = transition.getDuration(transitionType); 203 mInterpolator = transition.getInterpolator(transitionType); 204 } 205 } 206 207 @Override endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)208 public void endTransition(LayoutTransition transition, ViewGroup container, 209 View view, int transitionType) { 210 if (view.getId() == R.id.back) { 211 mBackTransitioning = false; 212 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { 213 mHomeAppearing = false; 214 } 215 } 216 onBackAltCleared()217 public void onBackAltCleared() { 218 ButtonDispatcher backButton = getBackButton(); 219 220 // When dismissing ime during unlock, force the back button to run the same appearance 221 // animation as home (if we catch this condition early enough). 222 if (!mBackTransitioning && backButton.getVisibility() == VISIBLE 223 && mHomeAppearing && getHomeButton().getAlpha() == 0) { 224 getBackButton().setAlpha(0); 225 ValueAnimator a = ObjectAnimator.ofFloat(backButton, "alpha", 0, 1); 226 a.setStartDelay(mStartDelay); 227 a.setDuration(mDuration); 228 a.setInterpolator(mInterpolator); 229 a.start(); 230 } 231 } 232 } 233 234 private final AccessibilityDelegate mQuickStepAccessibilityDelegate = 235 new AccessibilityDelegate() { 236 private AccessibilityAction mToggleOverviewAction; 237 238 @Override 239 public void onInitializeAccessibilityNodeInfo(View host, 240 AccessibilityNodeInfo info) { 241 super.onInitializeAccessibilityNodeInfo(host, info); 242 if (mToggleOverviewAction == null) { 243 mToggleOverviewAction = new AccessibilityAction( 244 R.id.action_toggle_overview, getContext().getString( 245 R.string.quick_step_accessibility_toggle_overview)); 246 } 247 info.addAction(mToggleOverviewAction); 248 } 249 250 @Override 251 public boolean performAccessibilityAction(View host, int action, Bundle args) { 252 if (action == R.id.action_toggle_overview) { 253 Dependency.get(Recents.class).toggleRecentApps(); 254 } else { 255 return super.performAccessibilityAction(host, action, args); 256 } 257 return true; 258 } 259 }; 260 261 private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> { 262 // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully 263 // gestural mode, the entire nav bar should be touchable. 264 if (!mEdgeBackGestureHandler.isHandlingGestures()) { 265 info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); 266 return; 267 } 268 269 // When in gestural and the IME is showing, don't use the nearest region since it will take 270 // gesture space away from the IME 271 info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 272 info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */, 273 false /* inScreen */, false /* useNearestRegion */)); 274 }; 275 276 private final Consumer<Boolean> mRotationButtonListener = (visible) -> { 277 if (visible) { 278 // If the button will actually become visible and the navbar is about to hide, 279 // tell the statusbar to keep it around for longer 280 mAutoHideController.touchAutoHide(); 281 } 282 notifyActiveTouchRegions(); 283 }; 284 285 private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> { 286 if (visible) { 287 mAutoHideController.touchAutoHide(); 288 } 289 notifyActiveTouchRegions(); 290 }; 291 NavigationBarView(Context context, AttributeSet attrs)292 public NavigationBarView(Context context, AttributeSet attrs) { 293 super(context, attrs); 294 295 final Context darkContext = new ContextThemeWrapper(context, 296 Utils.getThemeAttr(context, R.attr.darkIconTheme)); 297 mLightContext = new ContextThemeWrapper(context, 298 Utils.getThemeAttr(context, R.attr.lightIconTheme)); 299 mLightIconColor = Utils.getColorAttrDefaultColor(mLightContext, R.attr.singleToneColor); 300 mDarkIconColor = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor); 301 mIsVertical = false; 302 mLongClickableAccessibilityButton = false; 303 mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this); 304 305 mSysUiFlagContainer = Dependency.get(SysUiState.class); 306 // Set up the context group of buttons 307 mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container); 308 final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher, 309 mLightContext, R.drawable.ic_ime_switcher_default); 310 final ContextualButton accessibilityButton = 311 new ContextualButton(R.id.accessibility_button, mLightContext, 312 R.drawable.ic_sysbar_accessibility_button); 313 mContextualButtonGroup.addButton(imeSwitcherButton); 314 mContextualButtonGroup.addButton(accessibilityButton); 315 mRotationContextButton = new RotationContextButton(R.id.rotate_suggestion, 316 mLightContext, R.drawable.ic_sysbar_rotate_button_ccw_start_0); 317 mFloatingRotationButton = new FloatingRotationButton(context); 318 mRotationButtonController = new RotationButtonController(mLightContext, 319 mLightIconColor, mDarkIconColor); 320 updateRotationButton(); 321 322 mOverviewProxyService = Dependency.get(OverviewProxyService.class); 323 324 mConfiguration = new Configuration(); 325 mTmpLastConfiguration = new Configuration(); 326 mConfiguration.updateFrom(context.getResources().getConfiguration()); 327 328 mScreenPinningNotify = new ScreenPinningNotify(mContext); 329 mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class)); 330 331 mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back)); 332 mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home)); 333 mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle)); 334 mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps)); 335 mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton); 336 mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton); 337 mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup); 338 mDeadZone = new DeadZone(this); 339 340 mNavColorSampleMargin = getResources() 341 .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin); 342 mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class) 343 .create(mContext); 344 mEdgeBackGestureHandler.setStateChangeCallback(this::updateStates); 345 mRegionSamplingHelper = new RegionSamplingHelper(this, 346 new RegionSamplingHelper.SamplingCallback() { 347 @Override 348 public void onRegionDarknessChanged(boolean isRegionDark) { 349 getLightTransitionsController().setIconsDark(!isRegionDark , 350 true /* animate */); 351 } 352 353 @Override 354 public Rect getSampledRegion(View sampledView) { 355 if (mOrientedHandleSamplingRegion != null) { 356 return mOrientedHandleSamplingRegion; 357 } 358 359 updateSamplingRect(); 360 return mSamplingBounds; 361 } 362 363 @Override 364 public boolean isSamplingEnabled() { 365 return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode); 366 } 367 }); 368 369 mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class); 370 if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { 371 mNavBarOverlayController.init(mNavbarOverlayVisibilityChangeCallback, 372 mEdgeBackGestureHandler::updateNavigationBarOverlayExcludeRegion); 373 } 374 } 375 setAutoHideController(AutoHideController autoHideController)376 public void setAutoHideController(AutoHideController autoHideController) { 377 mAutoHideController = autoHideController; 378 } 379 getBarTransitions()380 public NavigationBarTransitions getBarTransitions() { 381 return mBarTransitions; 382 } 383 getLightTransitionsController()384 public LightBarTransitionsController getLightTransitionsController() { 385 return mBarTransitions.getLightTransitionsController(); 386 } 387 setComponents(NotificationPanelViewController panel)388 public void setComponents(NotificationPanelViewController panel) { 389 mPanelView = panel; 390 updatePanelSystemUiStateFlags(); 391 } 392 setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener)393 public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) { 394 mOnVerticalChangedListener = onVerticalChangedListener; 395 notifyVerticalChangedListener(mIsVertical); 396 } 397 398 @Override onInterceptTouchEvent(MotionEvent event)399 public boolean onInterceptTouchEvent(MotionEvent event) { 400 if (isGesturalMode(mNavBarMode) && mImeVisible 401 && event.getAction() == MotionEvent.ACTION_DOWN) { 402 SysUiStatsLog.write(SysUiStatsLog.IME_TOUCH_REPORTED, 403 (int) event.getX(), (int) event.getY()); 404 } 405 return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event); 406 } 407 408 @Override onTouchEvent(MotionEvent event)409 public boolean onTouchEvent(MotionEvent event) { 410 shouldDeadZoneConsumeTouchEvents(event); 411 return super.onTouchEvent(event); 412 } 413 414 /** 415 * If we're blurring the shade window. 416 */ setWindowHasBlurs(boolean hasBlurs)417 public void setWindowHasBlurs(boolean hasBlurs) { 418 mRegionSamplingHelper.setWindowHasBlurs(hasBlurs); 419 } 420 onTransientStateChanged(boolean isTransient)421 void onTransientStateChanged(boolean isTransient) { 422 mEdgeBackGestureHandler.onNavBarTransientStateChanged(isTransient); 423 424 // The visibility of the navigation bar buttons is dependent on the transient state of 425 // the navigation bar. 426 if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { 427 mNavBarOverlayController.setButtonState(isTransient, /* force */ false); 428 } 429 } 430 onBarTransition(int newMode)431 void onBarTransition(int newMode) { 432 if (newMode == MODE_OPAQUE) { 433 // If the nav bar background is opaque, stop auto tinting since we know the icons are 434 // showing over a dark background 435 mRegionSamplingHelper.stop(); 436 getLightTransitionsController().setIconsDark(false /* dark */, true /* animate */); 437 } else { 438 mRegionSamplingHelper.start(mSamplingBounds); 439 } 440 } 441 shouldDeadZoneConsumeTouchEvents(MotionEvent event)442 private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) { 443 int action = event.getActionMasked(); 444 if (action == MotionEvent.ACTION_DOWN) { 445 mDeadZoneConsuming = false; 446 } 447 if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) { 448 switch (action) { 449 case MotionEvent.ACTION_DOWN: 450 // Allow gestures starting in the deadzone to be slippery 451 setSlippery(true); 452 mDeadZoneConsuming = true; 453 break; 454 case MotionEvent.ACTION_CANCEL: 455 case MotionEvent.ACTION_UP: 456 // When a gesture started in the deadzone is finished, restore slippery state 457 updateSlippery(); 458 mDeadZoneConsuming = false; 459 break; 460 } 461 return true; 462 } 463 return false; 464 } 465 abortCurrentGesture()466 public void abortCurrentGesture() { 467 getHomeButton().abortCurrentGesture(); 468 } 469 getCurrentView()470 public View getCurrentView() { 471 return mCurrentView; 472 } 473 getRotationButtonController()474 public RotationButtonController getRotationButtonController() { 475 return mRotationButtonController; 476 } 477 getFloatingRotationButton()478 public FloatingRotationButton getFloatingRotationButton() { 479 return mFloatingRotationButton; 480 } 481 getRecentsButton()482 public ButtonDispatcher getRecentsButton() { 483 return mButtonDispatchers.get(R.id.recent_apps); 484 } 485 getBackButton()486 public ButtonDispatcher getBackButton() { 487 return mButtonDispatchers.get(R.id.back); 488 } 489 getHomeButton()490 public ButtonDispatcher getHomeButton() { 491 return mButtonDispatchers.get(R.id.home); 492 } 493 getImeSwitchButton()494 public ButtonDispatcher getImeSwitchButton() { 495 return mButtonDispatchers.get(R.id.ime_switcher); 496 } 497 getAccessibilityButton()498 public ButtonDispatcher getAccessibilityButton() { 499 return mButtonDispatchers.get(R.id.accessibility_button); 500 } 501 getRotateSuggestionButton()502 public RotationContextButton getRotateSuggestionButton() { 503 return (RotationContextButton) mButtonDispatchers.get(R.id.rotate_suggestion); 504 } 505 getHomeHandle()506 public ButtonDispatcher getHomeHandle() { 507 return mButtonDispatchers.get(R.id.home_handle); 508 } 509 getButtonDispatchers()510 public SparseArray<ButtonDispatcher> getButtonDispatchers() { 511 return mButtonDispatchers; 512 } 513 isRecentsButtonVisible()514 public boolean isRecentsButtonVisible() { 515 return getRecentsButton().getVisibility() == View.VISIBLE; 516 } 517 isOverviewEnabled()518 public boolean isOverviewEnabled() { 519 return (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) == 0; 520 } 521 isQuickStepSwipeUpEnabled()522 public boolean isQuickStepSwipeUpEnabled() { 523 return mOverviewProxyService.shouldShowSwipeUpUI() && isOverviewEnabled(); 524 } 525 reloadNavIcons()526 private void reloadNavIcons() { 527 updateIcons(Configuration.EMPTY); 528 } 529 updateIcons(Configuration oldConfig)530 private void updateIcons(Configuration oldConfig) { 531 final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation; 532 final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi; 533 final boolean dirChange = oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection(); 534 535 if (orientationChange || densityChange) { 536 mDockedIcon = getDrawable(R.drawable.ic_sysbar_docked); 537 mHomeDefaultIcon = getHomeDrawable(); 538 } 539 if (densityChange || dirChange) { 540 mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent); 541 mContextualButtonGroup.updateIcons(mLightIconColor, mDarkIconColor); 542 } 543 if (orientationChange || densityChange || dirChange) { 544 mBackIcon = getBackDrawable(); 545 } 546 } 547 548 /** 549 * Updates the rotation button based on the current navigation mode. 550 */ updateRotationButton()551 private void updateRotationButton() { 552 if (isGesturalMode(mNavBarMode)) { 553 mContextualButtonGroup.removeButton(R.id.rotate_suggestion); 554 mButtonDispatchers.remove(R.id.rotate_suggestion); 555 mRotationButtonController.setRotationButton(mFloatingRotationButton, 556 mRotationButtonListener); 557 } else if (mContextualButtonGroup.getContextButton(R.id.rotate_suggestion) == null) { 558 mContextualButtonGroup.addButton(mRotationContextButton); 559 mButtonDispatchers.put(R.id.rotate_suggestion, mRotationContextButton); 560 mRotationButtonController.setRotationButton(mRotationContextButton, 561 mRotationButtonListener); 562 } 563 } 564 getBackDrawable()565 public KeyButtonDrawable getBackDrawable() { 566 KeyButtonDrawable drawable = getDrawable(getBackDrawableRes()); 567 orientBackButton(drawable); 568 return drawable; 569 } 570 getBackDrawableRes()571 public @DrawableRes int getBackDrawableRes() { 572 return chooseNavigationIconDrawableRes(R.drawable.ic_sysbar_back, 573 R.drawable.ic_sysbar_back_quick_step); 574 } 575 getHomeDrawable()576 public KeyButtonDrawable getHomeDrawable() { 577 final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI(); 578 KeyButtonDrawable drawable = quickStepEnabled 579 ? getDrawable(R.drawable.ic_sysbar_home_quick_step) 580 : getDrawable(R.drawable.ic_sysbar_home); 581 orientHomeButton(drawable); 582 return drawable; 583 } 584 orientBackButton(KeyButtonDrawable drawable)585 private void orientBackButton(KeyButtonDrawable drawable) { 586 final boolean useAltBack = 587 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; 588 final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 589 float degrees = useAltBack ? (isRtl ? 90 : -90) : 0; 590 if (drawable.getRotation() == degrees) { 591 return; 592 } 593 594 if (isGesturalMode(mNavBarMode)) { 595 drawable.setRotation(degrees); 596 return; 597 } 598 599 // Animate the back button's rotation to the new degrees and only in portrait move up the 600 // back button to line up with the other buttons 601 float targetY = !mOverviewProxyService.shouldShowSwipeUpUI() && !mIsVertical && useAltBack 602 ? - getResources().getDimension(R.dimen.navbar_back_button_ime_offset) 603 : 0; 604 ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable, 605 PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees), 606 PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY)); 607 navBarAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 608 navBarAnimator.setDuration(200); 609 navBarAnimator.start(); 610 } 611 orientHomeButton(KeyButtonDrawable drawable)612 private void orientHomeButton(KeyButtonDrawable drawable) { 613 drawable.setRotation(mIsVertical ? 90 : 0); 614 } 615 chooseNavigationIconDrawableRes(@rawableRes int icon, @DrawableRes int quickStepIcon)616 private @DrawableRes int chooseNavigationIconDrawableRes(@DrawableRes int icon, 617 @DrawableRes int quickStepIcon) { 618 final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI(); 619 return quickStepEnabled ? quickStepIcon : icon; 620 } 621 getDrawable(@rawableRes int icon)622 private KeyButtonDrawable getDrawable(@DrawableRes int icon) { 623 return KeyButtonDrawable.create(mLightContext, mLightIconColor, mDarkIconColor, icon, 624 true /* hasShadow */, null /* ovalBackgroundColor */); 625 } 626 627 /** To be called when screen lock/unlock state changes */ onScreenStateChanged(boolean isScreenOn)628 public void onScreenStateChanged(boolean isScreenOn) { 629 mScreenOn = isScreenOn; 630 if (isScreenOn) { 631 if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) { 632 mRegionSamplingHelper.start(mSamplingBounds); 633 } 634 } else { 635 mRegionSamplingHelper.stop(); 636 } 637 } 638 setWindowVisible(boolean visible)639 public void setWindowVisible(boolean visible) { 640 mRegionSamplingHelper.setWindowVisible(visible); 641 mRotationButtonController.onNavigationBarWindowVisibilityChange(visible); 642 } 643 setBehavior(@ehavior int behavior)644 public void setBehavior(@Behavior int behavior) { 645 mRotationButtonController.onBehaviorChanged(behavior); 646 } 647 648 @Override setLayoutDirection(int layoutDirection)649 public void setLayoutDirection(int layoutDirection) { 650 reloadNavIcons(); 651 652 super.setLayoutDirection(layoutDirection); 653 } 654 setNavigationIconHints(int hints)655 public void setNavigationIconHints(int hints) { 656 if (hints == mNavigationIconHints) return; 657 final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; 658 final boolean oldBackAlt = 659 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; 660 if (newBackAlt != oldBackAlt) { 661 onImeVisibilityChanged(newBackAlt); 662 } 663 664 if (DEBUG) { 665 android.widget.Toast.makeText(getContext(), 666 "Navigation icon hints = " + hints, 667 500).show(); 668 } 669 mNavigationIconHints = hints; 670 updateNavButtonIcons(); 671 } 672 onImeVisibilityChanged(boolean visible)673 private void onImeVisibilityChanged(boolean visible) { 674 if (!visible) { 675 mTransitionListener.onBackAltCleared(); 676 } 677 mImeVisible = visible; 678 mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible); 679 if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { 680 mNavBarOverlayController.setCanShow(!mImeVisible); 681 } 682 } 683 setDisabledFlags(int disabledFlags)684 public void setDisabledFlags(int disabledFlags) { 685 if (mDisabledFlags == disabledFlags) return; 686 687 final boolean overviewEnabledBefore = isOverviewEnabled(); 688 mDisabledFlags = disabledFlags; 689 690 // Update icons if overview was just enabled to ensure the correct icons are present 691 if (!overviewEnabledBefore && isOverviewEnabled()) { 692 reloadNavIcons(); 693 } 694 695 updateNavButtonIcons(); 696 updateSlippery(); 697 updateDisabledSystemUiStateFlags(); 698 } 699 updateNavButtonIcons()700 public void updateNavButtonIcons() { 701 // We have to replace or restore the back and home button icons when exiting or entering 702 // carmode, respectively. Recents are not available in CarMode in nav bar so change 703 // to recent icon is not required. 704 final boolean useAltBack = 705 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; 706 KeyButtonDrawable backIcon = mBackIcon; 707 orientBackButton(backIcon); 708 KeyButtonDrawable homeIcon = mHomeDefaultIcon; 709 if (!mUseCarModeUi) { 710 orientHomeButton(homeIcon); 711 } 712 getHomeButton().setImageDrawable(homeIcon); 713 getBackButton().setImageDrawable(backIcon); 714 715 updateRecentsIcon(); 716 717 // Update IME button visibility, a11y and rotate button always overrides the appearance 718 mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, 719 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0); 720 721 mBarTransitions.reapplyDarkIntensity(); 722 723 boolean disableHome = isGesturalMode(mNavBarMode) 724 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); 725 726 // Always disable recents when alternate car mode UI is active and for secondary displays. 727 boolean disableRecent = isRecentsButtonDisabled(); 728 729 // Disable the home handle if both hone and recents are disabled 730 boolean disableHomeHandle = disableRecent 731 && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); 732 733 boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures() 734 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)); 735 736 // When screen pinning, don't hide back and home when connected service or back and 737 // recents buttons when disconnected from launcher service in screen pinning mode, 738 // as they are used for exiting. 739 final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive(); 740 if (mOverviewProxyService.isEnabled()) { 741 // Force disable recents when not in legacy mode 742 disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode); 743 if (pinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) { 744 disableBack = disableHome = false; 745 } 746 } else if (pinningActive) { 747 disableBack = disableRecent = false; 748 } 749 750 ViewGroup navButtons = getCurrentView().findViewById(R.id.nav_buttons); 751 if (navButtons != null) { 752 LayoutTransition lt = navButtons.getLayoutTransition(); 753 if (lt != null) { 754 if (!lt.getTransitionListeners().contains(mTransitionListener)) { 755 lt.addTransitionListener(mTransitionListener); 756 } 757 } 758 } 759 760 getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); 761 getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); 762 getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); 763 getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE); 764 notifyActiveTouchRegions(); 765 } 766 767 @VisibleForTesting isRecentsButtonDisabled()768 boolean isRecentsButtonDisabled() { 769 return mUseCarModeUi || !isOverviewEnabled() 770 || getContext().getDisplayId() != Display.DEFAULT_DISPLAY; 771 } 772 getContextDisplay()773 private Display getContextDisplay() { 774 return getContext().getDisplay(); 775 } 776 setLayoutTransitionsEnabled(boolean enabled)777 public void setLayoutTransitionsEnabled(boolean enabled) { 778 mLayoutTransitionsEnabled = enabled; 779 updateLayoutTransitionsEnabled(); 780 } 781 setWakeAndUnlocking(boolean wakeAndUnlocking)782 public void setWakeAndUnlocking(boolean wakeAndUnlocking) { 783 setUseFadingAnimations(wakeAndUnlocking); 784 mWakeAndUnlocking = wakeAndUnlocking; 785 updateLayoutTransitionsEnabled(); 786 } 787 updateLayoutTransitionsEnabled()788 private void updateLayoutTransitionsEnabled() { 789 boolean enabled = !mWakeAndUnlocking && mLayoutTransitionsEnabled; 790 ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons); 791 LayoutTransition lt = navButtons.getLayoutTransition(); 792 if (lt != null) { 793 if (enabled) { 794 lt.enableTransitionType(LayoutTransition.APPEARING); 795 lt.enableTransitionType(LayoutTransition.DISAPPEARING); 796 lt.enableTransitionType(LayoutTransition.CHANGE_APPEARING); 797 lt.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); 798 } else { 799 lt.disableTransitionType(LayoutTransition.APPEARING); 800 lt.disableTransitionType(LayoutTransition.DISAPPEARING); 801 lt.disableTransitionType(LayoutTransition.CHANGE_APPEARING); 802 lt.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); 803 } 804 } 805 } 806 setUseFadingAnimations(boolean useFadingAnimations)807 private void setUseFadingAnimations(boolean useFadingAnimations) { 808 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) ((ViewGroup) getParent()) 809 .getLayoutParams(); 810 if (lp != null) { 811 boolean old = lp.windowAnimations != 0; 812 if (!old && useFadingAnimations) { 813 lp.windowAnimations = R.style.Animation_NavigationBarFadeIn; 814 } else if (old && !useFadingAnimations) { 815 lp.windowAnimations = 0; 816 } else { 817 return; 818 } 819 WindowManager wm = getContext().getSystemService(WindowManager.class); 820 wm.updateViewLayout((View) getParent(), lp); 821 } 822 } 823 onStatusBarPanelStateChanged()824 public void onStatusBarPanelStateChanged() { 825 updateSlippery(); 826 updatePanelSystemUiStateFlags(); 827 } 828 updateDisabledSystemUiStateFlags()829 public void updateDisabledSystemUiStateFlags() { 830 int displayId = mContext.getDisplayId(); 831 832 mSysUiFlagContainer.setFlag(SYSUI_STATE_SCREEN_PINNING, 833 ActivityManagerWrapper.getInstance().isScreenPinningActive()) 834 .setFlag(SYSUI_STATE_OVERVIEW_DISABLED, 835 (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0) 836 .setFlag(SYSUI_STATE_HOME_DISABLED, 837 (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0) 838 .setFlag(SYSUI_STATE_SEARCH_DISABLED, 839 (mDisabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0) 840 .commitUpdate(displayId); 841 } 842 updatePanelSystemUiStateFlags()843 public void updatePanelSystemUiStateFlags() { 844 int displayId = mContext.getDisplayId(); 845 if (SysUiState.DEBUG) { 846 Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView); 847 } 848 if (mPanelView != null) { 849 if (SysUiState.DEBUG) { 850 Log.d(TAG, "Updating panel sysui state flags: fullyExpanded=" 851 + mPanelView.isFullyExpanded() + " inQs=" + mPanelView.isInSettings()); 852 } 853 mSysUiFlagContainer.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED, 854 mPanelView.isFullyExpanded() && !mPanelView.isInSettings()) 855 .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, 856 mPanelView.isInSettings()) 857 .commitUpdate(displayId); 858 } 859 } 860 updateStates()861 public void updateStates() { 862 final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI(); 863 864 if (mNavigationInflaterView != null) { 865 // Reinflate the navbar if needed, no-op unless the swipe up state changes 866 mNavigationInflaterView.onLikelyDefaultLayoutChange(); 867 } 868 869 updateSlippery(); 870 reloadNavIcons(); 871 updateNavButtonIcons(); 872 WindowManagerWrapper.getInstance().setNavBarVirtualKeyHapticFeedbackEnabled(!showSwipeUpUI); 873 getHomeButton().setAccessibilityDelegate( 874 showSwipeUpUI ? mQuickStepAccessibilityDelegate : null); 875 } 876 877 /** 878 * Updates the {@link WindowManager.LayoutParams.FLAG_SLIPPERY} state dependent on if swipe up 879 * is enabled, or the notifications is fully opened without being in an animated state. If 880 * slippery is enabled, touch events will leave the nav bar window and enter into the fullscreen 881 * app/home window, if not nav bar will receive a cancelled touch event once gesture leaves bar. 882 */ updateSlippery()883 public void updateSlippery() { 884 setSlippery(!isQuickStepSwipeUpEnabled() || 885 (mPanelView != null && mPanelView.isFullyExpanded() && !mPanelView.isCollapsing())); 886 } 887 setSlippery(boolean slippery)888 private void setSlippery(boolean slippery) { 889 setWindowFlag(WindowManager.LayoutParams.FLAG_SLIPPERY, slippery); 890 } 891 setWindowFlag(int flags, boolean enable)892 private void setWindowFlag(int flags, boolean enable) { 893 final ViewGroup navbarView = ((ViewGroup) getParent()); 894 if (navbarView == null) { 895 return; 896 } 897 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView.getLayoutParams(); 898 if (lp == null || enable == ((lp.flags & flags) != 0)) { 899 return; 900 } 901 if (enable) { 902 lp.flags |= flags; 903 } else { 904 lp.flags &= ~flags; 905 } 906 WindowManager wm = getContext().getSystemService(WindowManager.class); 907 wm.updateViewLayout(navbarView, lp); 908 } 909 910 @Override onNavigationModeChanged(int mode)911 public void onNavigationModeChanged(int mode) { 912 mNavBarMode = mode; 913 mBarTransitions.onNavigationModeChanged(mNavBarMode); 914 mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode); 915 updateRotationButton(); 916 917 if (isGesturalMode(mNavBarMode)) { 918 mRegionSamplingHelper.start(mSamplingBounds); 919 } else { 920 mRegionSamplingHelper.stop(); 921 } 922 } 923 setAccessibilityButtonState(final boolean visible, final boolean longClickable)924 public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) { 925 mLongClickableAccessibilityButton = longClickable; 926 getAccessibilityButton().setLongClickable(longClickable); 927 mContextualButtonGroup.setButtonVisibility(R.id.accessibility_button, visible); 928 } 929 930 @Override onFinishInflate()931 public void onFinishInflate() { 932 super.onFinishInflate(); 933 mNavigationInflaterView = findViewById(R.id.navigation_inflater); 934 mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers); 935 936 updateOrientationViews(); 937 reloadNavIcons(); 938 } 939 940 @Override onDraw(Canvas canvas)941 protected void onDraw(Canvas canvas) { 942 mDeadZone.onDraw(canvas); 943 super.onDraw(canvas); 944 } 945 updateSamplingRect()946 private void updateSamplingRect() { 947 mSamplingBounds.setEmpty(); 948 // TODO: Extend this to 2/3 button layout as well 949 View view = getHomeHandle().getCurrentView(); 950 951 if (view != null) { 952 int[] pos = new int[2]; 953 view.getLocationOnScreen(pos); 954 Point displaySize = new Point(); 955 view.getContext().getDisplay().getRealSize(displaySize); 956 final Rect samplingBounds = new Rect(pos[0] - mNavColorSampleMargin, 957 displaySize.y - getNavBarHeight(), 958 pos[0] + view.getWidth() + mNavColorSampleMargin, 959 displaySize.y); 960 mSamplingBounds.set(samplingBounds); 961 } 962 } 963 setOrientedHandleSamplingRegion(Rect orientedHandleSamplingRegion)964 void setOrientedHandleSamplingRegion(Rect orientedHandleSamplingRegion) { 965 mOrientedHandleSamplingRegion = orientedHandleSamplingRegion; 966 mRegionSamplingHelper.updateSamplingRect(); 967 } 968 969 @Override onLayout(boolean changed, int left, int top, int right, int bottom)970 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 971 super.onLayout(changed, left, top, right, bottom); 972 973 notifyActiveTouchRegions(); 974 } 975 976 /** 977 * Notifies the overview service of the active touch regions. 978 */ notifyActiveTouchRegions()979 public void notifyActiveTouchRegions() { 980 mOverviewProxyService.onActiveNavBarRegionChanges( 981 getButtonLocations(true /* includeFloatingButtons */, true /* inScreen */, 982 true /* useNearestRegion */)); 983 } 984 updateButtonTouchRegionCache()985 private void updateButtonTouchRegionCache() { 986 FrameLayout navBarLayout = mIsVertical 987 ? mNavigationInflaterView.mVertical 988 : mNavigationInflaterView.mHorizontal; 989 mButtonFullTouchableRegions = ((NearestTouchFrame) navBarLayout 990 .findViewById(R.id.nav_buttons)).getFullTouchableChildRegions(); 991 } 992 993 /** 994 * @param includeFloatingButtons Whether to include the floating rotation and overlay button in 995 * the region for all the buttons 996 * @param inScreenSpace Whether to return values in screen space or window space 997 * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds 998 * @return 999 */ getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace, boolean useNearestRegion)1000 private Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace, 1001 boolean useNearestRegion) { 1002 if (useNearestRegion && !inScreenSpace) { 1003 // We currently don't support getting the nearest region in anything but screen space 1004 useNearestRegion = false; 1005 } 1006 mTmpRegion.setEmpty(); 1007 updateButtonTouchRegionCache(); 1008 updateButtonLocation(getBackButton(), inScreenSpace, useNearestRegion); 1009 updateButtonLocation(getHomeButton(), inScreenSpace, useNearestRegion); 1010 updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion); 1011 updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion); 1012 updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion); 1013 if (includeFloatingButtons && mFloatingRotationButton.isVisible()) { 1014 // Note: this button is floating so the nearest region doesn't apply 1015 updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace); 1016 } else { 1017 updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion); 1018 } 1019 if (includeFloatingButtons && mNavBarOverlayController.isNavigationBarOverlayEnabled() 1020 && mNavBarOverlayController.isVisible()) { 1021 // Note: this button is floating so the nearest region doesn't apply 1022 updateButtonLocation(mNavBarOverlayController.getCurrentView(), inScreenSpace); 1023 } 1024 return mTmpRegion; 1025 } 1026 updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace, boolean useNearestRegion)1027 private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace, 1028 boolean useNearestRegion) { 1029 if (button == null) { 1030 return; 1031 } 1032 View view = button.getCurrentView(); 1033 if (view == null || !button.isVisible()) { 1034 return; 1035 } 1036 // If the button is tappable from perspective of NearestTouchFrame, then we'll 1037 // include the regions where the tap is valid instead of just the button layout location 1038 if (useNearestRegion && mButtonFullTouchableRegions.containsKey(view)) { 1039 mTmpRegion.op(mButtonFullTouchableRegions.get(view), Op.UNION); 1040 return; 1041 } 1042 updateButtonLocation(view, inScreenSpace); 1043 } 1044 updateButtonLocation(View view, boolean inScreenSpace)1045 private void updateButtonLocation(View view, boolean inScreenSpace) { 1046 if (inScreenSpace) { 1047 view.getBoundsOnScreen(mTmpBounds); 1048 } else { 1049 view.getLocationInWindow(mTmpPosition); 1050 mTmpBounds.set(mTmpPosition[0], mTmpPosition[1], 1051 mTmpPosition[0] + view.getWidth(), 1052 mTmpPosition[1] + view.getHeight()); 1053 } 1054 mTmpRegion.op(mTmpBounds, Op.UNION); 1055 } 1056 updateOrientationViews()1057 private void updateOrientationViews() { 1058 mHorizontal = findViewById(R.id.horizontal); 1059 mVertical = findViewById(R.id.vertical); 1060 1061 updateCurrentView(); 1062 } 1063 needsReorient(int rotation)1064 boolean needsReorient(int rotation) { 1065 return mCurrentRotation != rotation; 1066 } 1067 updateCurrentView()1068 private void updateCurrentView() { 1069 resetViews(); 1070 mCurrentView = mIsVertical ? mVertical : mHorizontal; 1071 mCurrentView.setVisibility(View.VISIBLE); 1072 mNavigationInflaterView.setVertical(mIsVertical); 1073 mCurrentRotation = getContextDisplay().getRotation(); 1074 mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90); 1075 mNavigationInflaterView.updateButtonDispatchersCurrentView(); 1076 updateLayoutTransitionsEnabled(); 1077 } 1078 resetViews()1079 private void resetViews() { 1080 mHorizontal.setVisibility(View.GONE); 1081 mVertical.setVisibility(View.GONE); 1082 } 1083 updateRecentsIcon()1084 private void updateRecentsIcon() { 1085 mDockedIcon.setRotation(mDockedStackExists && mIsVertical ? 90 : 0); 1086 getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon); 1087 mBarTransitions.reapplyDarkIntensity(); 1088 } 1089 showPinningEnterExitToast(boolean entering)1090 public void showPinningEnterExitToast(boolean entering) { 1091 if (entering) { 1092 mScreenPinningNotify.showPinningStartToast(); 1093 } else { 1094 mScreenPinningNotify.showPinningExitToast(); 1095 } 1096 } 1097 showPinningEscapeToast()1098 public void showPinningEscapeToast() { 1099 mScreenPinningNotify.showEscapeToast( 1100 mNavBarMode == NAV_BAR_MODE_GESTURAL, isRecentsButtonVisible()); 1101 } 1102 isVertical()1103 public boolean isVertical() { 1104 return mIsVertical; 1105 } 1106 reorient()1107 public void reorient() { 1108 updateCurrentView(); 1109 1110 ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone); 1111 mDeadZone.onConfigurationChanged(mCurrentRotation); 1112 1113 // force the low profile & disabled states into compliance 1114 mBarTransitions.init(); 1115 1116 if (DEBUG) { 1117 Log.d(TAG, "reorient(): rot=" + mCurrentRotation); 1118 } 1119 1120 // Resolve layout direction if not resolved since components changing layout direction such 1121 // as changing languages will recreate this view and the direction will be resolved later 1122 if (!isLayoutDirectionResolved()) { 1123 resolveLayoutDirection(); 1124 } 1125 updateNavButtonIcons(); 1126 1127 getHomeButton().setVertical(mIsVertical); 1128 } 1129 1130 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)1131 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1132 int w = MeasureSpec.getSize(widthMeasureSpec); 1133 int h = MeasureSpec.getSize(heightMeasureSpec); 1134 if (DEBUG) Log.d(TAG, String.format( 1135 "onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight())); 1136 1137 final boolean newVertical = w > 0 && h > w 1138 && !isGesturalMode(mNavBarMode); 1139 if (newVertical != mIsVertical) { 1140 mIsVertical = newVertical; 1141 if (DEBUG) { 1142 Log.d(TAG, String.format("onMeasure: h=%d, w=%d, vert=%s", h, w, 1143 mIsVertical ? "y" : "n")); 1144 } 1145 reorient(); 1146 notifyVerticalChangedListener(newVertical); 1147 } 1148 1149 if (isGesturalMode(mNavBarMode)) { 1150 // Update the nav bar background to match the height of the visible nav bar 1151 int height = mIsVertical 1152 ? getResources().getDimensionPixelSize( 1153 com.android.internal.R.dimen.navigation_bar_height_landscape) 1154 : getResources().getDimensionPixelSize( 1155 com.android.internal.R.dimen.navigation_bar_height); 1156 int frameHeight = getResources().getDimensionPixelSize( 1157 com.android.internal.R.dimen.navigation_bar_frame_height); 1158 mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h)); 1159 } else { 1160 mBarTransitions.setBackgroundFrame(null); 1161 } 1162 1163 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1164 } 1165 getNavBarHeight()1166 private int getNavBarHeight() { 1167 return mIsVertical 1168 ? getResources().getDimensionPixelSize( 1169 com.android.internal.R.dimen.navigation_bar_height_landscape) 1170 : getResources().getDimensionPixelSize( 1171 com.android.internal.R.dimen.navigation_bar_height); 1172 } 1173 notifyVerticalChangedListener(boolean newVertical)1174 private void notifyVerticalChangedListener(boolean newVertical) { 1175 if (mOnVerticalChangedListener != null) { 1176 mOnVerticalChangedListener.onVerticalChanged(newVertical); 1177 } 1178 } 1179 1180 @Override onConfigurationChanged(Configuration newConfig)1181 protected void onConfigurationChanged(Configuration newConfig) { 1182 super.onConfigurationChanged(newConfig); 1183 mTmpLastConfiguration.updateFrom(mConfiguration); 1184 mConfiguration.updateFrom(newConfig); 1185 boolean uiCarModeChanged = updateCarMode(); 1186 updateIcons(mTmpLastConfiguration); 1187 updateRecentsIcon(); 1188 mEdgeBackGestureHandler.onConfigurationChanged(mConfiguration); 1189 if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi 1190 || mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) { 1191 // If car mode or density changes, we need to reset the icons. 1192 updateNavButtonIcons(); 1193 } 1194 } 1195 1196 /** 1197 * If the configuration changed, update the carmode and return that it was updated. 1198 */ updateCarMode()1199 private boolean updateCarMode() { 1200 boolean uiCarModeChanged = false; 1201 if (mConfiguration != null) { 1202 int uiMode = mConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK; 1203 final boolean isCarMode = (uiMode == Configuration.UI_MODE_TYPE_CAR); 1204 1205 if (isCarMode != mInCarMode) { 1206 mInCarMode = isCarMode; 1207 if (ALTERNATE_CAR_MODE_UI) { 1208 mUseCarModeUi = isCarMode; 1209 uiCarModeChanged = true; 1210 } else { 1211 // Don't use car mode behavior if ALTERNATE_CAR_MODE_UI not set. 1212 mUseCarModeUi = false; 1213 } 1214 } 1215 } 1216 return uiCarModeChanged; 1217 } 1218 getResourceName(int resId)1219 private String getResourceName(int resId) { 1220 if (resId != 0) { 1221 final android.content.res.Resources res = getContext().getResources(); 1222 try { 1223 return res.getResourceName(resId); 1224 } catch (android.content.res.Resources.NotFoundException ex) { 1225 return "(unknown)"; 1226 } 1227 } else { 1228 return "(null)"; 1229 } 1230 } 1231 visibilityToString(int vis)1232 private static String visibilityToString(int vis) { 1233 switch (vis) { 1234 case View.INVISIBLE: 1235 return "INVISIBLE"; 1236 case View.GONE: 1237 return "GONE"; 1238 default: 1239 return "VISIBLE"; 1240 } 1241 } 1242 1243 @Override onAttachedToWindow()1244 protected void onAttachedToWindow() { 1245 super.onAttachedToWindow(); 1246 // This needs to happen first as it can changed the enabled state which can affect whether 1247 // the back button is visible 1248 mEdgeBackGestureHandler.onNavBarAttached(); 1249 requestApplyInsets(); 1250 reorient(); 1251 onNavigationModeChanged(mNavBarMode); 1252 if (mRotationButtonController != null) { 1253 mRotationButtonController.registerListeners(); 1254 } 1255 if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { 1256 mNavBarOverlayController.registerListeners(); 1257 } 1258 1259 getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener); 1260 updateNavButtonIcons(); 1261 } 1262 1263 @Override onDetachedFromWindow()1264 protected void onDetachedFromWindow() { 1265 super.onDetachedFromWindow(); 1266 Dependency.get(NavigationModeController.class).removeListener(this); 1267 for (int i = 0; i < mButtonDispatchers.size(); ++i) { 1268 mButtonDispatchers.valueAt(i).onDestroy(); 1269 } 1270 if (mRotationButtonController != null) { 1271 mRotationButtonController.unregisterListeners(); 1272 } 1273 1274 if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { 1275 mNavBarOverlayController.unregisterListeners(); 1276 } 1277 1278 mEdgeBackGestureHandler.onNavBarDetached(); 1279 getViewTreeObserver().removeOnComputeInternalInsetsListener( 1280 mOnComputeInternalInsetsListener); 1281 } 1282 dump(PrintWriter pw)1283 public void dump(PrintWriter pw) { 1284 final Rect r = new Rect(); 1285 final Point size = new Point(); 1286 getContextDisplay().getRealSize(size); 1287 1288 pw.println("NavigationBarView:"); 1289 pw.println(String.format(" this: " + StatusBar.viewInfo(this) 1290 + " " + visibilityToString(getVisibility()))); 1291 1292 getWindowVisibleDisplayFrame(r); 1293 final boolean offscreen = r.right > size.x || r.bottom > size.y; 1294 pw.println(" window: " 1295 + r.toShortString() 1296 + " " + visibilityToString(getWindowVisibility()) 1297 + (offscreen ? " OFFSCREEN!" : "")); 1298 1299 pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s %f", 1300 getResourceName(getCurrentView().getId()), 1301 getCurrentView().getWidth(), getCurrentView().getHeight(), 1302 visibilityToString(getCurrentView().getVisibility()), 1303 getCurrentView().getAlpha())); 1304 1305 pw.println(String.format(" disabled=0x%08x vertical=%s darkIntensity=%.2f", 1306 mDisabledFlags, 1307 mIsVertical ? "true" : "false", 1308 getLightTransitionsController().getCurrentDarkIntensity())); 1309 1310 pw.println(" mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion); 1311 pw.println(" mScreenOn: " + mScreenOn); 1312 1313 1314 dumpButton(pw, "back", getBackButton()); 1315 dumpButton(pw, "home", getHomeButton()); 1316 dumpButton(pw, "rcnt", getRecentsButton()); 1317 dumpButton(pw, "rota", getRotateSuggestionButton()); 1318 dumpButton(pw, "a11y", getAccessibilityButton()); 1319 dumpButton(pw, "ime", getImeSwitchButton()); 1320 1321 if (mNavigationInflaterView != null) { 1322 mNavigationInflaterView.dump(pw); 1323 } 1324 mBarTransitions.dump(pw); 1325 mContextualButtonGroup.dump(pw); 1326 mRegionSamplingHelper.dump(pw); 1327 mEdgeBackGestureHandler.dump(pw); 1328 } 1329 1330 @Override onApplyWindowInsets(WindowInsets insets)1331 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 1332 int leftInset = insets.getSystemWindowInsetLeft(); 1333 int rightInset = insets.getSystemWindowInsetRight(); 1334 setPadding(leftInset, insets.getSystemWindowInsetTop(), rightInset, 1335 insets.getSystemWindowInsetBottom()); 1336 // we're passing the insets onto the gesture handler since the back arrow is only 1337 // conditionally added and doesn't always get all the insets. 1338 mEdgeBackGestureHandler.setInsets(leftInset, rightInset); 1339 1340 // this allows assist handle to be drawn outside its bound so that it can align screen 1341 // bottom by translating its y position. 1342 final boolean shouldClip = 1343 !isGesturalMode(mNavBarMode) || insets.getSystemWindowInsetBottom() == 0; 1344 setClipChildren(shouldClip); 1345 setClipToPadding(shouldClip); 1346 1347 return super.onApplyWindowInsets(insets); 1348 } 1349 registerDockedListener(LegacySplitScreen legacySplitScreen)1350 void registerDockedListener(LegacySplitScreen legacySplitScreen) { 1351 legacySplitScreen.registerInSplitScreenListener(mDockedListener); 1352 } 1353 registerPipExclusionBoundsChangeListener(Pip pip)1354 void registerPipExclusionBoundsChangeListener(Pip pip) { 1355 pip.setPipExclusionBoundsChangeListener(mPipListener); 1356 } 1357 dumpButton(PrintWriter pw, String caption, ButtonDispatcher button)1358 private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) { 1359 pw.print(" " + caption + ": "); 1360 if (button == null) { 1361 pw.print("null"); 1362 } else { 1363 pw.print(visibilityToString(button.getVisibility()) 1364 + " alpha=" + button.getAlpha() 1365 ); 1366 } 1367 pw.println(); 1368 } 1369 1370 public interface OnVerticalChangedListener { onVerticalChanged(boolean isVertical)1371 void onVerticalChanged(boolean isVertical); 1372 } 1373 1374 private final Consumer<Boolean> mDockedListener = exists -> post(() -> { 1375 mDockedStackExists = exists; 1376 updateRecentsIcon(); 1377 }); 1378 1379 private final Consumer<Rect> mPipListener = bounds -> post(() -> { 1380 mEdgeBackGestureHandler.setPipStashExclusionBounds(bounds); 1381 }); 1382 setNavigationBarLumaSamplingEnabled(boolean enable)1383 void setNavigationBarLumaSamplingEnabled(boolean enable) { 1384 if (enable) { 1385 mRegionSamplingHelper.start(mSamplingBounds); 1386 } else { 1387 mRegionSamplingHelper.stop(); 1388 } 1389 } 1390 } 1391