2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.statusbar.phone;
19 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
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_SCREEN_PINNING;
25 import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
26 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
28 import android.animation.LayoutTransition;
29 import android.animation.LayoutTransition.TransitionListener;
30 import android.animation.ObjectAnimator;
31 import android.animation.PropertyValuesHolder;
32 import android.animation.TimeInterpolator;
33 import android.animation.ValueAnimator;
34 import android.annotation.DrawableRes;
35 import android.app.StatusBarManager;
36 import android.content.Context;
37 import android.content.res.Configuration;
38 import android.graphics.Canvas;
39 import android.graphics.Point;
40 import android.graphics.Rect;
41 import android.graphics.Region;
42 import android.graphics.Region.Op;
43 import android.os.Bundle;
44 import android.util.AttributeSet;
45 import android.util.Log;
46 import android.util.SparseArray;
47 import android.view.Display;
48 import android.view.MotionEvent;
49 import android.view.Surface;
50 import android.view.View;
51 import android.view.ViewGroup;
52 import android.view.ViewTreeObserver.InternalInsetsInfo;
53 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
54 import android.view.WindowInsets;
55 import android.view.WindowManager;
56 import android.view.accessibility.AccessibilityNodeInfo;
57 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
58 import android.view.inputmethod.InputMethodManager;
59 import android.widget.FrameLayout;
61 import com.android.internal.annotations.VisibleForTesting;
62 import com.android.systemui.Dependency;
63 import com.android.systemui.DockedStackExistsListener;
64 import com.android.systemui.Interpolators;
65 import com.android.systemui.R;
66 import com.android.systemui.SysUiServiceProvider;
67 import com.android.systemui.assist.AssistManager;
68 import com.android.systemui.recents.OverviewProxyService;
69 import com.android.systemui.recents.Recents;
70 import com.android.systemui.recents.RecentsOnboarding;
71 import com.android.systemui.shared.system.ActivityManagerWrapper;
72 import com.android.systemui.shared.system.QuickStepContract;
73 import com.android.systemui.shared.system.WindowManagerWrapper;
74 import com.android.systemui.statusbar.policy.DeadZone;
75 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
77 import java.io.FileDescriptor;
78 import java.io.PrintWriter;
79 import java.util.function.Consumer;
81 public class NavigationBarView extends FrameLayout implements
82 NavigationModeController.ModeChangedListener {
83 final static boolean DEBUG = false;
84 final static String TAG = "StatusBar/NavBarView";
86 // slippery nav bar when everything is disabled, e.g. during setup
87 final static boolean SLIPPERY_WHEN_DISABLED = true;
89 final static boolean ALTERNATE_CAR_MODE_UI = false;
91 View mCurrentView = null;
92 private View mVertical;
93 private View mHorizontal;
95 /** Indicates that navigation bar is vertical. */
96 private boolean mIsVertical;
97 private int mCurrentRotation = -1;
99 boolean mLongClickableAccessibilityButton;
100 int mDisabledFlags = 0;
101 int mNavigationIconHints = 0;
102 private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
104 private Rect mHomeButtonBounds = new Rect();
105 private Rect mBackButtonBounds = new Rect();
106 private Rect mRecentsButtonBounds = new Rect();
107 private Rect mRotationButtonBounds = new Rect();
108 private final Region mActiveRegion = new Region();
109 private int[] mTmpPosition = new int[2];
111 private KeyButtonDrawable mBackIcon;
112 private KeyButtonDrawable mHomeDefaultIcon;
113 private KeyButtonDrawable mRecentIcon;
114 private KeyButtonDrawable mDockedIcon;
116 private final EdgeBackGestureHandler mEdgeBackGestureHandler;
117 private final DeadZone mDeadZone;
118 private boolean mDeadZoneConsuming = false;
119 private final NavigationBarTransitions mBarTransitions;
120 private final OverviewProxyService mOverviewProxyService;
122 // performs manual animation in sync with layout transitions
123 private final NavTransitionListener mTransitionListener = new NavTransitionListener();
125 private OnVerticalChangedListener mOnVerticalChangedListener;
126 private boolean mLayoutTransitionsEnabled = true;
127 private boolean mWakeAndUnlocking;
128 private boolean mUseCarModeUi = false;
129 private boolean mInCarMode = false;
130 private boolean mDockedStackExists;
131 private boolean mImeVisible;
133 private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
134 private final ContextualButtonGroup mContextualButtonGroup;
135 private Configuration mConfiguration;
136 private Configuration mTmpLastConfiguration;
138 private NavigationBarInflaterView mNavigationInflaterView;
139 private RecentsOnboarding mRecentsOnboarding;
140 private NotificationPanelView mPanelView;
141 private FloatingRotationButton mFloatingRotationButton;
142 private RotationButtonController mRotationButtonController;
144 private NavBarTintController mTintController;
147 * Helper that is responsible for showing the right toast when a disallowed activity operation
148 * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
149 * fully locked mode we only show that unlocking is blocked.
151 private ScreenPinningNotify mScreenPinningNotify;
153 private class NavTransitionListener implements TransitionListener {
154 private boolean mBackTransitioning;
155 private boolean mHomeAppearing;
156 private long mStartDelay;
157 private long mDuration;
158 private TimeInterpolator mInterpolator;
161 public void startTransition(LayoutTransition transition, ViewGroup container,
162 View view, int transitionType) {
163 if (view.getId() == R.id.back) {
164 mBackTransitioning = true;
165 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
166 mHomeAppearing = true;
167 mStartDelay = transition.getStartDelay(transitionType);
168 mDuration = transition.getDuration(transitionType);
169 mInterpolator = transition.getInterpolator(transitionType);
174 public void endTransition(LayoutTransition transition, ViewGroup container,
175 View view, int transitionType) {
176 if (view.getId() == R.id.back) {
177 mBackTransitioning = false;
178 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
179 mHomeAppearing = false;
183 public void onBackAltCleared() {
184 ButtonDispatcher backButton = getBackButton();
186 // When dismissing ime during unlock, force the back button to run the same appearance
187 // animation as home (if we catch this condition early enough).
188 if (!mBackTransitioning && backButton.getVisibility() == VISIBLE
189 && mHomeAppearing && getHomeButton().getAlpha() == 0) {
190 getBackButton().setAlpha(0);
191 ValueAnimator a = ObjectAnimator.ofFloat(backButton, "alpha", 0, 1);
192 a.setStartDelay(mStartDelay);
193 a.setDuration(mDuration);
194 a.setInterpolator(mInterpolator);
200 private final OnClickListener mImeSwitcherClickListener = new OnClickListener() {
202 public void onClick(View view) {
203 mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
204 true /* showAuxiliarySubtypes */, getContext().getDisplayId());
208 private final AccessibilityDelegate mQuickStepAccessibilityDelegate
209 = new AccessibilityDelegate() {
210 private AccessibilityAction mToggleOverviewAction;
213 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
214 super.onInitializeAccessibilityNodeInfo(host, info);
215 if (mToggleOverviewAction == null) {
216 mToggleOverviewAction = new AccessibilityAction(R.id.action_toggle_overview,
217 getContext().getString(R.string.quick_step_accessibility_toggle_overview));
219 info.addAction(mToggleOverviewAction);
223 public boolean performAccessibilityAction(View host, int action, Bundle args) {
224 if (action == R.id.action_toggle_overview) {
225 SysUiServiceProvider.getComponent(getContext(), Recents.class)
228 return super.performAccessibilityAction(host, action, args);
234 private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
235 // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
236 // gestural mode, the entire nav bar should be touchable.
237 if (!isGesturalMode(mNavBarMode) || mImeVisible) {
238 info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
242 info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
243 ButtonDispatcher imeSwitchButton = getImeSwitchButton();
244 if (imeSwitchButton.getVisibility() == VISIBLE) {
245 // If the IME is not up, but the ime switch button is visible, then make sure that
246 // button is touchable
247 int[] loc = new int[2];
248 View buttonView = imeSwitchButton.getCurrentView();
249 buttonView.getLocationInWindow(loc);
250 info.touchableRegion.set(loc[0], loc[1], loc[0] + buttonView.getWidth(),
251 loc[1] + buttonView.getHeight());
254 info.touchableRegion.setEmpty();
257 public NavigationBarView(Context context, AttributeSet attrs) {
258 super(context, attrs);
261 mLongClickableAccessibilityButton = false;
262 mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
263 boolean isGesturalMode = isGesturalMode(mNavBarMode);
265 // Set up the context group of buttons
266 mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
267 final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
268 R.drawable.ic_ime_switcher_default);
269 final RotationContextButton rotateSuggestionButton = new RotationContextButton(
270 R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
271 final ContextualButton accessibilityButton =
272 new ContextualButton(R.id.accessibility_button,
273 R.drawable.ic_sysbar_accessibility_button);
274 mContextualButtonGroup.addButton(imeSwitcherButton);
275 if (!isGesturalMode) {
276 mContextualButtonGroup.addButton(rotateSuggestionButton);
278 mContextualButtonGroup.addButton(accessibilityButton);
280 mOverviewProxyService = Dependency.get(OverviewProxyService.class);
281 mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
282 mFloatingRotationButton = new FloatingRotationButton(context);
283 mRotationButtonController = new RotationButtonController(context,
284 R.style.RotateButtonCCWStart90,
285 isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
287 final ContextualButton backButton = new ContextualButton(R.id.back, 0);
289 mConfiguration = new Configuration();
290 mTmpLastConfiguration = new Configuration();
291 mConfiguration.updateFrom(context.getResources().getConfiguration());
293 mScreenPinningNotify = new ScreenPinningNotify(mContext);
294 mBarTransitions = new NavigationBarTransitions(this);
296 mButtonDispatchers.put(R.id.back, backButton);
297 mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
298 mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
299 mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
300 mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
301 mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
302 mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
303 mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
304 mDeadZone = new DeadZone(this);
306 mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService);
307 mTintController = new NavBarTintController(this, getLightTransitionsController());
310 public NavBarTintController getTintController() {
311 return mTintController;
314 public NavigationBarTransitions getBarTransitions() {
315 return mBarTransitions;
318 public LightBarTransitionsController getLightTransitionsController() {
319 return mBarTransitions.getLightTransitionsController();
322 public void setComponents(NotificationPanelView panel, AssistManager assistManager) {
324 updateSystemUiStateFlags();
328 protected void dispatchDraw(Canvas canvas) {
329 super.dispatchDraw(canvas);
330 mTintController.onDraw();
333 public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
334 mOnVerticalChangedListener = onVerticalChangedListener;
335 notifyVerticalChangedListener(mIsVertical);
339 public boolean onInterceptTouchEvent(MotionEvent event) {
340 return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event);
344 public boolean onTouchEvent(MotionEvent event) {
345 shouldDeadZoneConsumeTouchEvents(event);
346 return super.onTouchEvent(event);
349 void onBarTransition(int newMode) {
350 if (newMode == MODE_OPAQUE) {
351 // If the nav bar background is opaque, stop auto tinting since we know the icons are
352 // showing over a dark background
353 mTintController.stop();
354 getLightTransitionsController().setIconsDark(false /* dark */, true /* animate */);
356 mTintController.start();
360 private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) {
361 int action = event.getActionMasked();
362 if (action == MotionEvent.ACTION_DOWN) {
363 mDeadZoneConsuming = false;
365 if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) {
367 case MotionEvent.ACTION_DOWN:
368 // Allow gestures starting in the deadzone to be slippery
370 mDeadZoneConsuming = true;
372 case MotionEvent.ACTION_CANCEL:
373 case MotionEvent.ACTION_UP:
374 // When a gesture started in the deadzone is finished, restore slippery state
376 mDeadZoneConsuming = false;
384 public void abortCurrentGesture() {
385 getHomeButton().abortCurrentGesture();
388 public View getCurrentView() {
392 public RotationButtonController getRotationButtonController() {
393 return mRotationButtonController;
396 public FloatingRotationButton getFloatingRotationButton() {
397 return mFloatingRotationButton;
400 public ButtonDispatcher getRecentsButton() {
401 return mButtonDispatchers.get(R.id.recent_apps);
404 public ButtonDispatcher getBackButton() {
405 return mButtonDispatchers.get(R.id.back);
408 public ButtonDispatcher getHomeButton() {
409 return mButtonDispatchers.get(R.id.home);
412 public ButtonDispatcher getImeSwitchButton() {
413 return mButtonDispatchers.get(R.id.ime_switcher);
416 public ButtonDispatcher getAccessibilityButton() {
417 return mButtonDispatchers.get(R.id.accessibility_button);
420 public RotationContextButton getRotateSuggestionButton() {
421 return (RotationContextButton) mButtonDispatchers.get(R.id.rotate_suggestion);
424 public ButtonDispatcher getHomeHandle() {
425 return mButtonDispatchers.get(R.id.home_handle);
428 public SparseArray<ButtonDispatcher> getButtonDispatchers() {
429 return mButtonDispatchers;
432 public boolean isRecentsButtonVisible() {
433 return getRecentsButton().getVisibility() == View.VISIBLE;
436 public boolean isOverviewEnabled() {
437 return (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) == 0;
440 public boolean isQuickStepSwipeUpEnabled() {
441 return mOverviewProxyService.shouldShowSwipeUpUI() && isOverviewEnabled();
444 private void reloadNavIcons() {
445 updateIcons(Configuration.EMPTY);
448 private void updateIcons(Configuration oldConfig) {
449 final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;
450 final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;
451 final boolean dirChange = oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
453 if (orientationChange || densityChange) {
454 mDockedIcon = getDrawable(R.drawable.ic_sysbar_docked);
455 mHomeDefaultIcon = getHomeDrawable();
457 if (densityChange || dirChange) {
458 mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);
459 mContextualButtonGroup.updateIcons();
461 if (orientationChange || densityChange || dirChange) {
462 mBackIcon = getBackDrawable();
466 public KeyButtonDrawable getBackDrawable() {
467 KeyButtonDrawable drawable = getDrawable(getBackDrawableRes());
468 orientBackButton(drawable);
472 public @DrawableRes int getBackDrawableRes() {
473 return chooseNavigationIconDrawableRes(R.drawable.ic_sysbar_back,
474 R.drawable.ic_sysbar_back_quick_step);
477 public KeyButtonDrawable getHomeDrawable() {
478 final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
479 KeyButtonDrawable drawable = quickStepEnabled
480 ? getDrawable(R.drawable.ic_sysbar_home_quick_step)
481 : getDrawable(R.drawable.ic_sysbar_home);
482 orientHomeButton(drawable);
486 private void orientBackButton(KeyButtonDrawable drawable) {
487 final boolean useAltBack =
488 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
489 final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
490 float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
491 if (drawable.getRotation() == degrees) {
495 if (isGesturalMode(mNavBarMode)) {
496 drawable.setRotation(degrees);
500 // Animate the back button's rotation to the new degrees and only in portrait move up the
501 // back button to line up with the other buttons
502 float targetY = !mOverviewProxyService.shouldShowSwipeUpUI() && !mIsVertical && useAltBack
503 ? - getResources().getDimension(R.dimen.navbar_back_button_ime_offset)
505 ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
506 PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees),
507 PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY));
508 navBarAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
509 navBarAnimator.setDuration(200);
510 navBarAnimator.start();
513 private void orientHomeButton(KeyButtonDrawable drawable) {
514 drawable.setRotation(mIsVertical ? 90 : 0);
517 private KeyButtonDrawable chooseNavigationIconDrawable(@DrawableRes int icon,
518 @DrawableRes int quickStepIcon) {
519 return getDrawable(chooseNavigationIconDrawableRes(icon, quickStepIcon));
522 private @DrawableRes int chooseNavigationIconDrawableRes(@DrawableRes int icon,
523 @DrawableRes int quickStepIcon) {
524 final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
525 return quickStepEnabled ? quickStepIcon : icon;
528 private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
529 return KeyButtonDrawable.create(mContext, icon, true /* hasShadow */);
532 private KeyButtonDrawable getDrawable(@DrawableRes int icon, boolean hasShadow) {
533 return KeyButtonDrawable.create(mContext, icon, hasShadow);
536 public void setWindowVisible(boolean visible) {
537 mTintController.setWindowVisible(visible);
538 mRotationButtonController.onNavigationBarWindowVisibilityChange(visible);
542 public void setLayoutDirection(int layoutDirection) {
545 super.setLayoutDirection(layoutDirection);
548 public void setNavigationIconHints(int hints) {
549 if (hints == mNavigationIconHints) return;
550 final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
551 final boolean oldBackAlt =
552 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
553 if (newBackAlt != oldBackAlt) {
554 onImeVisibilityChanged(newBackAlt);
558 android.widget.Toast.makeText(getContext(),
559 "Navigation icon hints = " + hints,
562 mNavigationIconHints = hints;
563 updateNavButtonIcons();
566 private void onImeVisibilityChanged(boolean visible) {
568 mTransitionListener.onBackAltCleared();
570 mImeVisible = visible;
571 mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible);
574 public void setDisabledFlags(int disabledFlags) {
575 if (mDisabledFlags == disabledFlags) return;
577 final boolean overviewEnabledBefore = isOverviewEnabled();
578 mDisabledFlags = disabledFlags;
580 // Update icons if overview was just enabled to ensure the correct icons are present
581 if (!overviewEnabledBefore && isOverviewEnabled()) {
585 updateNavButtonIcons();
587 setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
588 updateSystemUiStateFlags();
591 public void updateNavButtonIcons() {
592 // We have to replace or restore the back and home button icons when exiting or entering
593 // carmode, respectively. Recents are not available in CarMode in nav bar so change
594 // to recent icon is not required.
595 final boolean useAltBack =
596 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
597 KeyButtonDrawable backIcon = mBackIcon;
598 orientBackButton(backIcon);
599 KeyButtonDrawable homeIcon = mHomeDefaultIcon;
600 if (!mUseCarModeUi) {
601 orientHomeButton(homeIcon);
603 getHomeButton().setImageDrawable(homeIcon);
604 getBackButton().setImageDrawable(backIcon);
608 // Update IME button visibility, a11y and rotate button always overrides the appearance
609 mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher,
610 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
612 mBarTransitions.reapplyDarkIntensity();
614 boolean disableHome = isGesturalMode(mNavBarMode)
615 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
617 // Always disable recents when alternate car mode UI is active and for secondary displays.
618 boolean disableRecent = isRecentsButtonDisabled();
620 boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode)
621 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
623 // When screen pinning, don't hide back and home when connected service or back and
624 // recents buttons when disconnected from launcher service in screen pinning mode,
625 // as they are used for exiting.
626 final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
627 if (mOverviewProxyService.isEnabled()) {
628 // Force disable recents when not in legacy mode
629 disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode);
631 disableBack = disableHome = false;
633 } else if (pinningActive) {
634 disableBack = disableRecent = false;
637 ViewGroup navButtons = getCurrentView().findViewById(R.id.nav_buttons);
638 if (navButtons != null) {
639 LayoutTransition lt = navButtons.getLayoutTransition();
641 if (!lt.getTransitionListeners().contains(mTransitionListener)) {
642 lt.addTransitionListener(mTransitionListener);
647 getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
648 getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
649 getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
653 boolean isRecentsButtonDisabled() {
654 return mUseCarModeUi || !isOverviewEnabled()
655 || getContext().getDisplayId() != Display.DEFAULT_DISPLAY;
658 private Display getContextDisplay() {
659 return getContext().getDisplay();
662 public void setLayoutTransitionsEnabled(boolean enabled) {
663 mLayoutTransitionsEnabled = enabled;
664 updateLayoutTransitionsEnabled();
667 public void setWakeAndUnlocking(boolean wakeAndUnlocking) {
668 setUseFadingAnimations(wakeAndUnlocking);
669 mWakeAndUnlocking = wakeAndUnlocking;
670 updateLayoutTransitionsEnabled();
673 private void updateLayoutTransitionsEnabled() {
674 boolean enabled = !mWakeAndUnlocking && mLayoutTransitionsEnabled;
675 ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
676 LayoutTransition lt = navButtons.getLayoutTransition();
679 lt.enableTransitionType(LayoutTransition.APPEARING);
680 lt.enableTransitionType(LayoutTransition.DISAPPEARING);
681 lt.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
682 lt.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
684 lt.disableTransitionType(LayoutTransition.APPEARING);
685 lt.disableTransitionType(LayoutTransition.DISAPPEARING);
686 lt.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
687 lt.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
692 private void setUseFadingAnimations(boolean useFadingAnimations) {
693 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) ((ViewGroup) getParent())
696 boolean old = lp.windowAnimations != 0;
697 if (!old && useFadingAnimations) {
698 lp.windowAnimations = R.style.Animation_NavigationBarFadeIn;
699 } else if (old && !useFadingAnimations) {
700 lp.windowAnimations = 0;
704 WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
705 wm.updateViewLayout((View) getParent(), lp);
709 public void onPanelExpandedChange() {
711 updateSystemUiStateFlags();
714 public void updateSystemUiStateFlags() {
715 int displayId = mContext.getDisplayId();
716 mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_SCREEN_PINNING,
717 ActivityManagerWrapper.getInstance().isScreenPinningActive(), displayId);
718 mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_OVERVIEW_DISABLED,
719 (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0, displayId);
720 mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_HOME_DISABLED,
721 (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0, displayId);
722 if (mPanelView != null) {
723 mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
724 mPanelView.isFullyExpanded() && !mPanelView.isInSettings(), displayId);
728 public void updateStates() {
729 final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI();
731 if (mNavigationInflaterView != null) {
732 // Reinflate the navbar if needed, no-op unless the swipe up state changes
733 mNavigationInflaterView.onLikelyDefaultLayoutChange();
738 updateNavButtonIcons();
739 setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
740 WindowManagerWrapper.getInstance().setNavBarVirtualKeyHapticFeedbackEnabled(!showSwipeUpUI);
741 getHomeButton().setAccessibilityDelegate(
742 showSwipeUpUI ? mQuickStepAccessibilityDelegate : null);
746 * Updates the {@link WindowManager.LayoutParams.FLAG_SLIPPERY} state dependent on if swipe up
747 * is enabled, or the notifications is fully opened without being in an animated state. If
748 * slippery is enabled, touch events will leave the nav bar window and enter into the fullscreen
749 * app/home window, if not nav bar will receive a cancelled touch event once gesture leaves bar.
751 public void updateSlippery() {
752 setSlippery(!isQuickStepSwipeUpEnabled() ||
753 (mPanelView.isFullyExpanded() && !mPanelView.isCollapsing()));
756 private void setSlippery(boolean slippery) {
757 setWindowFlag(WindowManager.LayoutParams.FLAG_SLIPPERY, slippery);
760 private void setWindowFlag(int flags, boolean enable) {
761 final ViewGroup navbarView = ((ViewGroup) getParent());
762 if (navbarView == null) {
765 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView.getLayoutParams();
766 if (lp == null || enable == ((lp.flags & flags) != 0)) {
774 WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
775 wm.updateViewLayout(navbarView, lp);
779 public void onNavigationModeChanged(int mode) {
780 Context curUserCtx = Dependency.get(NavigationModeController.class).getCurrentUserContext();
782 mBarTransitions.onNavigationModeChanged(mNavBarMode);
783 mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode, curUserCtx);
784 mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
785 getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode);
787 // Color adaption is tied with showing home handle, only avaliable if visible
788 mTintController.onNavigationModeChanged(mNavBarMode);
789 if (isGesturalMode(mNavBarMode)) {
790 mTintController.start();
792 mTintController.stop();
796 public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
797 mLongClickableAccessibilityButton = longClickable;
798 getAccessibilityButton().setLongClickable(longClickable);
799 mContextualButtonGroup.setButtonVisibility(R.id.accessibility_button, visible);
802 void hideRecentsOnboarding() {
803 mRecentsOnboarding.hide(true);
807 public void onFinishInflate() {
808 mNavigationInflaterView = findViewById(R.id.navigation_inflater);
809 mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
811 getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
813 DockedStackExistsListener.register(mDockedListener);
814 updateOrientationViews();
819 protected void onDraw(Canvas canvas) {
820 mDeadZone.onDraw(canvas);
821 super.onDraw(canvas);
825 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
826 super.onLayout(changed, left, top, right, bottom);
828 mActiveRegion.setEmpty();
829 updateButtonLocation(getBackButton(), mBackButtonBounds, true);
830 updateButtonLocation(getHomeButton(), mHomeButtonBounds, false);
831 updateButtonLocation(getRecentsButton(), mRecentsButtonBounds, false);
832 updateButtonLocation(getRotateSuggestionButton(), mRotationButtonBounds, true);
833 // TODO: Handle button visibility changes
834 mOverviewProxyService.onActiveNavBarRegionChanges(mActiveRegion);
835 mRecentsOnboarding.setNavBarHeight(getMeasuredHeight());
838 private void updateButtonLocation(ButtonDispatcher button, Rect buttonBounds,
840 View view = button.getCurrentView();
842 buttonBounds.setEmpty();
845 // Temporarily reset the translation back to origin to get the position in window
846 final float posX = view.getTranslationX();
847 final float posY = view.getTranslationY();
848 view.setTranslationX(0);
849 view.setTranslationY(0);
852 view.getLocationOnScreen(mTmpPosition);
853 buttonBounds.set(mTmpPosition[0], mTmpPosition[1],
854 mTmpPosition[0] + view.getMeasuredWidth(),
855 mTmpPosition[1] + view.getMeasuredHeight());
856 mActiveRegion.op(buttonBounds, Op.UNION);
858 view.getLocationInWindow(mTmpPosition);
859 buttonBounds.set(mTmpPosition[0], mTmpPosition[1],
860 mTmpPosition[0] + view.getMeasuredWidth(),
861 mTmpPosition[1] + view.getMeasuredHeight());
862 view.setTranslationX(posX);
863 view.setTranslationY(posY);
866 private void updateOrientationViews() {
867 mHorizontal = findViewById(R.id.horizontal);
868 mVertical = findViewById(R.id.vertical);
873 boolean needsReorient(int rotation) {
874 return mCurrentRotation != rotation;
877 private void updateCurrentView() {
879 mCurrentView = mIsVertical ? mVertical : mHorizontal;
880 mCurrentView.setVisibility(View.VISIBLE);
881 mNavigationInflaterView.setVertical(mIsVertical);
882 mCurrentRotation = getContextDisplay().getRotation();
883 mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90);
884 mNavigationInflaterView.updateButtonDispatchersCurrentView();
885 updateLayoutTransitionsEnabled();
888 private void resetViews() {
889 mHorizontal.setVisibility(View.GONE);
890 mVertical.setVisibility(View.GONE);
893 private void updateRecentsIcon() {
894 mDockedIcon.setRotation(mDockedStackExists && mIsVertical ? 90 : 0);
895 getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
896 mBarTransitions.reapplyDarkIntensity();
899 public void showPinningEnterExitToast(boolean entering) {
901 mScreenPinningNotify.showPinningStartToast();
903 mScreenPinningNotify.showPinningExitToast();
907 public void showPinningEscapeToast() {
908 mScreenPinningNotify.showEscapeToast(isRecentsButtonVisible());
911 public boolean isVertical() {
915 public void reorient() {
918 ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
919 mDeadZone.onConfigurationChanged(mCurrentRotation);
921 // force the low profile & disabled states into compliance
922 mBarTransitions.init();
925 Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
928 // Resolve layout direction if not resolved since components changing layout direction such
929 // as changing languages will recreate this view and the direction will be resolved later
930 if (!isLayoutDirectionResolved()) {
931 resolveLayoutDirection();
933 updateNavButtonIcons();
935 getHomeButton().setVertical(mIsVertical);
939 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
940 int w = MeasureSpec.getSize(widthMeasureSpec);
941 int h = MeasureSpec.getSize(heightMeasureSpec);
942 if (DEBUG) Log.d(TAG, String.format(
943 "onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
945 final boolean newVertical = w > 0 && h > w
946 && !isGesturalMode(mNavBarMode);
947 if (newVertical != mIsVertical) {
948 mIsVertical = newVertical;
950 Log.d(TAG, String.format("onMeasure: h=%d, w=%d, vert=%s", h, w,
951 mIsVertical ? "y" : "n"));
954 notifyVerticalChangedListener(newVertical);
957 if (isGesturalMode(mNavBarMode)) {
958 // Update the nav bar background to match the height of the visible nav bar
959 int height = mIsVertical
960 ? getResources().getDimensionPixelSize(
961 com.android.internal.R.dimen.navigation_bar_height_landscape)
962 : getResources().getDimensionPixelSize(
963 com.android.internal.R.dimen.navigation_bar_height);
964 int frameHeight = getResources().getDimensionPixelSize(
965 com.android.internal.R.dimen.navigation_bar_frame_height);
966 mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
969 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
972 private void notifyVerticalChangedListener(boolean newVertical) {
973 if (mOnVerticalChangedListener != null) {
974 mOnVerticalChangedListener.onVerticalChanged(newVertical);
979 protected void onConfigurationChanged(Configuration newConfig) {
980 super.onConfigurationChanged(newConfig);
981 mTmpLastConfiguration.updateFrom(mConfiguration);
982 mConfiguration.updateFrom(newConfig);
983 boolean uiCarModeChanged = updateCarMode();
984 updateIcons(mTmpLastConfiguration);
986 mRecentsOnboarding.onConfigurationChanged(mConfiguration);
987 if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
988 || mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) {
989 // If car mode or density changes, we need to reset the icons.
990 updateNavButtonIcons();
995 * If the configuration changed, update the carmode and return that it was updated.
997 private boolean updateCarMode() {
998 boolean uiCarModeChanged = false;
999 if (mConfiguration != null) {
1000 int uiMode = mConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK;
1001 final boolean isCarMode = (uiMode == Configuration.UI_MODE_TYPE_CAR);
1003 if (isCarMode != mInCarMode) {
1004 mInCarMode = isCarMode;
1005 if (ALTERNATE_CAR_MODE_UI) {
1006 mUseCarModeUi = isCarMode;
1007 uiCarModeChanged = true;
1009 // Don't use car mode behavior if ALTERNATE_CAR_MODE_UI not set.
1010 mUseCarModeUi = false;
1014 return uiCarModeChanged;
1017 private String getResourceName(int resId) {
1019 final android.content.res.Resources res = getContext().getResources();
1021 return res.getResourceName(resId);
1022 } catch (android.content.res.Resources.NotFoundException ex) {
1030 private static String visibilityToString(int vis) {
1032 case View.INVISIBLE:
1042 protected void onAttachedToWindow() {
1043 super.onAttachedToWindow();
1044 requestApplyInsets();
1046 onNavigationModeChanged(mNavBarMode);
1047 setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
1048 if (mRotationButtonController != null) {
1049 mRotationButtonController.registerListeners();
1052 mEdgeBackGestureHandler.onNavBarAttached();
1053 getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
1057 protected void onDetachedFromWindow() {
1058 super.onDetachedFromWindow();
1059 Dependency.get(NavigationModeController.class).removeListener(this);
1060 setUpSwipeUpOnboarding(false);
1061 for (int i = 0; i < mButtonDispatchers.size(); ++i) {
1062 mButtonDispatchers.valueAt(i).onDestroy();
1064 if (mRotationButtonController != null) {
1065 mRotationButtonController.unregisterListeners();
1068 mEdgeBackGestureHandler.onNavBarDetached();
1069 getViewTreeObserver().removeOnComputeInternalInsetsListener(
1070 mOnComputeInternalInsetsListener);
1073 private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
1074 if (connectedToOverviewProxy) {
1075 mRecentsOnboarding.onConnectedToLauncher();
1077 mRecentsOnboarding.onDisconnectedFromLauncher();
1081 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1082 pw.println("NavigationBarView {");
1083 final Rect r = new Rect();
1084 final Point size = new Point();
1085 getContextDisplay().getRealSize(size);
1087 pw.println(String.format(" this: " + StatusBar.viewInfo(this)
1088 + " " + visibilityToString(getVisibility())));
1090 getWindowVisibleDisplayFrame(r);
1091 final boolean offscreen = r.right > size.x || r.bottom > size.y;
1092 pw.println(" window: "
1094 + " " + visibilityToString(getWindowVisibility())
1095 + (offscreen ? " OFFSCREEN!" : ""));
1097 pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s %f",
1098 getResourceName(getCurrentView().getId()),
1099 getCurrentView().getWidth(), getCurrentView().getHeight(),
1100 visibilityToString(getCurrentView().getVisibility()),
1101 getCurrentView().getAlpha()));
1103 pw.println(String.format(" disabled=0x%08x vertical=%s darkIntensity=%.2f",
1105 mIsVertical ? "true" : "false",
1106 getLightTransitionsController().getCurrentDarkIntensity()));
1108 dumpButton(pw, "back", getBackButton());
1109 dumpButton(pw, "home", getHomeButton());
1110 dumpButton(pw, "rcnt", getRecentsButton());
1111 dumpButton(pw, "rota", getRotateSuggestionButton());
1112 dumpButton(pw, "a11y", getAccessibilityButton());
1116 mContextualButtonGroup.dump(pw);
1117 mRecentsOnboarding.dump(pw);
1118 mTintController.dump(pw);
1119 mEdgeBackGestureHandler.dump(pw);
1123 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1124 int leftInset = insets.getSystemWindowInsetLeft();
1125 int rightInset = insets.getSystemWindowInsetRight();
1126 setPadding(leftInset, insets.getSystemWindowInsetTop(), rightInset,
1127 insets.getSystemWindowInsetBottom());
1128 // we're passing the insets onto the gesture handler since the back arrow is only
1129 // conditionally added and doesn't always get all the insets.
1130 mEdgeBackGestureHandler.setInsets(leftInset, rightInset);
1131 return super.onApplyWindowInsets(insets);
1134 private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
1135 pw.print(" " + caption + ": ");
1136 if (button == null) {
1139 pw.print(visibilityToString(button.getVisibility())
1140 + " alpha=" + button.getAlpha()
1146 public interface OnVerticalChangedListener {
1147 void onVerticalChanged(boolean isVertical);
1150 private final Consumer<Boolean> mDockedListener = exists -> post(() -> {
1151 mDockedStackExists = exists;
1152 updateRecentsIcon();