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