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