OSDN Git Service

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