1 /*
2  * Copyright (C) 2008 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.statusbar.phone;
18 
19 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
20 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
21 
22 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
23 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
24 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED;
28 import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
29 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
30 
31 import android.animation.LayoutTransition;
32 import android.animation.LayoutTransition.TransitionListener;
33 import android.animation.ObjectAnimator;
34 import android.animation.PropertyValuesHolder;
35 import android.animation.TimeInterpolator;
36 import android.animation.ValueAnimator;
37 import android.annotation.DrawableRes;
38 import android.app.StatusBarManager;
39 import android.content.Context;
40 import android.content.res.Configuration;
41 import android.graphics.Canvas;
42 import android.graphics.Point;
43 import android.graphics.Rect;
44 import android.graphics.Region;
45 import android.graphics.Region.Op;
46 import android.os.Bundle;
47 import android.util.AttributeSet;
48 import android.util.Log;
49 import android.util.SparseArray;
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.WindowManager;
59 import android.view.accessibility.AccessibilityNodeInfo;
60 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
61 import android.view.inputmethod.InputMethodManager;
62 import android.widget.FrameLayout;
63 
64 import com.android.internal.annotations.VisibleForTesting;
65 import com.android.systemui.Dependency;
66 import com.android.systemui.DockedStackExistsListener;
67 import com.android.systemui.Interpolators;
68 import com.android.systemui.R;
69 import com.android.systemui.SysUiServiceProvider;
70 import com.android.systemui.assist.AssistManager;
71 import com.android.systemui.recents.OverviewProxyService;
72 import com.android.systemui.recents.Recents;
73 import com.android.systemui.recents.RecentsOnboarding;
74 import com.android.systemui.shared.system.ActivityManagerWrapper;
75 import com.android.systemui.shared.system.QuickStepContract;
76 import com.android.systemui.shared.system.WindowManagerWrapper;
77 import com.android.systemui.statusbar.policy.DeadZone;
78 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
79 
80 import java.io.FileDescriptor;
81 import java.io.PrintWriter;
82 import java.util.function.Consumer;
83 
84 public class NavigationBarView extends FrameLayout implements
85         NavigationModeController.ModeChangedListener {
86     final static boolean DEBUG = false;
87     final static String TAG = "StatusBar/NavBarView";
88 
89     // slippery nav bar when everything is disabled, e.g. during setup
90     final static boolean SLIPPERY_WHEN_DISABLED = true;
91 
92     final static boolean ALTERNATE_CAR_MODE_UI = false;
93 
94     View mCurrentView = null;
95     private View mVertical;
96     private View mHorizontal;
97 
98     /** Indicates that navigation bar is vertical. */
99     private boolean mIsVertical;
100     private int mCurrentRotation = -1;
101 
102     boolean mLongClickableAccessibilityButton;
103     int mDisabledFlags = 0;
104     int mNavigationIconHints = 0;
105     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
106 
107     private Rect mHomeButtonBounds = new Rect();
108     private Rect mBackButtonBounds = new Rect();
109     private Rect mRecentsButtonBounds = new Rect();
110     private Rect mRotationButtonBounds = new Rect();
111     private final Region mActiveRegion = new Region();
112     private int[] mTmpPosition = new int[2];
113 
114     private KeyButtonDrawable mBackIcon;
115     private KeyButtonDrawable mHomeDefaultIcon;
116     private KeyButtonDrawable mRecentIcon;
117     private KeyButtonDrawable mDockedIcon;
118 
119     private final EdgeBackGestureHandler mEdgeBackGestureHandler;
120     private final DeadZone mDeadZone;
121     private boolean mDeadZoneConsuming = false;
122     private final NavigationBarTransitions mBarTransitions;
123     private final OverviewProxyService mOverviewProxyService;
124 
125     // performs manual animation in sync with layout transitions
126     private final NavTransitionListener mTransitionListener = new NavTransitionListener();
127 
128     private OnVerticalChangedListener mOnVerticalChangedListener;
129     private boolean mLayoutTransitionsEnabled = true;
130     private boolean mWakeAndUnlocking;
131     private boolean mUseCarModeUi = false;
132     private boolean mInCarMode = false;
133     private boolean mDockedStackExists;
134     private boolean mImeVisible;
135 
136     private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
137     private final ContextualButtonGroup mContextualButtonGroup;
138     private Configuration mConfiguration;
139     private Configuration mTmpLastConfiguration;
140 
141     private NavigationBarInflaterView mNavigationInflaterView;
142     private RecentsOnboarding mRecentsOnboarding;
143     private NotificationPanelView mPanelView;
144     private FloatingRotationButton mFloatingRotationButton;
145     private RotationButtonController mRotationButtonController;
146 
147     private NavBarTintController mTintController;
148 
149     /**
150      * Helper that is responsible for showing the right toast when a disallowed activity operation
151      * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
152      * fully locked mode we only show that unlocking is blocked.
153      */
154     private ScreenPinningNotify mScreenPinningNotify;
155 
156     private class NavTransitionListener implements TransitionListener {
157         private boolean mBackTransitioning;
158         private boolean mHomeAppearing;
159         private long mStartDelay;
160         private long mDuration;
161         private TimeInterpolator mInterpolator;
162 
163         @Override
startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)164         public void startTransition(LayoutTransition transition, ViewGroup container,
165                 View view, int transitionType) {
166             if (view.getId() == R.id.back) {
167                 mBackTransitioning = true;
168             } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
169                 mHomeAppearing = true;
170                 mStartDelay = transition.getStartDelay(transitionType);
171                 mDuration = transition.getDuration(transitionType);
172                 mInterpolator = transition.getInterpolator(transitionType);
173             }
174         }
175 
176         @Override
endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)177         public void endTransition(LayoutTransition transition, ViewGroup container,
178                 View view, int transitionType) {
179             if (view.getId() == R.id.back) {
180                 mBackTransitioning = false;
181             } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
182                 mHomeAppearing = false;
183             }
184         }
185 
onBackAltCleared()186         public void onBackAltCleared() {
187             ButtonDispatcher backButton = getBackButton();
188 
189             // When dismissing ime during unlock, force the back button to run the same appearance
190             // animation as home (if we catch this condition early enough).
191             if (!mBackTransitioning && backButton.getVisibility() == VISIBLE
192                     && mHomeAppearing && getHomeButton().getAlpha() == 0) {
193                 getBackButton().setAlpha(0);
194                 ValueAnimator a = ObjectAnimator.ofFloat(backButton, "alpha", 0, 1);
195                 a.setStartDelay(mStartDelay);
196                 a.setDuration(mDuration);
197                 a.setInterpolator(mInterpolator);
198                 a.start();
199             }
200         }
201     }
202 
203     private final OnClickListener mImeSwitcherClickListener = new OnClickListener() {
204         @Override
205         public void onClick(View view) {
206             mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
207                     true /* showAuxiliarySubtypes */, getContext().getDisplayId());
208         }
209     };
210 
211     private final AccessibilityDelegate mQuickStepAccessibilityDelegate
212             = new AccessibilityDelegate() {
213         private AccessibilityAction mToggleOverviewAction;
214 
215         @Override
216         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
217             super.onInitializeAccessibilityNodeInfo(host, info);
218             if (mToggleOverviewAction == null) {
219                 mToggleOverviewAction = new AccessibilityAction(R.id.action_toggle_overview,
220                     getContext().getString(R.string.quick_step_accessibility_toggle_overview));
221             }
222             info.addAction(mToggleOverviewAction);
223         }
224 
225         @Override
226         public boolean performAccessibilityAction(View host, int action, Bundle args) {
227             if (action == R.id.action_toggle_overview) {
228                 SysUiServiceProvider.getComponent(getContext(), Recents.class)
229                         .toggleRecentApps();
230             } else {
231                 return super.performAccessibilityAction(host, action, args);
232             }
233             return true;
234         }
235     };
236 
237     private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
238         // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
239         // gestural mode, the entire nav bar should be touchable.
240         if (!isGesturalMode(mNavBarMode) || mImeVisible) {
241             info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
242             return;
243         }
244 
245         info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
246         ButtonDispatcher imeSwitchButton = getImeSwitchButton();
247         if (imeSwitchButton.getVisibility() == VISIBLE) {
248             // If the IME is not up, but the ime switch button is visible, then make sure that
249             // button is touchable
250             int[] loc = new int[2];
251             View buttonView = imeSwitchButton.getCurrentView();
252             buttonView.getLocationInWindow(loc);
253             info.touchableRegion.set(loc[0], loc[1], loc[0] + buttonView.getWidth(),
254                     loc[1] + buttonView.getHeight());
255             return;
256         }
257         info.touchableRegion.setEmpty();
258     };
259 
NavigationBarView(Context context, AttributeSet attrs)260     public NavigationBarView(Context context, AttributeSet attrs) {
261         super(context, attrs);
262 
263         mIsVertical = false;
264         mLongClickableAccessibilityButton = false;
265         mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
266         boolean isGesturalMode = isGesturalMode(mNavBarMode);
267 
268         // Set up the context group of buttons
269         mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
270         final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
271                 R.drawable.ic_ime_switcher_default);
272         final RotationContextButton rotateSuggestionButton = new RotationContextButton(
273                 R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
274         final ContextualButton accessibilityButton =
275                 new ContextualButton(R.id.accessibility_button,
276                         R.drawable.ic_sysbar_accessibility_button);
277         mContextualButtonGroup.addButton(imeSwitcherButton);
278         if (!isGesturalMode) {
279             mContextualButtonGroup.addButton(rotateSuggestionButton);
280         }
281         mContextualButtonGroup.addButton(accessibilityButton);
282 
283         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
284         mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
285         mFloatingRotationButton = new FloatingRotationButton(context);
286         mRotationButtonController = new RotationButtonController(context,
287                 R.style.RotateButtonCCWStart90,
288                 isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
289 
290         final ContextualButton backButton = new ContextualButton(R.id.back, 0);
291 
292         mConfiguration = new Configuration();
293         mTmpLastConfiguration = new Configuration();
294         mConfiguration.updateFrom(context.getResources().getConfiguration());
295 
296         mScreenPinningNotify = new ScreenPinningNotify(mContext);
297         mBarTransitions = new NavigationBarTransitions(this);
298 
299         mButtonDispatchers.put(R.id.back, backButton);
300         mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
301         mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
302         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
303         mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
304         mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
305         mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
306         mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
307         mDeadZone = new DeadZone(this);
308 
309         mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService);
310         mTintController = new NavBarTintController(this, getLightTransitionsController());
311     }
312 
getTintController()313     public NavBarTintController getTintController() {
314         return mTintController;
315     }
316 
getBarTransitions()317     public NavigationBarTransitions getBarTransitions() {
318         return mBarTransitions;
319     }
320 
getLightTransitionsController()321     public LightBarTransitionsController getLightTransitionsController() {
322         return mBarTransitions.getLightTransitionsController();
323     }
324 
setComponents(NotificationPanelView panel, AssistManager assistManager)325     public void setComponents(NotificationPanelView panel, AssistManager assistManager) {
326         mPanelView = panel;
327         updatePanelSystemUiStateFlags();
328     }
329 
330     @Override
dispatchDraw(Canvas canvas)331     protected void dispatchDraw(Canvas canvas) {
332         super.dispatchDraw(canvas);
333         mTintController.onDraw();
334     }
335 
setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener)336     public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
337         mOnVerticalChangedListener = onVerticalChangedListener;
338         notifyVerticalChangedListener(mIsVertical);
339     }
340 
341     @Override
onInterceptTouchEvent(MotionEvent event)342     public boolean onInterceptTouchEvent(MotionEvent event) {
343         return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event);
344     }
345 
346     @Override
onTouchEvent(MotionEvent event)347     public boolean onTouchEvent(MotionEvent event) {
348         shouldDeadZoneConsumeTouchEvents(event);
349         return super.onTouchEvent(event);
350     }
351 
onSystemUiVisibilityChanged(int systemUiVisibility)352     void onSystemUiVisibilityChanged(int systemUiVisibility) {
353         mEdgeBackGestureHandler.onSystemUiVisibilityChanged(systemUiVisibility);
354     }
355 
onBarTransition(int newMode)356     void onBarTransition(int newMode) {
357         if (newMode == MODE_OPAQUE) {
358             // If the nav bar background is opaque, stop auto tinting since we know the icons are
359             // showing over a dark background
360             mTintController.stop();
361             getLightTransitionsController().setIconsDark(false /* dark */, true /* animate */);
362         } else {
363             mTintController.start();
364         }
365     }
366 
shouldDeadZoneConsumeTouchEvents(MotionEvent event)367     private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) {
368         int action = event.getActionMasked();
369         if (action == MotionEvent.ACTION_DOWN) {
370             mDeadZoneConsuming = false;
371         }
372         if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) {
373             switch (action) {
374                 case MotionEvent.ACTION_DOWN:
375                     // Allow gestures starting in the deadzone to be slippery
376                     setSlippery(true);
377                     mDeadZoneConsuming = true;
378                     break;
379                 case MotionEvent.ACTION_CANCEL:
380                 case MotionEvent.ACTION_UP:
381                     // When a gesture started in the deadzone is finished, restore slippery state
382                     updateSlippery();
383                     mDeadZoneConsuming = false;
384                     break;
385             }
386             return true;
387         }
388         return false;
389     }
390 
abortCurrentGesture()391     public void abortCurrentGesture() {
392         getHomeButton().abortCurrentGesture();
393     }
394 
getCurrentView()395     public View getCurrentView() {
396         return mCurrentView;
397     }
398 
getRotationButtonController()399     public RotationButtonController getRotationButtonController() {
400         return mRotationButtonController;
401     }
402 
getFloatingRotationButton()403     public FloatingRotationButton getFloatingRotationButton() {
404         return mFloatingRotationButton;
405     }
406 
getRecentsButton()407     public ButtonDispatcher getRecentsButton() {
408         return mButtonDispatchers.get(R.id.recent_apps);
409     }
410 
getBackButton()411     public ButtonDispatcher getBackButton() {
412         return mButtonDispatchers.get(R.id.back);
413     }
414 
getHomeButton()415     public ButtonDispatcher getHomeButton() {
416         return mButtonDispatchers.get(R.id.home);
417     }
418 
getImeSwitchButton()419     public ButtonDispatcher getImeSwitchButton() {
420         return mButtonDispatchers.get(R.id.ime_switcher);
421     }
422 
getAccessibilityButton()423     public ButtonDispatcher getAccessibilityButton() {
424         return mButtonDispatchers.get(R.id.accessibility_button);
425     }
426 
getRotateSuggestionButton()427     public RotationContextButton getRotateSuggestionButton() {
428         return (RotationContextButton) mButtonDispatchers.get(R.id.rotate_suggestion);
429     }
430 
getHomeHandle()431     public ButtonDispatcher getHomeHandle() {
432         return mButtonDispatchers.get(R.id.home_handle);
433     }
434 
getButtonDispatchers()435     public SparseArray<ButtonDispatcher> getButtonDispatchers() {
436         return mButtonDispatchers;
437     }
438 
isRecentsButtonVisible()439     public boolean isRecentsButtonVisible() {
440         return getRecentsButton().getVisibility() == View.VISIBLE;
441     }
442 
isOverviewEnabled()443     public boolean isOverviewEnabled() {
444         return (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) == 0;
445     }
446 
isQuickStepSwipeUpEnabled()447     public boolean isQuickStepSwipeUpEnabled() {
448         return mOverviewProxyService.shouldShowSwipeUpUI() && isOverviewEnabled();
449     }
450 
reloadNavIcons()451     private void reloadNavIcons() {
452         updateIcons(Configuration.EMPTY);
453     }
454 
updateIcons(Configuration oldConfig)455     private void updateIcons(Configuration oldConfig) {
456         final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;
457         final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;
458         final boolean dirChange = oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
459 
460         if (orientationChange || densityChange) {
461             mDockedIcon = getDrawable(R.drawable.ic_sysbar_docked);
462             mHomeDefaultIcon = getHomeDrawable();
463         }
464         if (densityChange || dirChange) {
465             mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);
466             mContextualButtonGroup.updateIcons();
467         }
468         if (orientationChange || densityChange || dirChange) {
469             mBackIcon = getBackDrawable();
470         }
471     }
472 
getBackDrawable()473     public KeyButtonDrawable getBackDrawable() {
474         KeyButtonDrawable drawable = getDrawable(getBackDrawableRes());
475         orientBackButton(drawable);
476         return drawable;
477     }
478 
getBackDrawableRes()479     public @DrawableRes int getBackDrawableRes() {
480         return chooseNavigationIconDrawableRes(R.drawable.ic_sysbar_back,
481                 R.drawable.ic_sysbar_back_quick_step);
482     }
483 
getHomeDrawable()484     public KeyButtonDrawable getHomeDrawable() {
485         final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
486         KeyButtonDrawable drawable = quickStepEnabled
487                 ? getDrawable(R.drawable.ic_sysbar_home_quick_step)
488                 : getDrawable(R.drawable.ic_sysbar_home);
489         orientHomeButton(drawable);
490         return drawable;
491     }
492 
orientBackButton(KeyButtonDrawable drawable)493     private void orientBackButton(KeyButtonDrawable drawable) {
494         final boolean useAltBack =
495                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
496         final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
497         float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
498         if (drawable.getRotation() == degrees) {
499             return;
500         }
501 
502         if (isGesturalMode(mNavBarMode)) {
503             drawable.setRotation(degrees);
504             return;
505         }
506 
507         // Animate the back button's rotation to the new degrees and only in portrait move up the
508         // back button to line up with the other buttons
509         float targetY = !mOverviewProxyService.shouldShowSwipeUpUI() && !mIsVertical && useAltBack
510                 ? - getResources().getDimension(R.dimen.navbar_back_button_ime_offset)
511                 : 0;
512         ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
513                 PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees),
514                 PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY));
515         navBarAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
516         navBarAnimator.setDuration(200);
517         navBarAnimator.start();
518     }
519 
orientHomeButton(KeyButtonDrawable drawable)520     private void orientHomeButton(KeyButtonDrawable drawable) {
521         drawable.setRotation(mIsVertical ? 90 : 0);
522     }
523 
chooseNavigationIconDrawable(@rawableRes int icon, @DrawableRes int quickStepIcon)524     private KeyButtonDrawable chooseNavigationIconDrawable(@DrawableRes int icon,
525             @DrawableRes int quickStepIcon) {
526         return getDrawable(chooseNavigationIconDrawableRes(icon, quickStepIcon));
527     }
528 
chooseNavigationIconDrawableRes(@rawableRes int icon, @DrawableRes int quickStepIcon)529     private @DrawableRes int chooseNavigationIconDrawableRes(@DrawableRes int icon,
530             @DrawableRes int quickStepIcon) {
531         final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
532         return quickStepEnabled ? quickStepIcon : icon;
533     }
534 
getDrawable(@rawableRes int icon)535     private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
536         return KeyButtonDrawable.create(mContext, icon, true /* hasShadow */);
537     }
538 
getDrawable(@rawableRes int icon, boolean hasShadow)539     private KeyButtonDrawable getDrawable(@DrawableRes int icon, boolean hasShadow) {
540         return KeyButtonDrawable.create(mContext, icon, hasShadow);
541     }
542 
setWindowVisible(boolean visible)543     public void setWindowVisible(boolean visible) {
544         mTintController.setWindowVisible(visible);
545         mRotationButtonController.onNavigationBarWindowVisibilityChange(visible);
546     }
547 
548     @Override
setLayoutDirection(int layoutDirection)549     public void setLayoutDirection(int layoutDirection) {
550         reloadNavIcons();
551 
552         super.setLayoutDirection(layoutDirection);
553     }
554 
setNavigationIconHints(int hints)555     public void setNavigationIconHints(int hints) {
556         if (hints == mNavigationIconHints) return;
557         final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
558         final boolean oldBackAlt =
559                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
560         if (newBackAlt != oldBackAlt) {
561             onImeVisibilityChanged(newBackAlt);
562         }
563 
564         if (DEBUG) {
565             android.widget.Toast.makeText(getContext(),
566                 "Navigation icon hints = " + hints,
567                 500).show();
568         }
569         mNavigationIconHints = hints;
570         updateNavButtonIcons();
571     }
572 
onImeVisibilityChanged(boolean visible)573     private void onImeVisibilityChanged(boolean visible) {
574         if (!visible) {
575             mTransitionListener.onBackAltCleared();
576         }
577         mImeVisible = visible;
578         mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible);
579     }
580 
setDisabledFlags(int disabledFlags)581     public void setDisabledFlags(int disabledFlags) {
582         if (mDisabledFlags == disabledFlags) return;
583 
584         final boolean overviewEnabledBefore = isOverviewEnabled();
585         mDisabledFlags = disabledFlags;
586 
587         // Update icons if overview was just enabled to ensure the correct icons are present
588         if (!overviewEnabledBefore && isOverviewEnabled()) {
589             reloadNavIcons();
590         }
591 
592         updateNavButtonIcons();
593         updateSlippery();
594         setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
595         updateDisabledSystemUiStateFlags();
596     }
597 
updateNavButtonIcons()598     public void updateNavButtonIcons() {
599         // We have to replace or restore the back and home button icons when exiting or entering
600         // carmode, respectively. Recents are not available in CarMode in nav bar so change
601         // to recent icon is not required.
602         final boolean useAltBack =
603                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
604         KeyButtonDrawable backIcon = mBackIcon;
605         orientBackButton(backIcon);
606         KeyButtonDrawable homeIcon = mHomeDefaultIcon;
607         if (!mUseCarModeUi) {
608             orientHomeButton(homeIcon);
609         }
610         getHomeButton().setImageDrawable(homeIcon);
611         getBackButton().setImageDrawable(backIcon);
612 
613         updateRecentsIcon();
614 
615         // Update IME button visibility, a11y and rotate button always overrides the appearance
616         mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher,
617                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
618 
619         mBarTransitions.reapplyDarkIntensity();
620 
621         boolean disableHome = isGesturalMode(mNavBarMode)
622                 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
623 
624         // Always disable recents when alternate car mode UI is active and for secondary displays.
625         boolean disableRecent = isRecentsButtonDisabled();
626 
627         // Disable the home handle if both hone and recents are disabled
628         boolean disableHomeHandle = disableRecent
629                 && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
630 
631         boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode)
632                 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
633 
634         // When screen pinning, don't hide back and home when connected service or back and
635         // recents buttons when disconnected from launcher service in screen pinning mode,
636         // as they are used for exiting.
637         final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
638         if (mOverviewProxyService.isEnabled()) {
639             // Force disable recents when not in legacy mode
640             disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode);
641             if (pinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) {
642                 disableBack = disableHome = false;
643             }
644         } else if (pinningActive) {
645             disableBack = disableRecent = false;
646         }
647 
648         ViewGroup navButtons = getCurrentView().findViewById(R.id.nav_buttons);
649         if (navButtons != null) {
650             LayoutTransition lt = navButtons.getLayoutTransition();
651             if (lt != null) {
652                 if (!lt.getTransitionListeners().contains(mTransitionListener)) {
653                     lt.addTransitionListener(mTransitionListener);
654                 }
655             }
656         }
657 
658         getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
659         getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
660         getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
661         getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
662     }
663 
664     @VisibleForTesting
isRecentsButtonDisabled()665     boolean isRecentsButtonDisabled() {
666         return mUseCarModeUi || !isOverviewEnabled()
667                 || getContext().getDisplayId() != Display.DEFAULT_DISPLAY;
668     }
669 
getContextDisplay()670     private Display getContextDisplay() {
671         return getContext().getDisplay();
672     }
673 
setLayoutTransitionsEnabled(boolean enabled)674     public void setLayoutTransitionsEnabled(boolean enabled) {
675         mLayoutTransitionsEnabled = enabled;
676         updateLayoutTransitionsEnabled();
677     }
678 
setWakeAndUnlocking(boolean wakeAndUnlocking)679     public void setWakeAndUnlocking(boolean wakeAndUnlocking) {
680         setUseFadingAnimations(wakeAndUnlocking);
681         mWakeAndUnlocking = wakeAndUnlocking;
682         updateLayoutTransitionsEnabled();
683     }
684 
updateLayoutTransitionsEnabled()685     private void updateLayoutTransitionsEnabled() {
686         boolean enabled = !mWakeAndUnlocking && mLayoutTransitionsEnabled;
687         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
688         LayoutTransition lt = navButtons.getLayoutTransition();
689         if (lt != null) {
690             if (enabled) {
691                 lt.enableTransitionType(LayoutTransition.APPEARING);
692                 lt.enableTransitionType(LayoutTransition.DISAPPEARING);
693                 lt.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
694                 lt.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
695             } else {
696                 lt.disableTransitionType(LayoutTransition.APPEARING);
697                 lt.disableTransitionType(LayoutTransition.DISAPPEARING);
698                 lt.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
699                 lt.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
700             }
701         }
702     }
703 
setUseFadingAnimations(boolean useFadingAnimations)704     private void setUseFadingAnimations(boolean useFadingAnimations) {
705         WindowManager.LayoutParams lp = (WindowManager.LayoutParams) ((ViewGroup) getParent())
706                 .getLayoutParams();
707         if (lp != null) {
708             boolean old = lp.windowAnimations != 0;
709             if (!old && useFadingAnimations) {
710                 lp.windowAnimations = R.style.Animation_NavigationBarFadeIn;
711             } else if (old && !useFadingAnimations) {
712                 lp.windowAnimations = 0;
713             } else {
714                 return;
715             }
716             WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
717             wm.updateViewLayout((View) getParent(), lp);
718         }
719     }
720 
onStatusBarPanelStateChanged()721     public void onStatusBarPanelStateChanged() {
722         updateSlippery();
723         updatePanelSystemUiStateFlags();
724     }
725 
updateDisabledSystemUiStateFlags()726     public void updateDisabledSystemUiStateFlags() {
727         int displayId = mContext.getDisplayId();
728         mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_SCREEN_PINNING,
729                 ActivityManagerWrapper.getInstance().isScreenPinningActive(), displayId);
730         mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_OVERVIEW_DISABLED,
731                 (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0, displayId);
732         mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_HOME_DISABLED,
733                 (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0, displayId);
734         mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_SEARCH_DISABLED,
735                 (mDisabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0, displayId);
736     }
737 
updatePanelSystemUiStateFlags()738     public void updatePanelSystemUiStateFlags() {
739         int displayId = mContext.getDisplayId();
740         if (mPanelView != null) {
741             mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
742                     mPanelView.isFullyExpanded() && !mPanelView.isInSettings(), displayId);
743             mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
744                     mPanelView.isInSettings(), displayId);
745         }
746     }
747 
updateStates()748     public void updateStates() {
749         final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI();
750 
751         if (mNavigationInflaterView != null) {
752             // Reinflate the navbar if needed, no-op unless the swipe up state changes
753             mNavigationInflaterView.onLikelyDefaultLayoutChange();
754         }
755 
756         updateSlippery();
757         reloadNavIcons();
758         updateNavButtonIcons();
759         setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
760         WindowManagerWrapper.getInstance().setNavBarVirtualKeyHapticFeedbackEnabled(!showSwipeUpUI);
761         getHomeButton().setAccessibilityDelegate(
762                 showSwipeUpUI ? mQuickStepAccessibilityDelegate : null);
763     }
764 
765     /**
766      * Updates the {@link WindowManager.LayoutParams.FLAG_SLIPPERY} state dependent on if swipe up
767      * is enabled, or the notifications is fully opened without being in an animated state. If
768      * slippery is enabled, touch events will leave the nav bar window and enter into the fullscreen
769      * app/home window, if not nav bar will receive a cancelled touch event once gesture leaves bar.
770      */
updateSlippery()771     public void updateSlippery() {
772         setSlippery(!isQuickStepSwipeUpEnabled() ||
773                 (mPanelView.isFullyExpanded() && !mPanelView.isCollapsing()));
774     }
775 
setSlippery(boolean slippery)776     private void setSlippery(boolean slippery) {
777         setWindowFlag(WindowManager.LayoutParams.FLAG_SLIPPERY, slippery);
778     }
779 
setWindowFlag(int flags, boolean enable)780     private void setWindowFlag(int flags, boolean enable) {
781         final ViewGroup navbarView = ((ViewGroup) getParent());
782         if (navbarView == null) {
783             return;
784         }
785         WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView.getLayoutParams();
786         if (lp == null || enable == ((lp.flags & flags) != 0)) {
787             return;
788         }
789         if (enable) {
790             lp.flags |= flags;
791         } else {
792             lp.flags &= ~flags;
793         }
794         WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
795         wm.updateViewLayout(navbarView, lp);
796     }
797 
798     @Override
onNavigationModeChanged(int mode)799     public void onNavigationModeChanged(int mode) {
800         Context curUserCtx = Dependency.get(NavigationModeController.class).getCurrentUserContext();
801         mNavBarMode = mode;
802         mBarTransitions.onNavigationModeChanged(mNavBarMode);
803         mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode, curUserCtx);
804         mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
805         getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode);
806 
807         // Color adaption is tied with showing home handle, only avaliable if visible
808         mTintController.onNavigationModeChanged(mNavBarMode);
809         if (isGesturalMode(mNavBarMode)) {
810             mTintController.start();
811         } else {
812             mTintController.stop();
813         }
814     }
815 
setAccessibilityButtonState(final boolean visible, final boolean longClickable)816     public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
817         mLongClickableAccessibilityButton = longClickable;
818         getAccessibilityButton().setLongClickable(longClickable);
819         mContextualButtonGroup.setButtonVisibility(R.id.accessibility_button, visible);
820     }
821 
hideRecentsOnboarding()822     void hideRecentsOnboarding() {
823         mRecentsOnboarding.hide(true);
824     }
825 
826     @Override
onFinishInflate()827     public void onFinishInflate() {
828         mNavigationInflaterView = findViewById(R.id.navigation_inflater);
829         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
830 
831         getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
832 
833         DockedStackExistsListener.register(mDockedListener);
834         updateOrientationViews();
835         reloadNavIcons();
836     }
837 
838     @Override
onDraw(Canvas canvas)839     protected void onDraw(Canvas canvas) {
840         mDeadZone.onDraw(canvas);
841         super.onDraw(canvas);
842     }
843 
844     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)845     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
846         super.onLayout(changed, left, top, right, bottom);
847 
848         mActiveRegion.setEmpty();
849         updateButtonLocation(getBackButton(), mBackButtonBounds, true);
850         updateButtonLocation(getHomeButton(), mHomeButtonBounds, false);
851         updateButtonLocation(getRecentsButton(), mRecentsButtonBounds, false);
852         updateButtonLocation(getRotateSuggestionButton(), mRotationButtonBounds, true);
853         // TODO: Handle button visibility changes
854         mOverviewProxyService.onActiveNavBarRegionChanges(mActiveRegion);
855         mRecentsOnboarding.setNavBarHeight(getMeasuredHeight());
856     }
857 
updateButtonLocation(ButtonDispatcher button, Rect buttonBounds, boolean isActive)858     private void updateButtonLocation(ButtonDispatcher button, Rect buttonBounds,
859             boolean isActive) {
860         View view = button.getCurrentView();
861         if (view == null) {
862             buttonBounds.setEmpty();
863             return;
864         }
865         // Temporarily reset the translation back to origin to get the position in window
866         final float posX = view.getTranslationX();
867         final float posY = view.getTranslationY();
868         view.setTranslationX(0);
869         view.setTranslationY(0);
870 
871         if (isActive) {
872             view.getLocationOnScreen(mTmpPosition);
873             buttonBounds.set(mTmpPosition[0], mTmpPosition[1],
874                     mTmpPosition[0] + view.getMeasuredWidth(),
875                     mTmpPosition[1] + view.getMeasuredHeight());
876             mActiveRegion.op(buttonBounds, Op.UNION);
877         }
878         view.getLocationInWindow(mTmpPosition);
879         buttonBounds.set(mTmpPosition[0], mTmpPosition[1],
880                 mTmpPosition[0] + view.getMeasuredWidth(),
881                 mTmpPosition[1] + view.getMeasuredHeight());
882         view.setTranslationX(posX);
883         view.setTranslationY(posY);
884     }
885 
updateOrientationViews()886     private void updateOrientationViews() {
887         mHorizontal = findViewById(R.id.horizontal);
888         mVertical = findViewById(R.id.vertical);
889 
890         updateCurrentView();
891     }
892 
needsReorient(int rotation)893     boolean needsReorient(int rotation) {
894         return mCurrentRotation != rotation;
895     }
896 
updateCurrentView()897     private void updateCurrentView() {
898         resetViews();
899         mCurrentView = mIsVertical ? mVertical : mHorizontal;
900         mCurrentView.setVisibility(View.VISIBLE);
901         mNavigationInflaterView.setVertical(mIsVertical);
902         mCurrentRotation = getContextDisplay().getRotation();
903         mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90);
904         mNavigationInflaterView.updateButtonDispatchersCurrentView();
905         updateLayoutTransitionsEnabled();
906     }
907 
resetViews()908     private void resetViews() {
909         mHorizontal.setVisibility(View.GONE);
910         mVertical.setVisibility(View.GONE);
911     }
912 
updateRecentsIcon()913     private void updateRecentsIcon() {
914         mDockedIcon.setRotation(mDockedStackExists && mIsVertical ? 90 : 0);
915         getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
916         mBarTransitions.reapplyDarkIntensity();
917     }
918 
showPinningEnterExitToast(boolean entering)919     public void showPinningEnterExitToast(boolean entering) {
920         if (entering) {
921             mScreenPinningNotify.showPinningStartToast();
922         } else {
923             mScreenPinningNotify.showPinningExitToast();
924         }
925     }
926 
showPinningEscapeToast()927     public void showPinningEscapeToast() {
928         mScreenPinningNotify.showEscapeToast(
929                 mNavBarMode == NAV_BAR_MODE_GESTURAL, isRecentsButtonVisible());
930     }
931 
isVertical()932     public boolean isVertical() {
933         return mIsVertical;
934     }
935 
reorient()936     public void reorient() {
937         updateCurrentView();
938 
939         ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
940         mDeadZone.onConfigurationChanged(mCurrentRotation);
941 
942         // force the low profile & disabled states into compliance
943         mBarTransitions.init();
944 
945         if (DEBUG) {
946             Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
947         }
948 
949         // Resolve layout direction if not resolved since components changing layout direction such
950         // as changing languages will recreate this view and the direction will be resolved later
951         if (!isLayoutDirectionResolved()) {
952             resolveLayoutDirection();
953         }
954         updateNavButtonIcons();
955 
956         getHomeButton().setVertical(mIsVertical);
957     }
958 
959     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)960     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
961         int w = MeasureSpec.getSize(widthMeasureSpec);
962         int h = MeasureSpec.getSize(heightMeasureSpec);
963         if (DEBUG) Log.d(TAG, String.format(
964                 "onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
965 
966         final boolean newVertical = w > 0 && h > w
967                 && !isGesturalMode(mNavBarMode);
968         if (newVertical != mIsVertical) {
969             mIsVertical = newVertical;
970             if (DEBUG) {
971                 Log.d(TAG, String.format("onMeasure: h=%d, w=%d, vert=%s", h, w,
972                         mIsVertical ? "y" : "n"));
973             }
974             reorient();
975             notifyVerticalChangedListener(newVertical);
976         }
977 
978         if (isGesturalMode(mNavBarMode)) {
979             // Update the nav bar background to match the height of the visible nav bar
980             int height = mIsVertical
981                     ? getResources().getDimensionPixelSize(
982                             com.android.internal.R.dimen.navigation_bar_height_landscape)
983                     : getResources().getDimensionPixelSize(
984                             com.android.internal.R.dimen.navigation_bar_height);
985             int frameHeight = getResources().getDimensionPixelSize(
986                     com.android.internal.R.dimen.navigation_bar_frame_height);
987             mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
988         }
989 
990         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
991     }
992 
notifyVerticalChangedListener(boolean newVertical)993     private void notifyVerticalChangedListener(boolean newVertical) {
994         if (mOnVerticalChangedListener != null) {
995             mOnVerticalChangedListener.onVerticalChanged(newVertical);
996         }
997     }
998 
999     @Override
onConfigurationChanged(Configuration newConfig)1000     protected void onConfigurationChanged(Configuration newConfig) {
1001         super.onConfigurationChanged(newConfig);
1002         mTmpLastConfiguration.updateFrom(mConfiguration);
1003         mConfiguration.updateFrom(newConfig);
1004         boolean uiCarModeChanged = updateCarMode();
1005         updateIcons(mTmpLastConfiguration);
1006         updateRecentsIcon();
1007         mRecentsOnboarding.onConfigurationChanged(mConfiguration);
1008         if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
1009                 || mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) {
1010             // If car mode or density changes, we need to reset the icons.
1011             updateNavButtonIcons();
1012         }
1013     }
1014 
1015     /**
1016      * If the configuration changed, update the carmode and return that it was updated.
1017      */
updateCarMode()1018     private boolean updateCarMode() {
1019         boolean uiCarModeChanged = false;
1020         if (mConfiguration != null) {
1021             int uiMode = mConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK;
1022             final boolean isCarMode = (uiMode == Configuration.UI_MODE_TYPE_CAR);
1023 
1024             if (isCarMode != mInCarMode) {
1025                 mInCarMode = isCarMode;
1026                 if (ALTERNATE_CAR_MODE_UI) {
1027                     mUseCarModeUi = isCarMode;
1028                     uiCarModeChanged = true;
1029                 } else {
1030                     // Don't use car mode behavior if ALTERNATE_CAR_MODE_UI not set.
1031                     mUseCarModeUi = false;
1032                 }
1033             }
1034         }
1035         return uiCarModeChanged;
1036     }
1037 
getResourceName(int resId)1038     private String getResourceName(int resId) {
1039         if (resId != 0) {
1040             final android.content.res.Resources res = getContext().getResources();
1041             try {
1042                 return res.getResourceName(resId);
1043             } catch (android.content.res.Resources.NotFoundException ex) {
1044                 return "(unknown)";
1045             }
1046         } else {
1047             return "(null)";
1048         }
1049     }
1050 
visibilityToString(int vis)1051     private static String visibilityToString(int vis) {
1052         switch (vis) {
1053             case View.INVISIBLE:
1054                 return "INVISIBLE";
1055             case View.GONE:
1056                 return "GONE";
1057             default:
1058                 return "VISIBLE";
1059         }
1060     }
1061 
1062     @Override
onAttachedToWindow()1063     protected void onAttachedToWindow() {
1064         super.onAttachedToWindow();
1065         requestApplyInsets();
1066         reorient();
1067         onNavigationModeChanged(mNavBarMode);
1068         setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
1069         if (mRotationButtonController != null) {
1070             mRotationButtonController.registerListeners();
1071         }
1072 
1073         mEdgeBackGestureHandler.onNavBarAttached();
1074         getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
1075     }
1076 
1077     @Override
onDetachedFromWindow()1078     protected void onDetachedFromWindow() {
1079         super.onDetachedFromWindow();
1080         Dependency.get(NavigationModeController.class).removeListener(this);
1081         setUpSwipeUpOnboarding(false);
1082         for (int i = 0; i < mButtonDispatchers.size(); ++i) {
1083             mButtonDispatchers.valueAt(i).onDestroy();
1084         }
1085         if (mRotationButtonController != null) {
1086             mRotationButtonController.unregisterListeners();
1087         }
1088 
1089         mEdgeBackGestureHandler.onNavBarDetached();
1090         getViewTreeObserver().removeOnComputeInternalInsetsListener(
1091                 mOnComputeInternalInsetsListener);
1092     }
1093 
setUpSwipeUpOnboarding(boolean connectedToOverviewProxy)1094     private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
1095         if (connectedToOverviewProxy) {
1096             mRecentsOnboarding.onConnectedToLauncher();
1097         } else {
1098             mRecentsOnboarding.onDisconnectedFromLauncher();
1099         }
1100     }
1101 
dump(FileDescriptor fd, PrintWriter pw, String[] args)1102     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1103         pw.println("NavigationBarView {");
1104         final Rect r = new Rect();
1105         final Point size = new Point();
1106         getContextDisplay().getRealSize(size);
1107 
1108         pw.println(String.format("      this: " + StatusBar.viewInfo(this)
1109                         + " " + visibilityToString(getVisibility())));
1110 
1111         getWindowVisibleDisplayFrame(r);
1112         final boolean offscreen = r.right > size.x || r.bottom > size.y;
1113         pw.println("      window: "
1114                 + r.toShortString()
1115                 + " " + visibilityToString(getWindowVisibility())
1116                 + (offscreen ? " OFFSCREEN!" : ""));
1117 
1118         pw.println(String.format("      mCurrentView: id=%s (%dx%d) %s %f",
1119                         getResourceName(getCurrentView().getId()),
1120                         getCurrentView().getWidth(), getCurrentView().getHeight(),
1121                         visibilityToString(getCurrentView().getVisibility()),
1122                         getCurrentView().getAlpha()));
1123 
1124         pw.println(String.format("      disabled=0x%08x vertical=%s darkIntensity=%.2f",
1125                         mDisabledFlags,
1126                         mIsVertical ? "true" : "false",
1127                         getLightTransitionsController().getCurrentDarkIntensity()));
1128 
1129         dumpButton(pw, "back", getBackButton());
1130         dumpButton(pw, "home", getHomeButton());
1131         dumpButton(pw, "rcnt", getRecentsButton());
1132         dumpButton(pw, "rota", getRotateSuggestionButton());
1133         dumpButton(pw, "a11y", getAccessibilityButton());
1134 
1135         pw.println("    }");
1136 
1137         mContextualButtonGroup.dump(pw);
1138         mRecentsOnboarding.dump(pw);
1139         mTintController.dump(pw);
1140         mEdgeBackGestureHandler.dump(pw);
1141     }
1142 
1143     @Override
onApplyWindowInsets(WindowInsets insets)1144     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1145         int leftInset = insets.getSystemWindowInsetLeft();
1146         int rightInset = insets.getSystemWindowInsetRight();
1147         setPadding(leftInset, insets.getSystemWindowInsetTop(), rightInset,
1148                 insets.getSystemWindowInsetBottom());
1149         // we're passing the insets onto the gesture handler since the back arrow is only
1150         // conditionally added and doesn't always get all the insets.
1151         mEdgeBackGestureHandler.setInsets(leftInset, rightInset);
1152         return super.onApplyWindowInsets(insets);
1153     }
1154 
dumpButton(PrintWriter pw, String caption, ButtonDispatcher button)1155     private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
1156         pw.print("      " + caption + ": ");
1157         if (button == null) {
1158             pw.print("null");
1159         } else {
1160             pw.print(visibilityToString(button.getVisibility())
1161                     + " alpha=" + button.getAlpha()
1162                     );
1163         }
1164         pw.println();
1165     }
1166 
1167     public interface OnVerticalChangedListener {
onVerticalChanged(boolean isVertical)1168         void onVerticalChanged(boolean isVertical);
1169     }
1170 
1171     private final Consumer<Boolean> mDockedListener = exists -> post(() -> {
1172         mDockedStackExists = exists;
1173         updateRecentsIcon();
1174     });
1175 }
1176