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