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