1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.navigationbar;
18 
19 import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons;
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_OVERVIEW_DISABLED;
24 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED;
25 import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
26 
27 import android.animation.LayoutTransition;
28 import android.animation.LayoutTransition.TransitionListener;
29 import android.animation.ObjectAnimator;
30 import android.animation.PropertyValuesHolder;
31 import android.animation.TimeInterpolator;
32 import android.animation.ValueAnimator;
33 import android.annotation.DrawableRes;
34 import android.app.StatusBarManager;
35 import android.content.Context;
36 import android.content.res.Configuration;
37 import android.graphics.Canvas;
38 import android.graphics.Point;
39 import android.graphics.Rect;
40 import android.os.Bundle;
41 import android.os.RemoteException;
42 import android.util.AttributeSet;
43 import android.util.Log;
44 import android.util.SparseArray;
45 import android.view.ContextThemeWrapper;
46 import android.view.Display;
47 import android.view.MotionEvent;
48 import android.view.Surface;
49 import android.view.View;
50 import android.view.ViewGroup;
51 import android.view.WindowInsets;
52 import android.view.WindowInsetsController.Behavior;
53 import android.view.WindowManager;
54 import android.view.WindowManagerGlobal;
55 import android.view.accessibility.AccessibilityNodeInfo;
56 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
57 import android.widget.FrameLayout;
58 
59 import androidx.annotation.Nullable;
60 
61 import com.android.app.animation.Interpolators;
62 import com.android.internal.annotations.VisibleForTesting;
63 import com.android.settingslib.Utils;
64 import com.android.systemui.Gefingerpoken;
65 import com.android.systemui.R;
66 import com.android.systemui.model.SysUiState;
67 import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
68 import com.android.systemui.navigationbar.buttons.ContextualButton;
69 import com.android.systemui.navigationbar.buttons.ContextualButtonGroup;
70 import com.android.systemui.navigationbar.buttons.DeadZone;
71 import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
72 import com.android.systemui.navigationbar.buttons.NearestTouchFrame;
73 import com.android.systemui.navigationbar.buttons.RotationContextButton;
74 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
75 import com.android.systemui.recents.Recents;
76 import com.android.systemui.settings.DisplayTracker;
77 import com.android.systemui.shade.ShadeViewController;
78 import com.android.systemui.shared.rotation.FloatingRotationButton;
79 import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
80 import com.android.systemui.shared.rotation.RotationButtonController;
81 import com.android.systemui.shared.system.QuickStepContract;
82 import com.android.systemui.statusbar.phone.AutoHideController;
83 import com.android.systemui.statusbar.phone.CentralSurfaces;
84 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
85 import com.android.wm.shell.back.BackAnimation;
86 import com.android.wm.shell.pip.Pip;
87 
88 import java.io.PrintWriter;
89 import java.util.Map;
90 import java.util.Optional;
91 import java.util.concurrent.Executor;
92 import java.util.function.Consumer;
93 
94 /** */
95 public class NavigationBarView extends FrameLayout {
96     final static boolean DEBUG = false;
97     final static String TAG = "NavBarView";
98 
99     final static boolean ALTERNATE_CAR_MODE_UI = false;
100 
101     private Executor mBgExecutor;
102 
103     // The current view is one of mHorizontal or mVertical depending on the current configuration
104     View mCurrentView = null;
105     private View mVertical;
106     private View mHorizontal;
107 
108     /** Indicates that navigation bar is vertical. */
109     private boolean mIsVertical;
110     private int mCurrentRotation = -1;
111 
112     boolean mLongClickableAccessibilityButton;
113     int mDisabledFlags = 0;
114     int mNavigationIconHints = 0;
115     private int mNavBarMode;
116     private boolean mImeDrawsImeNavBar;
117 
118     private KeyButtonDrawable mBackIcon;
119     private KeyButtonDrawable mHomeDefaultIcon;
120     private KeyButtonDrawable mRecentIcon;
121     private KeyButtonDrawable mDockedIcon;
122     private Context mLightContext;
123     private int mLightIconColor;
124     private int mDarkIconColor;
125 
126     private EdgeBackGestureHandler mEdgeBackGestureHandler;
127     private DisplayTracker mDisplayTracker;
128     private final DeadZone mDeadZone;
129     private NavigationBarTransitions mBarTransitions;
130     @Nullable
131     private AutoHideController mAutoHideController;
132 
133     // performs manual animation in sync with layout transitions
134     private final NavTransitionListener mTransitionListener = new NavTransitionListener();
135 
136     private OnVerticalChangedListener mOnVerticalChangedListener;
137     private boolean mLayoutTransitionsEnabled = true;
138     private boolean mWakeAndUnlocking;
139     private boolean mUseCarModeUi = false;
140     private boolean mInCarMode = false;
141     private boolean mDockedStackExists;
142     private boolean mScreenOn = true;
143 
144     private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
145     private final ContextualButtonGroup mContextualButtonGroup;
146     private Configuration mConfiguration;
147     private Configuration mTmpLastConfiguration;
148 
149     private NavigationBarInflaterView mNavigationInflaterView;
150     private Optional<Recents> mRecentsOptional = Optional.empty();
151     @Nullable
152     private ShadeViewController mPanelView;
153     private RotationContextButton mRotationContextButton;
154     private FloatingRotationButton mFloatingRotationButton;
155     private RotationButtonController mRotationButtonController;
156 
157     /**
158      * Helper that is responsible for showing the right toast when a disallowed activity operation
159      * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
160      * fully locked mode we only show that unlocking is blocked.
161      */
162     private ScreenPinningNotify mScreenPinningNotify;
163     private boolean mScreenPinningActive = false;
164 
165     /**
166      * {@code true} if the IME can render the back button and the IME switcher button.
167      *
168      * <p>The value must be used when and only when
169      * {@link com.android.systemui.shared.system.QuickStepContract#isGesturalMode(int)} returns
170      * {@code true}</p>
171      *
172      * <p>Cache the value here for better performance.</p>
173      */
174     private final boolean mImeCanRenderGesturalNavButtons = canImeRenderGesturalNavButtons();
175     private Gefingerpoken mTouchHandler;
176     private boolean mOverviewProxyEnabled;
177     private boolean mShowSwipeUpUi;
178     private UpdateActiveTouchRegionsCallback mUpdateActiveTouchRegionsCallback;
179 
180     private class NavTransitionListener implements TransitionListener {
181         private boolean mBackTransitioning;
182         private boolean mHomeAppearing;
183         private long mStartDelay;
184         private long mDuration;
185         private TimeInterpolator mInterpolator;
186 
187         @Override
startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)188         public void startTransition(LayoutTransition transition, ViewGroup container,
189                 View view, int transitionType) {
190             if (view.getId() == R.id.back) {
191                 mBackTransitioning = true;
192             } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
193                 mHomeAppearing = true;
194                 mStartDelay = transition.getStartDelay(transitionType);
195                 mDuration = transition.getDuration(transitionType);
196                 mInterpolator = transition.getInterpolator(transitionType);
197             }
198         }
199 
200         @Override
endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)201         public void endTransition(LayoutTransition transition, ViewGroup container,
202                 View view, int transitionType) {
203             if (view.getId() == R.id.back) {
204                 mBackTransitioning = false;
205             } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
206                 mHomeAppearing = false;
207             }
208         }
209 
onBackAltCleared()210         public void onBackAltCleared() {
211             ButtonDispatcher backButton = getBackButton();
212 
213             // When dismissing ime during unlock, force the back button to run the same appearance
214             // animation as home (if we catch this condition early enough).
215             if (!mBackTransitioning && backButton.getVisibility() == VISIBLE
216                     && mHomeAppearing && getHomeButton().getAlpha() == 0) {
217                 getBackButton().setAlpha(0);
218                 ValueAnimator a = ObjectAnimator.ofFloat(backButton, "alpha", 0, 1);
219                 a.setStartDelay(mStartDelay);
220                 a.setDuration(mDuration);
221                 a.setInterpolator(mInterpolator);
222                 a.start();
223             }
224         }
225     }
226 
227     private final AccessibilityDelegate mQuickStepAccessibilityDelegate =
228             new AccessibilityDelegate() {
229                 private AccessibilityAction mToggleOverviewAction;
230 
231                 @Override
232                 public void onInitializeAccessibilityNodeInfo(View host,
233                         AccessibilityNodeInfo info) {
234                     super.onInitializeAccessibilityNodeInfo(host, info);
235                     if (mToggleOverviewAction == null) {
236                         mToggleOverviewAction = new AccessibilityAction(
237                                 R.id.action_toggle_overview, getContext().getString(
238                                 R.string.quick_step_accessibility_toggle_overview));
239                     }
240                     info.addAction(mToggleOverviewAction);
241                 }
242 
243                 @Override
244                 public boolean performAccessibilityAction(View host, int action, Bundle args) {
245                     if (action == R.id.action_toggle_overview) {
246                         mRecentsOptional.ifPresent(Recents::toggleRecentApps);
247                     } else {
248                         return super.performAccessibilityAction(host, action, args);
249                     }
250                     return true;
251                 }
252             };
253 
254     private final RotationButtonUpdatesCallback mRotationButtonListener =
255             new RotationButtonUpdatesCallback() {
256                 @Override
257                 public void onVisibilityChanged(boolean visible) {
258                     if (visible && mAutoHideController != null) {
259                         // If the button will actually become visible and the navbar is about
260                         // to hide, tell the statusbar to keep it around for longer
261                         mAutoHideController.touchAutoHide();
262                     }
263                     notifyActiveTouchRegions();
264                 }
265 
266                 @Override
267                 public void onPositionChanged() {
268                     notifyActiveTouchRegions();
269                 }
270             };
271 
NavigationBarView(Context context, AttributeSet attrs)272     public NavigationBarView(Context context, AttributeSet attrs) {
273         super(context, attrs);
274 
275         final Context darkContext = new ContextThemeWrapper(context,
276                 Utils.getThemeAttr(context, R.attr.darkIconTheme));
277         mLightContext = new ContextThemeWrapper(context,
278                 Utils.getThemeAttr(context, R.attr.lightIconTheme));
279         mLightIconColor = Utils.getColorAttrDefaultColor(mLightContext, R.attr.singleToneColor);
280         mDarkIconColor = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor);
281         mIsVertical = false;
282         mLongClickableAccessibilityButton = false;
283 
284         // Set up the context group of buttons
285         mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
286         final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
287                 mLightContext, R.drawable.ic_ime_switcher_default);
288         final ContextualButton accessibilityButton =
289                 new ContextualButton(R.id.accessibility_button, mLightContext,
290                         R.drawable.ic_sysbar_accessibility_button);
291         mContextualButtonGroup.addButton(imeSwitcherButton);
292         mContextualButtonGroup.addButton(accessibilityButton);
293         mRotationContextButton = new RotationContextButton(R.id.rotate_suggestion,
294                 mLightContext, R.drawable.ic_sysbar_rotate_button_ccw_start_0);
295         mFloatingRotationButton = new FloatingRotationButton(mContext,
296                 R.string.accessibility_rotate_button,
297                 R.layout.rotate_suggestion,
298                 R.id.rotate_suggestion,
299                 R.dimen.floating_rotation_button_min_margin,
300                 R.dimen.rounded_corner_content_padding,
301                 R.dimen.floating_rotation_button_taskbar_left_margin,
302                 R.dimen.floating_rotation_button_taskbar_bottom_margin,
303                 R.dimen.floating_rotation_button_diameter,
304                 R.dimen.key_button_ripple_max_width,
305                 R.bool.floating_rotation_button_position_left);
306         mRotationButtonController = new RotationButtonController(mLightContext, mLightIconColor,
307                 mDarkIconColor, R.drawable.ic_sysbar_rotate_button_ccw_start_0,
308                 R.drawable.ic_sysbar_rotate_button_ccw_start_90,
309                 R.drawable.ic_sysbar_rotate_button_cw_start_0,
310                 R.drawable.ic_sysbar_rotate_button_cw_start_90,
311                 () -> mCurrentRotation);
312 
313         mConfiguration = new Configuration();
314         mTmpLastConfiguration = new Configuration();
315         mConfiguration.updateFrom(context.getResources().getConfiguration());
316 
317         mScreenPinningNotify = new ScreenPinningNotify(mContext);
318 
319         mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
320         mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
321         mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
322         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
323         mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
324         mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
325         mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
326         mDeadZone = new DeadZone(this);
327     }
328 
setEdgeBackGestureHandler(EdgeBackGestureHandler edgeBackGestureHandler)329     public void setEdgeBackGestureHandler(EdgeBackGestureHandler edgeBackGestureHandler) {
330         mEdgeBackGestureHandler = edgeBackGestureHandler;
331     }
332 
setBarTransitions(NavigationBarTransitions navigationBarTransitions)333     void setBarTransitions(NavigationBarTransitions navigationBarTransitions) {
334         mBarTransitions = navigationBarTransitions;
335     }
336 
setAutoHideController(AutoHideController autoHideController)337     public void setAutoHideController(AutoHideController autoHideController) {
338         mAutoHideController = autoHideController;
339     }
340 
getLightTransitionsController()341     public LightBarTransitionsController getLightTransitionsController() {
342         return mBarTransitions.getLightTransitionsController();
343     }
344 
setComponents(Optional<Recents> recentsOptional)345     public void setComponents(Optional<Recents> recentsOptional) {
346         mRecentsOptional = recentsOptional;
347     }
348 
349     /** */
setComponents(ShadeViewController panel)350     public void setComponents(ShadeViewController panel) {
351         mPanelView = panel;
352         updatePanelSystemUiStateFlags();
353     }
354 
setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener)355     public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
356         mOnVerticalChangedListener = onVerticalChangedListener;
357         notifyVerticalChangedListener(mIsVertical);
358     }
359 
setBackgroundExecutor(Executor bgExecutor)360     public void setBackgroundExecutor(Executor bgExecutor) {
361         mBgExecutor = bgExecutor;
362         mRotationButtonController.setBgExecutor(bgExecutor);
363     }
364 
setDisplayTracker(DisplayTracker displayTracker)365     public void setDisplayTracker(DisplayTracker displayTracker) {
366         mDisplayTracker = displayTracker;
367     }
368 
setTouchHandler(Gefingerpoken touchHandler)369     public void setTouchHandler(Gefingerpoken touchHandler) {
370         mTouchHandler = touchHandler;
371     }
372 
373     @Override
onInterceptTouchEvent(MotionEvent event)374     public boolean onInterceptTouchEvent(MotionEvent event) {
375         return mTouchHandler.onInterceptTouchEvent(event) || super.onInterceptTouchEvent(event);
376     }
377 
378     @Override
onTouchEvent(MotionEvent event)379     public boolean onTouchEvent(MotionEvent event) {
380         mTouchHandler.onTouchEvent(event);
381         return super.onTouchEvent(event);
382     }
383 
abortCurrentGesture()384     public void abortCurrentGesture() {
385         getHomeButton().abortCurrentGesture();
386     }
387 
getCurrentView()388     public View getCurrentView() {
389         return mCurrentView;
390     }
391 
392     /**
393      * Applies {@param consumer} to each of the nav bar views.
394      */
forEachView(Consumer<View> consumer)395     public void forEachView(Consumer<View> consumer) {
396         if (mVertical != null) {
397             consumer.accept(mVertical);
398         }
399         if (mHorizontal != null) {
400             consumer.accept(mHorizontal);
401         }
402     }
403 
getRotationButtonController()404     public RotationButtonController getRotationButtonController() {
405         return mRotationButtonController;
406     }
407 
getFloatingRotationButton()408     public FloatingRotationButton getFloatingRotationButton() {
409         return mFloatingRotationButton;
410     }
411 
getRecentsButton()412     public ButtonDispatcher getRecentsButton() {
413         return mButtonDispatchers.get(R.id.recent_apps);
414     }
415 
getBackButton()416     public ButtonDispatcher getBackButton() {
417         return mButtonDispatchers.get(R.id.back);
418     }
419 
getHomeButton()420     public ButtonDispatcher getHomeButton() {
421         return mButtonDispatchers.get(R.id.home);
422     }
423 
getImeSwitchButton()424     public ButtonDispatcher getImeSwitchButton() {
425         return mButtonDispatchers.get(R.id.ime_switcher);
426     }
427 
getAccessibilityButton()428     public ButtonDispatcher getAccessibilityButton() {
429         return mButtonDispatchers.get(R.id.accessibility_button);
430     }
431 
getRotateSuggestionButton()432     public RotationContextButton getRotateSuggestionButton() {
433         return (RotationContextButton) mButtonDispatchers.get(R.id.rotate_suggestion);
434     }
435 
getHomeHandle()436     public ButtonDispatcher getHomeHandle() {
437         return mButtonDispatchers.get(R.id.home_handle);
438     }
439 
getButtonDispatchers()440     public SparseArray<ButtonDispatcher> getButtonDispatchers() {
441         return mButtonDispatchers;
442     }
443 
isRecentsButtonVisible()444     public boolean isRecentsButtonVisible() {
445         return getRecentsButton().getVisibility() == View.VISIBLE;
446     }
447 
isOverviewEnabled()448     public boolean isOverviewEnabled() {
449         return (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) == 0;
450     }
451 
isQuickStepSwipeUpEnabled()452     private boolean isQuickStepSwipeUpEnabled() {
453         return mShowSwipeUpUi && isOverviewEnabled();
454     }
455 
reloadNavIcons()456     private void reloadNavIcons() {
457         updateIcons(Configuration.EMPTY);
458     }
459 
updateIcons(Configuration oldConfig)460     private void updateIcons(Configuration oldConfig) {
461         final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;
462         final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;
463         final boolean dirChange = oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
464 
465         if (orientationChange || densityChange) {
466             mDockedIcon = getDrawable(R.drawable.ic_sysbar_docked);
467             mHomeDefaultIcon = getHomeDrawable();
468         }
469         if (densityChange || dirChange) {
470             mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);
471             mContextualButtonGroup.updateIcons(mLightIconColor, mDarkIconColor);
472         }
473         if (orientationChange || densityChange || dirChange) {
474             mBackIcon = getBackDrawable();
475         }
476     }
477 
478     /**
479      * Updates the rotation button based on the current navigation mode.
480      */
updateRotationButton()481     void updateRotationButton() {
482         if (isGesturalMode(mNavBarMode)) {
483             mContextualButtonGroup.removeButton(R.id.rotate_suggestion);
484             mButtonDispatchers.remove(R.id.rotate_suggestion);
485             mRotationButtonController.setRotationButton(mFloatingRotationButton,
486                     mRotationButtonListener);
487         } else if (mContextualButtonGroup.getContextButton(R.id.rotate_suggestion) == null) {
488             mContextualButtonGroup.addButton(mRotationContextButton);
489             mButtonDispatchers.put(R.id.rotate_suggestion, mRotationContextButton);
490             mRotationButtonController.setRotationButton(mRotationContextButton,
491                     mRotationButtonListener);
492         }
493         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
494     }
495 
getBackDrawable()496     public KeyButtonDrawable getBackDrawable() {
497         KeyButtonDrawable drawable = getDrawable(getBackDrawableRes());
498         orientBackButton(drawable);
499         return drawable;
500     }
501 
getBackDrawableRes()502     public @DrawableRes int getBackDrawableRes() {
503         return chooseNavigationIconDrawableRes(R.drawable.ic_sysbar_back,
504                 R.drawable.ic_sysbar_back_quick_step);
505     }
506 
getHomeDrawable()507     public KeyButtonDrawable getHomeDrawable() {
508         KeyButtonDrawable drawable = mShowSwipeUpUi
509                 ? getDrawable(R.drawable.ic_sysbar_home_quick_step)
510                 : getDrawable(R.drawable.ic_sysbar_home);
511         orientHomeButton(drawable);
512         return drawable;
513     }
514 
orientBackButton(KeyButtonDrawable drawable)515     private void orientBackButton(KeyButtonDrawable drawable) {
516         final boolean useAltBack =
517                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
518         final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
519         float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
520         if (drawable.getRotation() == degrees) {
521             return;
522         }
523 
524         if (isGesturalMode(mNavBarMode)) {
525             drawable.setRotation(degrees);
526             return;
527         }
528 
529         // Animate the back button's rotation to the new degrees and only in portrait move up the
530         // back button to line up with the other buttons
531         float targetY = !mShowSwipeUpUi && !mIsVertical && useAltBack
532                 ? - getResources().getDimension(R.dimen.navbar_back_button_ime_offset)
533                 : 0;
534         ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
535                 PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees),
536                 PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY));
537         navBarAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
538         navBarAnimator.setDuration(200);
539         navBarAnimator.start();
540     }
541 
orientHomeButton(KeyButtonDrawable drawable)542     private void orientHomeButton(KeyButtonDrawable drawable) {
543         drawable.setRotation(mIsVertical ? 90 : 0);
544     }
545 
chooseNavigationIconDrawableRes(@rawableRes int icon, @DrawableRes int quickStepIcon)546     private @DrawableRes int chooseNavigationIconDrawableRes(@DrawableRes int icon,
547             @DrawableRes int quickStepIcon) {
548         return mShowSwipeUpUi ? quickStepIcon : icon;
549     }
550 
getDrawable(@rawableRes int icon)551     private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
552         return KeyButtonDrawable.create(mLightContext, mLightIconColor, mDarkIconColor, icon,
553                 true /* hasShadow */, null /* ovalBackgroundColor */);
554     }
555 
556     /** To be called when screen lock/unlock state changes */
onScreenStateChanged(boolean isScreenOn)557     public void onScreenStateChanged(boolean isScreenOn) {
558         mScreenOn = isScreenOn;
559     }
560 
setWindowVisible(boolean visible)561     public void setWindowVisible(boolean visible) {
562         mRotationButtonController.onNavigationBarWindowVisibilityChange(visible);
563     }
564 
setBehavior(@ehavior int behavior)565     public void setBehavior(@Behavior int behavior) {
566         mRotationButtonController.onBehaviorChanged(mDisplayTracker.getDefaultDisplayId(),
567                 behavior);
568     }
569 
570     @Override
setLayoutDirection(int layoutDirection)571     public void setLayoutDirection(int layoutDirection) {
572         reloadNavIcons();
573 
574         super.setLayoutDirection(layoutDirection);
575     }
576 
setNavigationIconHints(int hints)577     void setNavigationIconHints(int hints) {
578         if (hints == mNavigationIconHints) return;
579         mNavigationIconHints = hints;
580         updateNavButtonIcons();
581     }
582 
onImeVisibilityChanged(boolean visible)583     void onImeVisibilityChanged(boolean visible) {
584         if (!visible) {
585             mTransitionListener.onBackAltCleared();
586         }
587     }
588 
setDisabledFlags(int disabledFlags, SysUiState sysUiState)589     void setDisabledFlags(int disabledFlags, SysUiState sysUiState) {
590         if (mDisabledFlags == disabledFlags) return;
591 
592         final boolean overviewEnabledBefore = isOverviewEnabled();
593         mDisabledFlags = disabledFlags;
594 
595         // Update icons if overview was just enabled to ensure the correct icons are present
596         if (!overviewEnabledBefore && isOverviewEnabled()) {
597             reloadNavIcons();
598         }
599 
600         updateNavButtonIcons();
601         updateSlippery();
602         updateDisabledSystemUiStateFlags(sysUiState);
603     }
604 
updateNavButtonIcons()605     public void updateNavButtonIcons() {
606         // We have to replace or restore the back and home button icons when exiting or entering
607         // carmode, respectively. Recents are not available in CarMode in nav bar so change
608         // to recent icon is not required.
609         final boolean useAltBack =
610                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
611         KeyButtonDrawable backIcon = mBackIcon;
612         orientBackButton(backIcon);
613         KeyButtonDrawable homeIcon = mHomeDefaultIcon;
614         if (!mUseCarModeUi) {
615             orientHomeButton(homeIcon);
616         }
617         getHomeButton().setImageDrawable(homeIcon);
618         getBackButton().setImageDrawable(backIcon);
619 
620         updateRecentsIcon();
621 
622         // Update IME button visibility, a11y and rotate button always overrides the appearance
623         boolean disableImeSwitcher =
624                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) == 0
625                 || isImeRenderingNavButtons();
626         mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, !disableImeSwitcher);
627 
628         mBarTransitions.reapplyDarkIntensity();
629 
630         boolean disableHome = isGesturalMode(mNavBarMode)
631                 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
632 
633         // Always disable recents when alternate car mode UI is active and for secondary displays.
634         boolean disableRecent = isRecentsButtonDisabled();
635 
636         // Disable the home handle if both hone and recents are disabled
637         boolean disableHomeHandle = disableRecent
638                 && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
639 
640         boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
641                 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0))
642                 || isImeRenderingNavButtons();
643 
644         // When screen pinning, don't hide back and home when connected service or back and
645         // recents buttons when disconnected from launcher service in screen pinning mode,
646         // as they are used for exiting.
647         if (mOverviewProxyEnabled) {
648             // Force disable recents when not in legacy mode
649             disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode);
650             if (mScreenPinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) {
651                 disableBack = disableHome = false;
652             }
653         } else if (mScreenPinningActive) {
654             disableBack = disableRecent = false;
655         }
656 
657         ViewGroup navButtons = getCurrentView().findViewById(R.id.nav_buttons);
658         if (navButtons != null) {
659             LayoutTransition lt = navButtons.getLayoutTransition();
660             if (lt != null) {
661                 if (!lt.getTransitionListeners().contains(mTransitionListener)) {
662                     lt.addTransitionListener(mTransitionListener);
663                 }
664             }
665         }
666 
667         getBackButton().setVisibility(disableBack       ? View.INVISIBLE : View.VISIBLE);
668         getHomeButton().setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
669         getRecentsButton().setVisibility(disableRecent  ? View.INVISIBLE : View.VISIBLE);
670         getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
671         notifyActiveTouchRegions();
672     }
673 
674     /**
675      * Returns whether the IME is currently visible and drawing the nav buttons.
676      */
isImeRenderingNavButtons()677     boolean isImeRenderingNavButtons() {
678         return mImeDrawsImeNavBar
679                 && mImeCanRenderGesturalNavButtons
680                 && (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
681     }
682 
683     @VisibleForTesting
isRecentsButtonDisabled()684     boolean isRecentsButtonDisabled() {
685         return mUseCarModeUi || !isOverviewEnabled()
686                 || getContext().getDisplayId() != mDisplayTracker.getDefaultDisplayId();
687     }
688 
getContextDisplay()689     private Display getContextDisplay() {
690         return getContext().getDisplay();
691     }
692 
setLayoutTransitionsEnabled(boolean enabled)693     public void setLayoutTransitionsEnabled(boolean enabled) {
694         mLayoutTransitionsEnabled = enabled;
695         updateLayoutTransitionsEnabled();
696     }
697 
setWakeAndUnlocking(boolean wakeAndUnlocking)698     public void setWakeAndUnlocking(boolean wakeAndUnlocking) {
699         setUseFadingAnimations(wakeAndUnlocking);
700         mWakeAndUnlocking = wakeAndUnlocking;
701         updateLayoutTransitionsEnabled();
702     }
703 
updateLayoutTransitionsEnabled()704     private void updateLayoutTransitionsEnabled() {
705         boolean enabled = !mWakeAndUnlocking && mLayoutTransitionsEnabled;
706         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
707         LayoutTransition lt = navButtons.getLayoutTransition();
708         if (lt != null) {
709             if (enabled) {
710                 lt.enableTransitionType(LayoutTransition.APPEARING);
711                 lt.enableTransitionType(LayoutTransition.DISAPPEARING);
712                 lt.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
713                 lt.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
714             } else {
715                 lt.disableTransitionType(LayoutTransition.APPEARING);
716                 lt.disableTransitionType(LayoutTransition.DISAPPEARING);
717                 lt.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
718                 lt.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
719             }
720         }
721     }
722 
setUseFadingAnimations(boolean useFadingAnimations)723     private void setUseFadingAnimations(boolean useFadingAnimations) {
724         WindowManager.LayoutParams lp = (WindowManager.LayoutParams) ((ViewGroup) getParent())
725                 .getLayoutParams();
726         if (lp != null) {
727             boolean old = lp.windowAnimations != 0;
728             if (!old && useFadingAnimations) {
729                 lp.windowAnimations = R.style.Animation_NavigationBarFadeIn;
730             } else if (old && !useFadingAnimations) {
731                 lp.windowAnimations = 0;
732             } else {
733                 return;
734             }
735             WindowManager wm = getContext().getSystemService(WindowManager.class);
736             wm.updateViewLayout((View) getParent(), lp);
737         }
738     }
739 
onStatusBarPanelStateChanged()740     public void onStatusBarPanelStateChanged() {
741         updateSlippery();
742     }
743 
744     /** */
updateDisabledSystemUiStateFlags(SysUiState sysUiState)745     public void updateDisabledSystemUiStateFlags(SysUiState sysUiState) {
746         int displayId = mContext.getDisplayId();
747 
748         sysUiState.setFlag(SYSUI_STATE_OVERVIEW_DISABLED,
749                         (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0)
750                 .setFlag(SYSUI_STATE_HOME_DISABLED,
751                         (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0)
752                 .setFlag(SYSUI_STATE_SEARCH_DISABLED,
753                         (mDisabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0)
754                 .commitUpdate(displayId);
755     }
756 
setInScreenPinning(boolean active)757     public void setInScreenPinning(boolean active) {
758         mScreenPinningActive = active;
759     }
760 
updatePanelSystemUiStateFlags()761     private void updatePanelSystemUiStateFlags() {
762         if (SysUiState.DEBUG) {
763             Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView);
764         }
765         if (mPanelView != null) {
766             mPanelView.updateSystemUiStateFlags();
767         }
768     }
769 
onOverviewProxyConnectionChange(boolean enabled)770     void onOverviewProxyConnectionChange(boolean enabled) {
771         mOverviewProxyEnabled = enabled;
772     }
773 
setShouldShowSwipeUpUi(boolean showSwipeUpUi)774     void setShouldShowSwipeUpUi(boolean showSwipeUpUi) {
775         mShowSwipeUpUi = showSwipeUpUi;
776         updateStates();
777     }
778 
779     /** */
updateStates()780     public void updateStates() {
781         if (mNavigationInflaterView != null) {
782             // Reinflate the navbar if needed, no-op unless the swipe up state changes
783             mNavigationInflaterView.onLikelyDefaultLayoutChange();
784         }
785 
786         updateSlippery();
787         reloadNavIcons();
788         updateNavButtonIcons();
789         mBgExecutor.execute(() -> setNavBarVirtualKeyHapticFeedbackEnabled(!mShowSwipeUpUi));
790         getHomeButton().setAccessibilityDelegate(
791                 mShowSwipeUpUi ? mQuickStepAccessibilityDelegate : null);
792     }
793 
794     /**
795      * Enable or disable haptic feedback on the navigation bar buttons.
796      */
setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled)797     private void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
798         try {
799             WindowManagerGlobal.getWindowManagerService()
800                     .setNavBarVirtualKeyHapticFeedbackEnabled(enabled);
801         } catch (RemoteException e) {
802             Log.w(TAG, "Failed to enable or disable navigation bar button haptics: ", e);
803         }
804     }
805 
806     /**
807      * Updates the {@link WindowManager.LayoutParams.FLAG_SLIPPERY} state dependent on if swipe up
808      * is enabled, or the notifications is fully opened without being in an animated state. If
809      * slippery is enabled, touch events will leave the nav bar window and enter into the fullscreen
810      * app/home window, if not nav bar will receive a cancelled touch event once gesture leaves bar.
811      */
updateSlippery()812     void updateSlippery() {
813         setSlippery(!isQuickStepSwipeUpEnabled() ||
814                 (mPanelView != null && mPanelView.isFullyExpanded() && !mPanelView.isCollapsing()));
815     }
816 
setSlippery(boolean slippery)817     void setSlippery(boolean slippery) {
818         setWindowFlag(WindowManager.LayoutParams.FLAG_SLIPPERY, slippery);
819     }
820 
setWindowFlag(int flags, boolean enable)821     private void setWindowFlag(int flags, boolean enable) {
822         final ViewGroup navbarView = ((ViewGroup) getParent());
823         if (navbarView == null) {
824             return;
825         }
826         WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView.getLayoutParams();
827         if (lp == null || enable == ((lp.flags & flags) != 0)) {
828             return;
829         }
830         if (enable) {
831             lp.flags |= flags;
832         } else {
833             lp.flags &= ~flags;
834         }
835         WindowManager wm = getContext().getSystemService(WindowManager.class);
836         wm.updateViewLayout(navbarView, lp);
837     }
838 
setNavBarMode(int mode, boolean imeDrawsImeNavBar)839     void setNavBarMode(int mode, boolean imeDrawsImeNavBar) {
840         mNavBarMode = mode;
841         mImeDrawsImeNavBar = imeDrawsImeNavBar;
842         mBarTransitions.onNavigationModeChanged(mNavBarMode);
843         mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
844         mRotationButtonController.onNavigationModeChanged(mNavBarMode);
845         updateRotationButton();
846     }
847 
setAccessibilityButtonState(final boolean visible, final boolean longClickable)848     public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
849         mLongClickableAccessibilityButton = longClickable;
850         getAccessibilityButton().setLongClickable(longClickable);
851         mContextualButtonGroup.setButtonVisibility(R.id.accessibility_button, visible);
852     }
853 
854     @Override
onFinishInflate()855     public void onFinishInflate() {
856         super.onFinishInflate();
857         mNavigationInflaterView = findViewById(R.id.navigation_inflater);
858         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
859 
860         updateOrientationViews();
861         reloadNavIcons();
862     }
863 
864     @Override
onDraw(Canvas canvas)865     protected void onDraw(Canvas canvas) {
866         mDeadZone.onDraw(canvas);
867         super.onDraw(canvas);
868     }
869 
870     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)871     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
872         super.onLayout(changed, left, top, right, bottom);
873 
874         notifyActiveTouchRegions();
875     }
876 
877     /**
878      * Notifies the overview service of the active touch regions.
879      */
notifyActiveTouchRegions()880     public void notifyActiveTouchRegions() {
881         if (mUpdateActiveTouchRegionsCallback != null) {
882             mUpdateActiveTouchRegionsCallback.update();
883         }
884     }
885 
setUpdateActiveTouchRegionsCallback(UpdateActiveTouchRegionsCallback callback)886     void setUpdateActiveTouchRegionsCallback(UpdateActiveTouchRegionsCallback callback) {
887         mUpdateActiveTouchRegionsCallback = callback;
888         notifyActiveTouchRegions();
889     }
890 
getButtonTouchRegionCache()891     Map<View, Rect> getButtonTouchRegionCache() {
892         FrameLayout navBarLayout = mIsVertical
893                 ? mNavigationInflaterView.mVertical
894                 : mNavigationInflaterView.mHorizontal;
895         return ((NearestTouchFrame) navBarLayout
896                 .findViewById(R.id.nav_buttons)).getFullTouchableChildRegions();
897     }
898 
updateOrientationViews()899     private void updateOrientationViews() {
900         mHorizontal = findViewById(R.id.horizontal);
901         mVertical = findViewById(R.id.vertical);
902 
903         updateCurrentView();
904     }
905 
needsReorient(int rotation)906     boolean needsReorient(int rotation) {
907         return mCurrentRotation != rotation;
908     }
909 
updateCurrentRotation()910     private void updateCurrentRotation() {
911         final int rotation = mConfiguration.windowConfiguration.getDisplayRotation();
912         if (mCurrentRotation == rotation) {
913             return;
914         }
915         mCurrentRotation = rotation;
916         mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90);
917         mDeadZone.onConfigurationChanged(mCurrentRotation);
918         if (DEBUG) {
919             Log.d(TAG, "updateCurrentRotation(): rot=" + mCurrentRotation);
920         }
921     }
922 
updateCurrentView()923     private void updateCurrentView() {
924         resetViews();
925         mCurrentView = mIsVertical ? mVertical : mHorizontal;
926         mCurrentView.setVisibility(View.VISIBLE);
927         mNavigationInflaterView.setVertical(mIsVertical);
928         mNavigationInflaterView.updateButtonDispatchersCurrentView();
929         updateLayoutTransitionsEnabled();
930         updateCurrentRotation();
931     }
932 
resetViews()933     private void resetViews() {
934         mHorizontal.setVisibility(View.GONE);
935         mVertical.setVisibility(View.GONE);
936     }
937 
updateRecentsIcon()938     private void updateRecentsIcon() {
939         mDockedIcon.setRotation(mDockedStackExists && mIsVertical ? 90 : 0);
940         getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
941         mBarTransitions.reapplyDarkIntensity();
942     }
943 
showPinningEnterExitToast(boolean entering)944     public void showPinningEnterExitToast(boolean entering) {
945         if (entering) {
946             mScreenPinningNotify.showPinningStartToast();
947         } else {
948             mScreenPinningNotify.showPinningExitToast();
949         }
950     }
951 
showPinningEscapeToast()952     public void showPinningEscapeToast() {
953         mScreenPinningNotify.showEscapeToast(
954                 mNavBarMode == NAV_BAR_MODE_GESTURAL, isRecentsButtonVisible());
955     }
956 
isVertical()957     public boolean isVertical() {
958         return mIsVertical;
959     }
960 
reorient()961     public void reorient() {
962         updateCurrentView();
963         ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
964 
965         // force the low profile & disabled states into compliance
966         mBarTransitions.init();
967 
968         // Resolve layout direction if not resolved since components changing layout direction such
969         // as changing languages will recreate this view and the direction will be resolved later
970         if (!isLayoutDirectionResolved()) {
971             resolveLayoutDirection();
972         }
973         updateNavButtonIcons();
974 
975         getHomeButton().setVertical(mIsVertical);
976     }
977 
978     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)979     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
980         int w = MeasureSpec.getSize(widthMeasureSpec);
981         int h = MeasureSpec.getSize(heightMeasureSpec);
982         if (DEBUG) Log.d(TAG, String.format(
983                 "onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
984 
985         final boolean newVertical = w > 0 && h > w
986                 && !isGesturalMode(mNavBarMode);
987         if (newVertical != mIsVertical) {
988             mIsVertical = newVertical;
989             if (DEBUG) {
990                 Log.d(TAG, String.format("onMeasure: h=%d, w=%d, vert=%s", h, w,
991                         mIsVertical ? "y" : "n"));
992             }
993             reorient();
994             notifyVerticalChangedListener(newVertical);
995         }
996 
997         if (isGesturalMode(mNavBarMode)) {
998             // Update the nav bar background to match the height of the visible nav bar
999             int height = mIsVertical
1000                     ? getResources().getDimensionPixelSize(
1001                             com.android.internal.R.dimen.navigation_bar_height_landscape)
1002                     : getResources().getDimensionPixelSize(
1003                             com.android.internal.R.dimen.navigation_bar_height);
1004             int frameHeight = getResources().getDimensionPixelSize(
1005                     com.android.internal.R.dimen.navigation_bar_frame_height);
1006             mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
1007         } else {
1008             mBarTransitions.setBackgroundFrame(null);
1009         }
1010 
1011         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1012     }
1013 
getNavBarHeight()1014     int getNavBarHeight() {
1015         return mIsVertical
1016                 ? getResources().getDimensionPixelSize(
1017                 com.android.internal.R.dimen.navigation_bar_height_landscape)
1018                 : getResources().getDimensionPixelSize(
1019                         com.android.internal.R.dimen.navigation_bar_height);
1020     }
1021 
notifyVerticalChangedListener(boolean newVertical)1022     private void notifyVerticalChangedListener(boolean newVertical) {
1023         if (mOnVerticalChangedListener != null) {
1024             mOnVerticalChangedListener.onVerticalChanged(newVertical);
1025         }
1026     }
1027 
1028     @Override
onConfigurationChanged(Configuration newConfig)1029     protected void onConfigurationChanged(Configuration newConfig) {
1030         super.onConfigurationChanged(newConfig);
1031         mTmpLastConfiguration.updateFrom(mConfiguration);
1032         final int changes = mConfiguration.updateFrom(newConfig);
1033         mFloatingRotationButton.onConfigurationChanged(changes);
1034 
1035         boolean uiCarModeChanged = updateCarMode();
1036         updateIcons(mTmpLastConfiguration);
1037         updateRecentsIcon();
1038         updateCurrentRotation();
1039         mEdgeBackGestureHandler.onConfigurationChanged(mConfiguration);
1040         if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
1041                 || mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) {
1042             // If car mode or density changes, we need to reset the icons.
1043             updateNavButtonIcons();
1044         }
1045     }
1046 
1047     /**
1048      * If the configuration changed, update the carmode and return that it was updated.
1049      */
updateCarMode()1050     private boolean updateCarMode() {
1051         boolean uiCarModeChanged = false;
1052         if (mConfiguration != null) {
1053             int uiMode = mConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK;
1054             final boolean isCarMode = (uiMode == Configuration.UI_MODE_TYPE_CAR);
1055 
1056             if (isCarMode != mInCarMode) {
1057                 mInCarMode = isCarMode;
1058                 if (ALTERNATE_CAR_MODE_UI) {
1059                     mUseCarModeUi = isCarMode;
1060                     uiCarModeChanged = true;
1061                 } else {
1062                     // Don't use car mode behavior if ALTERNATE_CAR_MODE_UI not set.
1063                     mUseCarModeUi = false;
1064                 }
1065             }
1066         }
1067         return uiCarModeChanged;
1068     }
1069 
getResourceName(int resId)1070     private String getResourceName(int resId) {
1071         if (resId != 0) {
1072             final android.content.res.Resources res = getContext().getResources();
1073             try {
1074                 return res.getResourceName(resId);
1075             } catch (android.content.res.Resources.NotFoundException ex) {
1076                 return "(unknown)";
1077             }
1078         } else {
1079             return "(null)";
1080         }
1081     }
1082 
visibilityToString(int vis)1083     private static String visibilityToString(int vis) {
1084         switch (vis) {
1085             case View.INVISIBLE:
1086                 return "INVISIBLE";
1087             case View.GONE:
1088                 return "GONE";
1089             default:
1090                 return "VISIBLE";
1091         }
1092     }
1093 
1094     @Override
onAttachedToWindow()1095     protected void onAttachedToWindow() {
1096         super.onAttachedToWindow();
1097         requestApplyInsets();
1098         reorient();
1099         if (mRotationButtonController != null) {
1100             mRotationButtonController.registerListeners(false /* registerRotationWatcher */);
1101         }
1102 
1103         updateNavButtonIcons();
1104     }
1105 
1106     @Override
onDetachedFromWindow()1107     protected void onDetachedFromWindow() {
1108         super.onDetachedFromWindow();
1109         for (int i = 0; i < mButtonDispatchers.size(); ++i) {
1110             mButtonDispatchers.valueAt(i).onDestroy();
1111         }
1112         if (mRotationButtonController != null) {
1113             mFloatingRotationButton.hide();
1114             mRotationButtonController.unregisterListeners();
1115         }
1116     }
1117 
dump(PrintWriter pw)1118     void dump(PrintWriter pw) {
1119         final Rect r = new Rect();
1120         final Point size = new Point();
1121         getContextDisplay().getRealSize(size);
1122 
1123         pw.println("NavigationBarView:");
1124         pw.println(String.format("      this: " + CentralSurfaces.viewInfo(this)
1125                         + " " + visibilityToString(getVisibility())));
1126 
1127         getWindowVisibleDisplayFrame(r);
1128         final boolean offscreen = r.right > size.x || r.bottom > size.y;
1129         pw.println("      window: "
1130                 + r.toShortString()
1131                 + " " + visibilityToString(getWindowVisibility())
1132                 + (offscreen ? " OFFSCREEN!" : ""));
1133 
1134         pw.println(String.format("      mCurrentView: id=%s (%dx%d) %s %f",
1135                         getResourceName(getCurrentView().getId()),
1136                         getCurrentView().getWidth(), getCurrentView().getHeight(),
1137                         visibilityToString(getCurrentView().getVisibility()),
1138                         getCurrentView().getAlpha()));
1139 
1140         pw.println(String.format("      disabled=0x%08x vertical=%s darkIntensity=%.2f",
1141                         mDisabledFlags,
1142                         mIsVertical ? "true" : "false",
1143                         getLightTransitionsController().getCurrentDarkIntensity()));
1144 
1145         pw.println("    mScreenOn: " + mScreenOn);
1146 
1147 
1148         dumpButton(pw, "back", getBackButton());
1149         dumpButton(pw, "home", getHomeButton());
1150         dumpButton(pw, "handle", getHomeHandle());
1151         dumpButton(pw, "rcnt", getRecentsButton());
1152         dumpButton(pw, "rota", getRotateSuggestionButton());
1153         dumpButton(pw, "a11y", getAccessibilityButton());
1154         dumpButton(pw, "ime", getImeSwitchButton());
1155 
1156         if (mNavigationInflaterView != null) {
1157             mNavigationInflaterView.dump(pw);
1158         }
1159         mBarTransitions.dump(pw);
1160         mContextualButtonGroup.dump(pw);
1161         mEdgeBackGestureHandler.dump(pw);
1162     }
1163 
1164     @Override
onApplyWindowInsets(WindowInsets insets)1165     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1166         int leftInset = insets.getSystemWindowInsetLeft();
1167         int rightInset = insets.getSystemWindowInsetRight();
1168         setPadding(leftInset, insets.getSystemWindowInsetTop(), rightInset,
1169                 insets.getSystemWindowInsetBottom());
1170         // we're passing the insets onto the gesture handler since the back arrow is only
1171         // conditionally added and doesn't always get all the insets.
1172         mEdgeBackGestureHandler.setInsets(leftInset, rightInset);
1173 
1174         // this allows assist handle to be drawn outside its bound so that it can align screen
1175         // bottom by translating its y position.
1176         final boolean shouldClip =
1177                 !isGesturalMode(mNavBarMode) || insets.getSystemWindowInsetBottom() == 0;
1178         setClipChildren(shouldClip);
1179         setClipToPadding(shouldClip);
1180 
1181         return super.onApplyWindowInsets(insets);
1182     }
1183 
addPipExclusionBoundsChangeListener(Pip pip)1184     void addPipExclusionBoundsChangeListener(Pip pip) {
1185         pip.addPipExclusionBoundsChangeListener(mPipListener);
1186     }
1187 
removePipExclusionBoundsChangeListener(Pip pip)1188     void removePipExclusionBoundsChangeListener(Pip pip) {
1189         pip.removePipExclusionBoundsChangeListener(mPipListener);
1190     }
1191 
registerBackAnimation(BackAnimation backAnimation)1192     void registerBackAnimation(BackAnimation backAnimation) {
1193         mEdgeBackGestureHandler.setBackAnimation(backAnimation);
1194     }
1195 
dumpButton(PrintWriter pw, String caption, ButtonDispatcher button)1196     private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
1197         pw.print("      " + caption + ": ");
1198         if (button == null) {
1199             pw.print("null");
1200         } else {
1201             pw.print(visibilityToString(button.getVisibility())
1202                     + " alpha=" + button.getAlpha()
1203                     );
1204         }
1205         pw.println();
1206     }
1207 
1208     public interface OnVerticalChangedListener {
onVerticalChanged(boolean isVertical)1209         void onVerticalChanged(boolean isVertical);
1210     }
1211 
1212     private final Consumer<Boolean> mDockedListener = exists -> post(() -> {
1213         mDockedStackExists = exists;
1214         updateRecentsIcon();
1215     });
1216 
1217     private final Consumer<Rect> mPipListener = bounds -> post(() -> {
1218         mEdgeBackGestureHandler.setPipStashExclusionBounds(bounds);
1219     });
1220 
1221 
1222     interface UpdateActiveTouchRegionsCallback {
update()1223         void update();
1224     }
1225 }
1226