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.MotionEvent.ACTION_DOWN;
20 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
21 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
22 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
23 
24 import android.animation.LayoutTransition;
25 import android.animation.LayoutTransition.TransitionListener;
26 import android.animation.ObjectAnimator;
27 import android.animation.TimeInterpolator;
28 import android.animation.ValueAnimator;
29 import android.annotation.DrawableRes;
30 import android.annotation.StyleRes;
31 import android.app.StatusBarManager;
32 import android.content.Context;
33 import android.content.res.Configuration;
34 import android.graphics.Canvas;
35 import android.graphics.Point;
36 import android.graphics.Rect;
37 import android.graphics.drawable.AnimatedVectorDrawable;
38 import android.graphics.drawable.Drawable;
39 import android.os.Bundle;
40 import android.os.Handler;
41 import android.os.Message;
42 import android.os.SystemProperties;
43 import android.support.annotation.ColorInt;
44 import android.util.AttributeSet;
45 import android.util.Log;
46 import android.util.SparseArray;
47 import android.view.ContextThemeWrapper;
48 import android.view.Display;
49 import android.view.MotionEvent;
50 import android.view.Surface;
51 import android.view.View;
52 import android.view.ViewGroup;
53 import android.view.WindowInsets;
54 import android.view.WindowManager;
55 import android.view.accessibility.AccessibilityNodeInfo;
56 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
57 import android.view.inputmethod.InputMethodManager;
58 import android.widget.FrameLayout;
59 
60 import com.android.settingslib.Utils;
61 import com.android.systemui.Dependency;
62 import com.android.systemui.DockedStackExistsListener;
63 import com.android.systemui.OverviewProxyService;
64 import com.android.systemui.R;
65 import com.android.systemui.RecentsComponent;
66 import com.android.systemui.SysUiServiceProvider;
67 import com.android.systemui.plugins.PluginListener;
68 import com.android.systemui.plugins.PluginManager;
69 import com.android.systemui.plugins.statusbar.phone.NavGesture;
70 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
71 import com.android.systemui.recents.Recents;
72 import com.android.systemui.recents.RecentsOnboarding;
73 import com.android.systemui.shared.recents.IOverviewProxy;
74 import com.android.systemui.shared.system.ActivityManagerWrapper;
75 import com.android.systemui.shared.system.NavigationBarCompat;
76 import com.android.systemui.shared.system.WindowManagerWrapper;
77 import com.android.systemui.stackdivider.Divider;
78 import com.android.systemui.statusbar.policy.DeadZone;
79 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
80 import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
81 
82 import java.io.FileDescriptor;
83 import java.io.PrintWriter;
84 import java.util.function.Consumer;
85 
86 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
87 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
88 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
89 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
90 
91 public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> {
92     final static boolean DEBUG = false;
93     final static String TAG = "StatusBar/NavBarView";
94 
95     // slippery nav bar when everything is disabled, e.g. during setup
96     final static boolean SLIPPERY_WHEN_DISABLED = true;
97 
98     final static boolean ALTERNATE_CAR_MODE_UI = false;
99 
100     final Display mDisplay;
101     View mCurrentView = null;
102     View[] mRotatedViews = new View[4];
103 
104     boolean mVertical;
105     private int mCurrentRotation = -1;
106 
107     boolean mShowMenu;
108     boolean mShowAccessibilityButton;
109     boolean mLongClickableAccessibilityButton;
110     boolean mShowRotateButton;
111     int mDisabledFlags = 0;
112     int mNavigationIconHints = 0;
113 
114     private @NavigationBarCompat.HitTarget int mDownHitTarget = HIT_TARGET_NONE;
115     private Rect mHomeButtonBounds = new Rect();
116     private Rect mBackButtonBounds = new Rect();
117     private Rect mRecentsButtonBounds = new Rect();
118     private Rect mRotationButtonBounds = new Rect();
119     private int[] mTmpPosition = new int[2];
120     private Rect mTmpRect = new Rect();
121 
122     private KeyButtonDrawable mBackIcon;
123     private KeyButtonDrawable mBackCarModeIcon, mBackLandCarModeIcon;
124     private KeyButtonDrawable mBackAltCarModeIcon, mBackAltLandCarModeIcon;
125     private KeyButtonDrawable mHomeDefaultIcon, mHomeCarModeIcon;
126     private KeyButtonDrawable mRecentIcon;
127     private KeyButtonDrawable mDockedIcon;
128     private KeyButtonDrawable mImeIcon;
129     private KeyButtonDrawable mMenuIcon;
130     private KeyButtonDrawable mAccessibilityIcon;
131     private TintedKeyButtonDrawable mRotateSuggestionIcon;
132 
133     private GestureHelper mGestureHelper;
134     private final DeadZone mDeadZone;
135     private boolean mDeadZoneConsuming = false;
136     private final NavigationBarTransitions mBarTransitions;
137     private final OverviewProxyService mOverviewProxyService;
138 
139     // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
140     final static boolean WORKAROUND_INVALID_LAYOUT = true;
141     final static int MSG_CHECK_INVALID_LAYOUT = 8686;
142 
143     // performs manual animation in sync with layout transitions
144     private final NavTransitionListener mTransitionListener = new NavTransitionListener();
145 
146     private OnVerticalChangedListener mOnVerticalChangedListener;
147     private boolean mLayoutTransitionsEnabled = true;
148     private boolean mWakeAndUnlocking;
149     private boolean mUseCarModeUi = false;
150     private boolean mInCarMode = false;
151     private boolean mDockedStackExists;
152 
153     private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
154     private Configuration mConfiguration;
155 
156     private NavigationBarInflaterView mNavigationInflaterView;
157     private RecentsComponent mRecentsComponent;
158     private Divider mDivider;
159     private RecentsOnboarding mRecentsOnboarding;
160     private NotificationPanelView mPanelView;
161 
162     private int mRotateBtnStyle = R.style.RotateButtonCCWStart90;
163 
164     private class NavTransitionListener implements TransitionListener {
165         private boolean mBackTransitioning;
166         private boolean mHomeAppearing;
167         private long mStartDelay;
168         private long mDuration;
169         private TimeInterpolator mInterpolator;
170 
171         @Override
startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)172         public void startTransition(LayoutTransition transition, ViewGroup container,
173                 View view, int transitionType) {
174             if (view.getId() == R.id.back) {
175                 mBackTransitioning = true;
176             } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
177                 mHomeAppearing = true;
178                 mStartDelay = transition.getStartDelay(transitionType);
179                 mDuration = transition.getDuration(transitionType);
180                 mInterpolator = transition.getInterpolator(transitionType);
181             }
182         }
183 
184         @Override
endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)185         public void endTransition(LayoutTransition transition, ViewGroup container,
186                 View view, int transitionType) {
187             if (view.getId() == R.id.back) {
188                 mBackTransitioning = false;
189             } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
190                 mHomeAppearing = false;
191             }
192         }
193 
onBackAltCleared()194         public void onBackAltCleared() {
195             ButtonDispatcher backButton = getBackButton();
196 
197             // When dismissing ime during unlock, force the back button to run the same appearance
198             // animation as home (if we catch this condition early enough).
199             if (!mBackTransitioning && backButton.getVisibility() == VISIBLE
200                     && mHomeAppearing && getHomeButton().getAlpha() == 0) {
201                 getBackButton().setAlpha(0);
202                 ValueAnimator a = ObjectAnimator.ofFloat(backButton, "alpha", 0, 1);
203                 a.setStartDelay(mStartDelay);
204                 a.setDuration(mDuration);
205                 a.setInterpolator(mInterpolator);
206                 a.start();
207             }
208         }
209     }
210 
211     private final OnClickListener mImeSwitcherClickListener = new OnClickListener() {
212         @Override
213         public void onClick(View view) {
214             mContext.getSystemService(InputMethodManager.class)
215                     .showInputMethodPicker(true /* showAuxiliarySubtypes */);
216         }
217     };
218 
219     private class H extends Handler {
handleMessage(Message m)220         public void handleMessage(Message m) {
221             switch (m.what) {
222                 case MSG_CHECK_INVALID_LAYOUT:
223                     final String how = "" + m.obj;
224                     final int w = getWidth();
225                     final int h = getHeight();
226                     final int vw = getCurrentView().getWidth();
227                     final int vh = getCurrentView().getHeight();
228 
229                     if (h != vh || w != vw) {
230                         Log.w(TAG, String.format(
231                             "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)",
232                             how, w, h, vw, vh));
233                         if (WORKAROUND_INVALID_LAYOUT) {
234                             requestLayout();
235                         }
236                     }
237                     break;
238             }
239         }
240     }
241 
242     private final AccessibilityDelegate mQuickStepAccessibilityDelegate
243             = new AccessibilityDelegate() {
244         private AccessibilityAction mToggleOverviewAction;
245 
246         @Override
247         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
248             super.onInitializeAccessibilityNodeInfo(host, info);
249             if (mToggleOverviewAction == null) {
250                 mToggleOverviewAction = new AccessibilityAction(R.id.action_toggle_overview,
251                     getContext().getString(R.string.quick_step_accessibility_toggle_overview));
252             }
253             info.addAction(mToggleOverviewAction);
254         }
255 
256         @Override
257         public boolean performAccessibilityAction(View host, int action, Bundle args) {
258             switch (action) {
259                 case R.id.action_toggle_overview:
260                     SysUiServiceProvider.getComponent(getContext(), Recents.class)
261                             .toggleRecentApps();
262                     break;
263                 default:
264                     return super.performAccessibilityAction(host, action, args);
265             }
266             return true;
267         }
268     };
269 
NavigationBarView(Context context, AttributeSet attrs)270     public NavigationBarView(Context context, AttributeSet attrs) {
271         super(context, attrs);
272 
273         mDisplay = ((WindowManager) context.getSystemService(
274                 Context.WINDOW_SERVICE)).getDefaultDisplay();
275 
276         mVertical = false;
277         mShowMenu = false;
278 
279         mShowAccessibilityButton = false;
280         mLongClickableAccessibilityButton = false;
281 
282         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
283         mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
284 
285         mConfiguration = new Configuration();
286         mConfiguration.updateFrom(context.getResources().getConfiguration());
287         reloadNavIcons();
288 
289         mBarTransitions = new NavigationBarTransitions(this);
290 
291         mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
292         mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
293         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
294         mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
295         mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
296         mButtonDispatchers.put(R.id.accessibility_button,
297                 new ButtonDispatcher(R.id.accessibility_button));
298         mButtonDispatchers.put(R.id.rotate_suggestion,
299                 new ButtonDispatcher(R.id.rotate_suggestion));
300         mButtonDispatchers.put(R.id.menu_container,
301                 new ButtonDispatcher(R.id.menu_container));
302         mDeadZone = new DeadZone(this);
303     }
304 
getBarTransitions()305     public BarTransitions getBarTransitions() {
306         return mBarTransitions;
307     }
308 
getLightTransitionsController()309     public LightBarTransitionsController getLightTransitionsController() {
310         return mBarTransitions.getLightTransitionsController();
311     }
312 
setComponents(RecentsComponent recentsComponent, Divider divider, NotificationPanelView panel)313     public void setComponents(RecentsComponent recentsComponent, Divider divider,
314             NotificationPanelView panel) {
315         mRecentsComponent = recentsComponent;
316         mDivider = divider;
317         mPanelView = panel;
318         if (mGestureHelper instanceof NavigationBarGestureHelper) {
319             ((NavigationBarGestureHelper) mGestureHelper).setComponents(
320                     recentsComponent, divider, this);
321         }
322     }
323 
setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener)324     public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
325         mOnVerticalChangedListener = onVerticalChangedListener;
326         notifyVerticalChangedListener(mVertical);
327     }
328 
329     @Override
onInterceptTouchEvent(MotionEvent event)330     public boolean onInterceptTouchEvent(MotionEvent event) {
331         if (shouldDeadZoneConsumeTouchEvents(event)) {
332             return true;
333         }
334         switch (event.getActionMasked()) {
335             case ACTION_DOWN:
336                 int x = (int) event.getX();
337                 int y = (int) event.getY();
338                 mDownHitTarget = HIT_TARGET_NONE;
339                 if (getBackButton().isVisible() && mBackButtonBounds.contains(x, y)) {
340                     mDownHitTarget = HIT_TARGET_BACK;
341                 } else if (getHomeButton().isVisible() && mHomeButtonBounds.contains(x, y)) {
342                     mDownHitTarget = HIT_TARGET_HOME;
343                 } else if (getRecentsButton().isVisible() && mRecentsButtonBounds.contains(x, y)) {
344                     mDownHitTarget = HIT_TARGET_OVERVIEW;
345                 } else if (getRotateSuggestionButton().isVisible()
346                         && mRotationButtonBounds.contains(x, y)) {
347                     mDownHitTarget = HIT_TARGET_ROTATION;
348                 }
349                 break;
350         }
351         return mGestureHelper.onInterceptTouchEvent(event);
352     }
353 
354     @Override
onTouchEvent(MotionEvent event)355     public boolean onTouchEvent(MotionEvent event) {
356         if (shouldDeadZoneConsumeTouchEvents(event)) {
357             return true;
358         }
359         if (mGestureHelper.onTouchEvent(event)) {
360             return true;
361         }
362         return super.onTouchEvent(event);
363     }
364 
shouldDeadZoneConsumeTouchEvents(MotionEvent event)365     private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) {
366         if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) {
367             switch (event.getActionMasked()) {
368                 case MotionEvent.ACTION_DOWN:
369                     // Allow gestures starting in the deadzone to be slippery
370                     setSlippery(true);
371                     mDeadZoneConsuming = true;
372                     break;
373                 case MotionEvent.ACTION_CANCEL:
374                 case MotionEvent.ACTION_UP:
375                     // When a gesture started in the deadzone is finished, restore slippery state
376                     updateSlippery();
377                     mDeadZoneConsuming = false;
378                     break;
379             }
380             return true;
381         }
382         return false;
383     }
384 
getDownHitTarget()385     public @NavigationBarCompat.HitTarget int getDownHitTarget() {
386         return mDownHitTarget;
387     }
388 
abortCurrentGesture()389     public void abortCurrentGesture() {
390         getHomeButton().abortCurrentGesture();
391     }
392 
393     private H mHandler = new H();
394 
getCurrentView()395     public View getCurrentView() {
396         return mCurrentView;
397     }
398 
getAllViews()399     public View[] getAllViews() {
400         return mRotatedViews;
401     }
402 
getRecentsButton()403     public ButtonDispatcher getRecentsButton() {
404         return mButtonDispatchers.get(R.id.recent_apps);
405     }
406 
getMenuButton()407     public ButtonDispatcher getMenuButton() {
408         return mButtonDispatchers.get(R.id.menu);
409     }
410 
getBackButton()411     public ButtonDispatcher getBackButton() {
412         return mButtonDispatchers.get(R.id.back);
413     }
414 
getHomeButton()415     public ButtonDispatcher getHomeButton() {
416         return mButtonDispatchers.get(R.id.home);
417     }
418 
getImeSwitchButton()419     public ButtonDispatcher getImeSwitchButton() {
420         return mButtonDispatchers.get(R.id.ime_switcher);
421     }
422 
getAccessibilityButton()423     public ButtonDispatcher getAccessibilityButton() {
424         return mButtonDispatchers.get(R.id.accessibility_button);
425     }
426 
getRotateSuggestionButton()427     public ButtonDispatcher getRotateSuggestionButton() {
428         return mButtonDispatchers.get(R.id.rotate_suggestion);
429     }
430 
getMenuContainer()431     public ButtonDispatcher getMenuContainer() {
432         return mButtonDispatchers.get(R.id.menu_container);
433     }
434 
getButtonDispatchers()435     public SparseArray<ButtonDispatcher> getButtonDispatchers() {
436         return mButtonDispatchers;
437     }
438 
isRecentsButtonVisible()439     public boolean isRecentsButtonVisible() {
440         return getRecentsButton().getVisibility() == View.VISIBLE;
441     }
442 
isOverviewEnabled()443     public boolean isOverviewEnabled() {
444         return (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) == 0;
445     }
446 
isQuickStepSwipeUpEnabled()447     public boolean isQuickStepSwipeUpEnabled() {
448         return mOverviewProxyService.shouldShowSwipeUpUI() && isOverviewEnabled();
449     }
450 
isQuickScrubEnabled()451     public boolean isQuickScrubEnabled() {
452         return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", true)
453                 && mOverviewProxyService.isEnabled() && isOverviewEnabled()
454                 && ((mOverviewProxyService.getInteractionFlags() & FLAG_DISABLE_QUICK_SCRUB) == 0);
455     }
456 
457     // TODO(b/80003212): change car mode icons to vector icons.
updateCarModeIcons(Context ctx)458     private void updateCarModeIcons(Context ctx) {
459         mBackCarModeIcon = getDrawable(ctx,
460                 R.drawable.ic_sysbar_back_carmode, R.drawable.ic_sysbar_back_carmode);
461         mBackLandCarModeIcon = mBackCarModeIcon;
462         mBackAltCarModeIcon = getDrawable(ctx,
463                 R.drawable.ic_sysbar_back_ime_carmode, R.drawable.ic_sysbar_back_ime_carmode);
464         mBackAltLandCarModeIcon = mBackAltCarModeIcon;
465         mHomeCarModeIcon = getDrawable(ctx,
466                 R.drawable.ic_sysbar_home_carmode, R.drawable.ic_sysbar_home_carmode);
467     }
468 
reloadNavIcons()469     private void reloadNavIcons() {
470         updateIcons(mContext, Configuration.EMPTY, mConfiguration);
471     }
472 
updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig)473     private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
474         int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
475         int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
476         Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
477         Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
478 
479         if (oldConfig.orientation != newConfig.orientation
480                 || oldConfig.densityDpi != newConfig.densityDpi) {
481             mDockedIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_docked);
482             mHomeDefaultIcon = getHomeDrawable(lightContext, darkContext);
483         }
484         if (oldConfig.densityDpi != newConfig.densityDpi
485                 || oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) {
486             mBackIcon = getBackDrawable(lightContext, darkContext);
487             mRecentIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_recent);
488             mMenuIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_menu);
489 
490             mAccessibilityIcon = getDrawable(lightContext, darkContext,
491                     R.drawable.ic_sysbar_accessibility_button, false /* hasShadow */);
492 
493             mImeIcon = getDrawable(lightContext, darkContext, R.drawable.ic_ime_switcher_default,
494                     false /* hasShadow */);
495 
496             updateRotateSuggestionButtonStyle(mRotateBtnStyle, false);
497 
498             if (ALTERNATE_CAR_MODE_UI) {
499                 updateCarModeIcons(ctx);
500             }
501         }
502     }
503 
getBackDrawable(Context lightContext, Context darkContext)504     public KeyButtonDrawable getBackDrawable(Context lightContext, Context darkContext) {
505         KeyButtonDrawable drawable = chooseNavigationIconDrawable(lightContext, darkContext,
506                 R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_quick_step);
507         orientBackButton(drawable);
508         return drawable;
509     }
510 
getHomeDrawable(Context lightContext, Context darkContext)511     public KeyButtonDrawable getHomeDrawable(Context lightContext, Context darkContext) {
512         final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
513         KeyButtonDrawable drawable = quickStepEnabled
514                 ? getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_home_quick_step)
515                 : getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_home,
516                         false /* hasShadow */);
517         orientHomeButton(drawable);
518         return drawable;
519     }
520 
orientBackButton(KeyButtonDrawable drawable)521     private void orientBackButton(KeyButtonDrawable drawable) {
522         final boolean useAltBack =
523             (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
524         drawable.setRotation(useAltBack
525                 ? -90 : (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) ? 180 : 0);
526     }
527 
orientHomeButton(KeyButtonDrawable drawable)528     private void orientHomeButton(KeyButtonDrawable drawable) {
529         drawable.setRotation(mVertical ? 90 : 0);
530     }
531 
chooseNavigationIconDrawable(Context lightContext, Context darkContext, @DrawableRes int icon, @DrawableRes int quickStepIcon)532     private KeyButtonDrawable chooseNavigationIconDrawable(Context lightContext,
533             Context darkContext, @DrawableRes int icon, @DrawableRes int quickStepIcon) {
534         final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
535         return quickStepEnabled
536                 ? getDrawable(lightContext, darkContext, quickStepIcon)
537                 : getDrawable(lightContext, darkContext, icon);
538     }
539 
getDrawable(Context lightContext, Context darkContext, @DrawableRes int icon)540     private KeyButtonDrawable getDrawable(Context lightContext, Context darkContext,
541             @DrawableRes int icon) {
542         return getDrawable(lightContext, darkContext, icon, true /* hasShadow */);
543     }
544 
getDrawable(Context lightContext, Context darkContext, @DrawableRes int icon, boolean hasShadow)545     private KeyButtonDrawable getDrawable(Context lightContext, Context darkContext,
546             @DrawableRes int icon, boolean hasShadow) {
547         return KeyButtonDrawable.create(lightContext, lightContext.getDrawable(icon),
548                 darkContext.getDrawable(icon), hasShadow);
549     }
550 
getDrawable(Context ctx, @DrawableRes int lightIcon, @DrawableRes int darkIcon)551     private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int lightIcon,
552             @DrawableRes int darkIcon) {
553         // Legacy image icons using separate light and dark images will not support shadows
554         return KeyButtonDrawable.create(ctx, ctx.getDrawable(lightIcon),
555             ctx.getDrawable(darkIcon), false /* hasShadow */);
556     }
557 
getDrawable(Context ctx, @DrawableRes int icon, @ColorInt int lightColor, @ColorInt int darkColor)558     private TintedKeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon,
559             @ColorInt int lightColor, @ColorInt int darkColor) {
560         return TintedKeyButtonDrawable.create(ctx.getDrawable(icon), lightColor, darkColor);
561     }
562 
563     @Override
setLayoutDirection(int layoutDirection)564     public void setLayoutDirection(int layoutDirection) {
565         reloadNavIcons();
566 
567         super.setLayoutDirection(layoutDirection);
568     }
569 
getBackIconWithAlt(boolean carMode, boolean landscape)570     private KeyButtonDrawable getBackIconWithAlt(boolean carMode, boolean landscape) {
571         return landscape
572                 ? carMode ? mBackAltLandCarModeIcon : mBackIcon
573                 : carMode ? mBackAltCarModeIcon : mBackIcon;
574     }
575 
getBackIcon(boolean carMode, boolean landscape)576     private KeyButtonDrawable getBackIcon(boolean carMode, boolean landscape) {
577         return landscape
578                 ? carMode ? mBackLandCarModeIcon : mBackIcon
579                 : carMode ? mBackCarModeIcon : mBackIcon;
580     }
581 
setNavigationIconHints(int hints)582     public void setNavigationIconHints(int hints) {
583         if (hints == mNavigationIconHints) return;
584         final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
585         if ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0 && !backAlt) {
586             mTransitionListener.onBackAltCleared();
587         }
588         if (DEBUG) {
589             android.widget.Toast.makeText(getContext(),
590                 "Navigation icon hints = " + hints,
591                 500).show();
592         }
593         mNavigationIconHints = hints;
594         updateNavButtonIcons();
595     }
596 
setDisabledFlags(int disabledFlags)597     public void setDisabledFlags(int disabledFlags) {
598         if (mDisabledFlags == disabledFlags) return;
599 
600         final boolean overviewEnabledBefore = isOverviewEnabled();
601         mDisabledFlags = disabledFlags;
602 
603         // Update icons if overview was just enabled to ensure the correct icons are present
604         if (!overviewEnabledBefore && isOverviewEnabled()) {
605             reloadNavIcons();
606         }
607 
608         updateNavButtonIcons();
609         updateSlippery();
610         setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
611     }
612 
updateNavButtonIcons()613     public void updateNavButtonIcons() {
614         // We have to replace or restore the back and home button icons when exiting or entering
615         // carmode, respectively. Recents are not available in CarMode in nav bar so change
616         // to recent icon is not required.
617         final boolean useAltBack =
618                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
619         KeyButtonDrawable backIcon = useAltBack
620                 ? getBackIconWithAlt(mUseCarModeUi, mVertical)
621                 : getBackIcon(mUseCarModeUi, mVertical);
622         KeyButtonDrawable homeIcon = mUseCarModeUi ? mHomeCarModeIcon : mHomeDefaultIcon;
623         if (!mUseCarModeUi) {
624             orientBackButton(backIcon);
625             orientHomeButton(homeIcon);
626         }
627         getHomeButton().setImageDrawable(homeIcon);
628         getBackButton().setImageDrawable(backIcon);
629 
630         updateRecentsIcon();
631 
632         // Update IME button visibility, a11y and rotate button always overrides the appearance
633         final boolean showImeButton =
634                 !mShowAccessibilityButton &&
635                         !mShowRotateButton &&
636                         ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
637         getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
638         getImeSwitchButton().setImageDrawable(mImeIcon);
639 
640         // Update menu button, visibility logic in method
641         setMenuVisibility(mShowMenu, true);
642         getMenuButton().setImageDrawable(mMenuIcon);
643 
644         // Update rotate button, visibility altered by a11y button logic
645         getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
646 
647         // Update a11y button, visibility logic in state method
648         setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton);
649         getAccessibilityButton().setImageDrawable(mAccessibilityIcon);
650 
651         mBarTransitions.reapplyDarkIntensity();
652 
653         boolean disableHome = ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
654 
655         // Always disable recents when alternate car mode UI is active.
656         boolean disableRecent = mUseCarModeUi || !isOverviewEnabled();
657 
658         boolean disableBack = ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) && !useAltBack;
659 
660         // When screen pinning, don't hide back and home when connected service or back and
661         // recents buttons when disconnected from launcher service in screen pinning mode,
662         // as they are used for exiting.
663         final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
664         if (mOverviewProxyService.isEnabled()) {
665             // Use interaction flags to show/hide navigation buttons but will be shown if required
666             // to exit screen pinning.
667             final int flags = mOverviewProxyService.getInteractionFlags();
668             disableRecent |= (flags & FLAG_SHOW_OVERVIEW_BUTTON) == 0;
669             if (pinningActive) {
670                 disableBack = disableHome = false;
671             }
672         } else if (pinningActive) {
673             disableBack = disableRecent = false;
674         }
675 
676         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
677         if (navButtons != null) {
678             LayoutTransition lt = navButtons.getLayoutTransition();
679             if (lt != null) {
680                 if (!lt.getTransitionListeners().contains(mTransitionListener)) {
681                     lt.addTransitionListener(mTransitionListener);
682                 }
683             }
684         }
685 
686         getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
687         getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
688         getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
689     }
690 
inScreenPinning()691     public boolean inScreenPinning() {
692         return ActivityManagerWrapper.getInstance().isScreenPinningActive();
693     }
694 
setLayoutTransitionsEnabled(boolean enabled)695     public void setLayoutTransitionsEnabled(boolean enabled) {
696         mLayoutTransitionsEnabled = enabled;
697         updateLayoutTransitionsEnabled();
698     }
699 
setWakeAndUnlocking(boolean wakeAndUnlocking)700     public void setWakeAndUnlocking(boolean wakeAndUnlocking) {
701         setUseFadingAnimations(wakeAndUnlocking);
702         mWakeAndUnlocking = wakeAndUnlocking;
703         updateLayoutTransitionsEnabled();
704     }
705 
updateLayoutTransitionsEnabled()706     private void updateLayoutTransitionsEnabled() {
707         boolean enabled = !mWakeAndUnlocking && mLayoutTransitionsEnabled;
708         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
709         LayoutTransition lt = navButtons.getLayoutTransition();
710         if (lt != null) {
711             if (enabled) {
712                 lt.enableTransitionType(LayoutTransition.APPEARING);
713                 lt.enableTransitionType(LayoutTransition.DISAPPEARING);
714                 lt.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
715                 lt.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
716             } else {
717                 lt.disableTransitionType(LayoutTransition.APPEARING);
718                 lt.disableTransitionType(LayoutTransition.DISAPPEARING);
719                 lt.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
720                 lt.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
721             }
722         }
723     }
724 
setUseFadingAnimations(boolean useFadingAnimations)725     private void setUseFadingAnimations(boolean useFadingAnimations) {
726         WindowManager.LayoutParams lp = (WindowManager.LayoutParams) ((ViewGroup) getParent())
727                 .getLayoutParams();
728         if (lp != null) {
729             boolean old = lp.windowAnimations != 0;
730             if (!old && useFadingAnimations) {
731                 lp.windowAnimations = R.style.Animation_NavigationBarFadeIn;
732             } else if (old && !useFadingAnimations) {
733                 lp.windowAnimations = 0;
734             } else {
735                 return;
736             }
737             WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
738             wm.updateViewLayout((View) getParent(), lp);
739         }
740     }
741 
onNavigationButtonLongPress(View v)742     public void onNavigationButtonLongPress(View v) {
743         mGestureHelper.onNavigationButtonLongPress(v);
744     }
745 
onPanelExpandedChange(boolean expanded)746     public void onPanelExpandedChange(boolean expanded) {
747         updateSlippery();
748     }
749 
updateStates()750     public void updateStates() {
751         final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI();
752 
753         if (mNavigationInflaterView != null) {
754             // Reinflate the navbar if needed, no-op unless the swipe up state changes
755             mNavigationInflaterView.onLikelyDefaultLayoutChange();
756         }
757 
758         updateSlippery();
759         reloadNavIcons();
760         updateNavButtonIcons();
761         setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
762         WindowManagerWrapper.getInstance().setNavBarVirtualKeyHapticFeedbackEnabled(!showSwipeUpUI);
763         getHomeButton().setAccessibilityDelegate(
764                 showSwipeUpUI ? mQuickStepAccessibilityDelegate : null);
765     }
766 
updateSlippery()767     private void updateSlippery() {
768         setSlippery(!isQuickStepSwipeUpEnabled() || mPanelView.isFullyExpanded());
769     }
770 
setSlippery(boolean slippery)771     private void setSlippery(boolean slippery) {
772         boolean changed = false;
773         final ViewGroup navbarView = ((ViewGroup) getParent());
774         final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView
775                 .getLayoutParams();
776         if (lp == null) {
777             return;
778         }
779         if (slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) == 0) {
780             lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
781             changed = true;
782         } else if (!slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0) {
783             lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
784             changed = true;
785         }
786         if (changed) {
787             WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
788             wm.updateViewLayout(navbarView, lp);
789         }
790     }
791 
setMenuVisibility(final boolean show)792     public void setMenuVisibility(final boolean show) {
793         setMenuVisibility(show, false);
794     }
795 
setMenuVisibility(final boolean show, final boolean force)796     public void setMenuVisibility(final boolean show, final boolean force) {
797         if (!force && mShowMenu == show) return;
798 
799         mShowMenu = show;
800 
801         // Only show Menu if IME switcher, rotate and Accessibility buttons are not shown.
802         final boolean shouldShow = mShowMenu &&
803                 !mShowAccessibilityButton &&
804                 !mShowRotateButton &&
805                 ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
806 
807         getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
808     }
809 
setAccessibilityButtonState(final boolean visible, final boolean longClickable)810     public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
811         mShowAccessibilityButton = visible;
812         mLongClickableAccessibilityButton = longClickable;
813         if (visible) {
814             // Accessibility button overrides Menu, IME switcher and rotate buttons.
815             setMenuVisibility(false, true);
816             getImeSwitchButton().setVisibility(View.INVISIBLE);
817             setRotateButtonVisibility(false);
818         }
819 
820         getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
821         getAccessibilityButton().setLongClickable(longClickable);
822     }
823 
updateRotateSuggestionButtonStyle(@tyleRes int style, boolean setIcon)824     public void updateRotateSuggestionButtonStyle(@StyleRes int style, boolean setIcon) {
825         mRotateBtnStyle = style;
826         final Context ctx = getContext();
827 
828         // Extract the dark and light tints
829         final int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
830         final int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
831         Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
832         Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
833         final int lightColor = Utils.getColorAttr(lightContext, R.attr.singleToneColor);
834         final int darkColor = Utils.getColorAttr(darkContext, R.attr.singleToneColor);
835 
836         // Use the supplied style to set the icon's rotation parameters
837         Context rotateContext = new ContextThemeWrapper(ctx, style);
838 
839         // Recreate the icon and set it if needed
840         TintedKeyButtonDrawable priorIcon = mRotateSuggestionIcon;
841         mRotateSuggestionIcon = getDrawable(rotateContext, R.drawable.ic_sysbar_rotate_button,
842                 lightColor, darkColor);
843 
844         // Apply any prior set dark intensity
845         if (priorIcon != null && priorIcon.isDarkIntensitySet()) {
846             mRotateSuggestionIcon.setDarkIntensity(priorIcon.getDarkIntensity());
847         }
848 
849         if (setIcon) getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
850     }
851 
setRotateButtonVisibility(final boolean visible)852     public int setRotateButtonVisibility(final boolean visible) {
853         // Never show if a11y is visible
854         final boolean adjVisible = visible && !mShowAccessibilityButton;
855         final int vis = adjVisible ? View.VISIBLE : View.INVISIBLE;
856 
857         // No need to do anything if the request matches the current state
858         if (vis == getRotateSuggestionButton().getVisibility()) return vis;
859 
860         getRotateSuggestionButton().setVisibility(vis);
861         mShowRotateButton = visible;
862 
863         // Stop any active animations if hidden
864         if (!visible) {
865             Drawable d = mRotateSuggestionIcon.getDrawable(0);
866             if (d instanceof AnimatedVectorDrawable) {
867                 AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d;
868                 avd.clearAnimationCallbacks();
869                 avd.reset();
870             }
871         }
872 
873         // Hide/restore other button visibility, if necessary
874         updateNavButtonIcons();
875 
876         // Return applied visibility
877         return vis;
878     }
879 
isRotateButtonVisible()880     public boolean isRotateButtonVisible() { return mShowRotateButton; }
881 
882     /**
883      * @return the button at the given {@param x} and {@param y}.
884      */
getButtonAtPosition(int x, int y)885     ButtonDispatcher getButtonAtPosition(int x, int y) {
886         for (int i = 0; i < mButtonDispatchers.size(); i++) {
887             ButtonDispatcher button = mButtonDispatchers.valueAt(i);
888             View buttonView = button.getCurrentView();
889             if (buttonView != null) {
890                 buttonView.getHitRect(mTmpRect);
891                 offsetDescendantRectToMyCoords(buttonView, mTmpRect);
892                 if (mTmpRect.contains(x, y)) {
893                     return button;
894                 }
895             }
896         }
897         return null;
898     }
899 
900     @Override
onFinishInflate()901     public void onFinishInflate() {
902         mNavigationInflaterView = findViewById(R.id.navigation_inflater);
903         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
904 
905         getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
906 
907         DockedStackExistsListener.register(mDockedListener);
908         updateRotatedViews();
909     }
910 
onDarkIntensityChange(float intensity)911     public void onDarkIntensityChange(float intensity) {
912         if (mGestureHelper != null) {
913             mGestureHelper.onDarkIntensityChange(intensity);
914         }
915     }
916 
917     @Override
onDraw(Canvas canvas)918     protected void onDraw(Canvas canvas) {
919         mGestureHelper.onDraw(canvas);
920         mDeadZone.onDraw(canvas);
921         super.onDraw(canvas);
922     }
923 
924     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)925     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
926         super.onLayout(changed, left, top, right, bottom);
927         updateButtonLocationOnScreen(getBackButton(), mBackButtonBounds);
928         updateButtonLocationOnScreen(getHomeButton(), mHomeButtonBounds);
929         updateButtonLocationOnScreen(getRecentsButton(), mRecentsButtonBounds);
930         updateButtonLocationOnScreen(getRotateSuggestionButton(), mRotationButtonBounds);
931         mGestureHelper.onLayout(changed, left, top, right, bottom);
932         mRecentsOnboarding.setNavBarHeight(getMeasuredHeight());
933     }
934 
updateButtonLocationOnScreen(ButtonDispatcher button, Rect buttonBounds)935     private void updateButtonLocationOnScreen(ButtonDispatcher button, Rect buttonBounds) {
936         View view = button.getCurrentView();
937         if (view == null) {
938             buttonBounds.setEmpty();
939             return;
940         }
941         // Temporarily reset the translation back to origin to get the position in window
942         final float posX = view.getTranslationX();
943         final float posY = view.getTranslationY();
944         view.setTranslationX(0);
945         view.setTranslationY(0);
946         view.getLocationInWindow(mTmpPosition);
947         buttonBounds.set(mTmpPosition[0], mTmpPosition[1],
948                 mTmpPosition[0] + view.getMeasuredWidth(),
949                 mTmpPosition[1] + view.getMeasuredHeight());
950         view.setTranslationX(posX);
951         view.setTranslationY(posY);
952     }
953 
updateRotatedViews()954     private void updateRotatedViews() {
955         mRotatedViews[Surface.ROTATION_0] =
956                 mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
957         mRotatedViews[Surface.ROTATION_270] =
958                 mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
959 
960         updateCurrentView();
961     }
962 
needsReorient(int rotation)963     public boolean needsReorient(int rotation) {
964         return mCurrentRotation != rotation;
965     }
966 
updateCurrentView()967     private void updateCurrentView() {
968         final int rot = mDisplay.getRotation();
969         for (int i=0; i<4; i++) {
970             mRotatedViews[i].setVisibility(View.GONE);
971         }
972         mCurrentView = mRotatedViews[rot];
973         mCurrentView.setVisibility(View.VISIBLE);
974         mNavigationInflaterView.setAlternativeOrder(rot == Surface.ROTATION_90);
975         mNavigationInflaterView.updateButtonDispatchersCurrentView();
976         updateLayoutTransitionsEnabled();
977         mCurrentRotation = rot;
978     }
979 
updateRecentsIcon()980     private void updateRecentsIcon() {
981         mDockedIcon.setRotation(mDockedStackExists && mVertical ? 90 : 0);
982         getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
983         mBarTransitions.reapplyDarkIntensity();
984     }
985 
isVertical()986     public boolean isVertical() {
987         return mVertical;
988     }
989 
reorient()990     public void reorient() {
991         updateCurrentView();
992 
993         ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
994         mDeadZone.onConfigurationChanged(mCurrentRotation);
995 
996         // force the low profile & disabled states into compliance
997         mBarTransitions.init();
998         setMenuVisibility(mShowMenu, true /* force */);
999 
1000         if (DEBUG) {
1001             Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
1002         }
1003 
1004         // Resolve layout direction if not resolved since components changing layout direction such
1005         // as changing languages will recreate this view and the direction will be resolved later
1006         if (!isLayoutDirectionResolved()) {
1007             resolveLayoutDirection();
1008         }
1009         updateTaskSwitchHelper();
1010         updateNavButtonIcons();
1011 
1012         getHomeButton().setVertical(mVertical);
1013     }
1014 
updateTaskSwitchHelper()1015     private void updateTaskSwitchHelper() {
1016         if (mGestureHelper == null) return;
1017         boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
1018         mGestureHelper.setBarState(mVertical, isRtl);
1019     }
1020 
1021     @Override
onSizeChanged(int w, int h, int oldw, int oldh)1022     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1023         if (DEBUG) Log.d(TAG, String.format(
1024                     "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh));
1025 
1026         final boolean newVertical = w > 0 && h > w;
1027         if (newVertical != mVertical) {
1028             mVertical = newVertical;
1029             //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n"));
1030             reorient();
1031             notifyVerticalChangedListener(newVertical);
1032         }
1033 
1034         postCheckForInvalidLayout("sizeChanged");
1035         super.onSizeChanged(w, h, oldw, oldh);
1036     }
1037 
notifyVerticalChangedListener(boolean newVertical)1038     private void notifyVerticalChangedListener(boolean newVertical) {
1039         if (mOnVerticalChangedListener != null) {
1040             mOnVerticalChangedListener.onVerticalChanged(newVertical);
1041         }
1042     }
1043 
1044     @Override
onConfigurationChanged(Configuration newConfig)1045     protected void onConfigurationChanged(Configuration newConfig) {
1046         super.onConfigurationChanged(newConfig);
1047         boolean uiCarModeChanged = updateCarMode(newConfig);
1048         updateTaskSwitchHelper();
1049         updateIcons(getContext(), mConfiguration, newConfig);
1050         updateRecentsIcon();
1051         mRecentsOnboarding.onConfigurationChanged(newConfig);
1052         if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
1053                 || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
1054             // If car mode or density changes, we need to reset the icons.
1055             updateNavButtonIcons();
1056         }
1057         mConfiguration.updateFrom(newConfig);
1058     }
1059 
1060     /**
1061      * If the configuration changed, update the carmode and return that it was updated.
1062      */
updateCarMode(Configuration newConfig)1063     private boolean updateCarMode(Configuration newConfig) {
1064         boolean uiCarModeChanged = false;
1065         if (newConfig != null) {
1066             int uiMode = newConfig.uiMode & Configuration.UI_MODE_TYPE_MASK;
1067             final boolean isCarMode = (uiMode == Configuration.UI_MODE_TYPE_CAR);
1068 
1069             if (isCarMode != mInCarMode) {
1070                 mInCarMode = isCarMode;
1071                 if (ALTERNATE_CAR_MODE_UI) {
1072                     mUseCarModeUi = isCarMode;
1073                     uiCarModeChanged = true;
1074                 } else {
1075                     // Don't use car mode behavior if ALTERNATE_CAR_MODE_UI not set.
1076                     mUseCarModeUi = false;
1077                 }
1078             }
1079         }
1080         return uiCarModeChanged;
1081     }
1082 
1083     /*
1084     @Override
1085     protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
1086         if (DEBUG) Log.d(TAG, String.format(
1087                     "onLayout: %s (%d,%d,%d,%d)",
1088                     changed?"changed":"notchanged", left, top, right, bottom));
1089         super.onLayout(changed, left, top, right, bottom);
1090     }
1091 
1092     // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else
1093     // fails, any touch on the display will fix the layout.
1094     @Override
1095     public boolean onInterceptTouchEvent(MotionEvent ev) {
1096         if (DEBUG) Log.d(TAG, "onInterceptTouchEvent: " + ev.toString());
1097         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1098             postCheckForInvalidLayout("touch");
1099         }
1100         return super.onInterceptTouchEvent(ev);
1101     }
1102     */
1103 
1104 
getResourceName(int resId)1105     private String getResourceName(int resId) {
1106         if (resId != 0) {
1107             final android.content.res.Resources res = getContext().getResources();
1108             try {
1109                 return res.getResourceName(resId);
1110             } catch (android.content.res.Resources.NotFoundException ex) {
1111                 return "(unknown)";
1112             }
1113         } else {
1114             return "(null)";
1115         }
1116     }
1117 
postCheckForInvalidLayout(final String how)1118     private void postCheckForInvalidLayout(final String how) {
1119         mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget();
1120     }
1121 
visibilityToString(int vis)1122     private static String visibilityToString(int vis) {
1123         switch (vis) {
1124             case View.INVISIBLE:
1125                 return "INVISIBLE";
1126             case View.GONE:
1127                 return "GONE";
1128             default:
1129                 return "VISIBLE";
1130         }
1131     }
1132 
1133     @Override
onAttachedToWindow()1134     protected void onAttachedToWindow() {
1135         super.onAttachedToWindow();
1136         requestApplyInsets();
1137         reorient();
1138         onPluginDisconnected(null); // Create default gesture helper
1139         Dependency.get(PluginManager.class).addPluginListener(this,
1140                 NavGesture.class, false /* Only one */);
1141         setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
1142     }
1143 
1144     @Override
onDetachedFromWindow()1145     protected void onDetachedFromWindow() {
1146         super.onDetachedFromWindow();
1147         Dependency.get(PluginManager.class).removePluginListener(this);
1148         if (mGestureHelper != null) {
1149             mGestureHelper.destroy();
1150         }
1151         setUpSwipeUpOnboarding(false);
1152     }
1153 
setUpSwipeUpOnboarding(boolean connectedToOverviewProxy)1154     private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
1155         if (connectedToOverviewProxy) {
1156             mRecentsOnboarding.onConnectedToLauncher();
1157         } else {
1158             mRecentsOnboarding.onDisconnectedFromLauncher();
1159         }
1160     }
1161 
1162     @Override
onPluginConnected(NavGesture plugin, Context context)1163     public void onPluginConnected(NavGesture plugin, Context context) {
1164         mGestureHelper = plugin.getGestureHelper();
1165         updateTaskSwitchHelper();
1166     }
1167 
1168     @Override
onPluginDisconnected(NavGesture plugin)1169     public void onPluginDisconnected(NavGesture plugin) {
1170         NavigationBarGestureHelper defaultHelper = new NavigationBarGestureHelper(getContext());
1171         defaultHelper.setComponents(mRecentsComponent, mDivider, this);
1172         if (mGestureHelper != null) {
1173             mGestureHelper.destroy();
1174         }
1175         mGestureHelper = defaultHelper;
1176         updateTaskSwitchHelper();
1177     }
1178 
dump(FileDescriptor fd, PrintWriter pw, String[] args)1179     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1180         pw.println("NavigationBarView {");
1181         final Rect r = new Rect();
1182         final Point size = new Point();
1183         mDisplay.getRealSize(size);
1184 
1185         pw.println(String.format("      this: " + StatusBar.viewInfo(this)
1186                         + " " + visibilityToString(getVisibility())));
1187 
1188         getWindowVisibleDisplayFrame(r);
1189         final boolean offscreen = r.right > size.x || r.bottom > size.y;
1190         pw.println("      window: "
1191                 + r.toShortString()
1192                 + " " + visibilityToString(getWindowVisibility())
1193                 + (offscreen ? " OFFSCREEN!" : ""));
1194 
1195         pw.println(String.format("      mCurrentView: id=%s (%dx%d) %s %f",
1196                         getResourceName(getCurrentView().getId()),
1197                         getCurrentView().getWidth(), getCurrentView().getHeight(),
1198                         visibilityToString(getCurrentView().getVisibility()),
1199                         getCurrentView().getAlpha()));
1200 
1201         pw.println(String.format("      disabled=0x%08x vertical=%s menu=%s",
1202                         mDisabledFlags,
1203                         mVertical ? "true" : "false",
1204                         mShowMenu ? "true" : "false"));
1205 
1206         dumpButton(pw, "back", getBackButton());
1207         dumpButton(pw, "home", getHomeButton());
1208         dumpButton(pw, "rcnt", getRecentsButton());
1209         dumpButton(pw, "menu", getMenuButton());
1210         dumpButton(pw, "a11y", getAccessibilityButton());
1211 
1212         mRecentsOnboarding.dump(pw);
1213 
1214         pw.println("    }");
1215     }
1216 
1217     @Override
onApplyWindowInsets(WindowInsets insets)1218     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1219         setPadding(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
1220                 insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
1221         return super.onApplyWindowInsets(insets);
1222     }
1223 
dumpButton(PrintWriter pw, String caption, ButtonDispatcher button)1224     private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
1225         pw.print("      " + caption + ": ");
1226         if (button == null) {
1227             pw.print("null");
1228         } else {
1229             pw.print(visibilityToString(button.getVisibility())
1230                     + " alpha=" + button.getAlpha()
1231                     );
1232         }
1233         pw.println();
1234     }
1235 
1236     public interface OnVerticalChangedListener {
onVerticalChanged(boolean isVertical)1237         void onVerticalChanged(boolean isVertical);
1238     }
1239 
1240     private final Consumer<Boolean> mDockedListener = exists -> mHandler.post(() -> {
1241         mDockedStackExists = exists;
1242         updateRecentsIcon();
1243     });
1244 }
1245