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