Home | History | Annotate | Download | only in phone
      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
    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
    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 
    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 {
    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 
    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 
    305     public BarTransitions getBarTransitions() {
    306         return mBarTransitions;
    307     }
    308 
    309     public LightBarTransitionsController getLightTransitionsController() {
    310         return mBarTransitions.getLightTransitionsController();
    311     }
    312 
    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 
    324     public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
    325         mOnVerticalChangedListener = onVerticalChangedListener;
    326         notifyVerticalChangedListener(mVertical);
    327     }
    328 
    329     @Override
    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
    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 
    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 
    385     public @NavigationBarCompat.HitTarget int getDownHitTarget() {
    386         return mDownHitTarget;
    387     }
    388 
    389     public void abortCurrentGesture() {
    390         getHomeButton().abortCurrentGesture();
    391     }
    392 
    393     private H mHandler = new H();
    394 
    395     public View getCurrentView() {
    396         return mCurrentView;
    397     }
    398 
    399     public View[] getAllViews() {
    400         return mRotatedViews;
    401     }
    402 
    403     public ButtonDispatcher getRecentsButton() {
    404         return mButtonDispatchers.get(R.id.recent_apps);
    405     }
    406 
    407     public ButtonDispatcher getMenuButton() {
    408         return mButtonDispatchers.get(R.id.menu);
    409     }
    410 
    411     public ButtonDispatcher getBackButton() {
    412         return mButtonDispatchers.get(R.id.back);
    413     }
    414 
    415     public ButtonDispatcher getHomeButton() {
    416         return mButtonDispatchers.get(R.id.home);
    417     }
    418 
    419     public ButtonDispatcher getImeSwitchButton() {
    420         return mButtonDispatchers.get(R.id.ime_switcher);
    421     }
    422 
    423     public ButtonDispatcher getAccessibilityButton() {
    424         return mButtonDispatchers.get(R.id.accessibility_button);
    425     }
    426 
    427     public ButtonDispatcher getRotateSuggestionButton() {
    428         return mButtonDispatchers.get(R.id.rotate_suggestion);
    429     }
    430 
    431     public ButtonDispatcher getMenuContainer() {
    432         return mButtonDispatchers.get(R.id.menu_container);
    433     }
    434 
    435     public SparseArray<ButtonDispatcher> getButtonDispatchers() {
    436         return mButtonDispatchers;
    437     }
    438 
    439     public boolean isRecentsButtonVisible() {
    440         return getRecentsButton().getVisibility() == View.VISIBLE;
    441     }
    442 
    443     public boolean isOverviewEnabled() {
    444         return (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) == 0;
    445     }
    446 
    447     public boolean isQuickStepSwipeUpEnabled() {
    448         return mOverviewProxyService.shouldShowSwipeUpUI() && isOverviewEnabled();
    449     }
    450 
    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.
    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 
    469     private void reloadNavIcons() {
    470         updateIcons(mContext, Configuration.EMPTY, mConfiguration);
    471     }
    472 
    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 
    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 
    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 
    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 
    528     private void orientHomeButton(KeyButtonDrawable drawable) {
    529         drawable.setRotation(mVertical ? 90 : 0);
    530     }
    531 
    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 
    540     private KeyButtonDrawable getDrawable(Context lightContext, Context darkContext,
    541             @DrawableRes int icon) {
    542         return getDrawable(lightContext, darkContext, icon, true /* hasShadow */);
    543     }
    544 
    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 
    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 
    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
    564     public void setLayoutDirection(int layoutDirection) {
    565         reloadNavIcons();
    566 
    567         super.setLayoutDirection(layoutDirection);
    568     }
    569 
    570     private KeyButtonDrawable getBackIconWithAlt(boolean carMode, boolean landscape) {
    571         return landscape
    572                 ? carMode ? mBackAltLandCarModeIcon : mBackIcon
    573                 : carMode ? mBackAltCarModeIcon : mBackIcon;
    574     }
    575 
    576     private KeyButtonDrawable getBackIcon(boolean carMode, boolean landscape) {
    577         return landscape
    578                 ? carMode ? mBackLandCarModeIcon : mBackIcon
    579                 : carMode ? mBackCarModeIcon : mBackIcon;
    580     }
    581 
    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 
    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 
    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 
    691     public boolean inScreenPinning() {
    692         return ActivityManagerWrapper.getInstance().isScreenPinningActive();
    693     }
    694 
    695     public void setLayoutTransitionsEnabled(boolean enabled) {
    696         mLayoutTransitionsEnabled = enabled;
    697         updateLayoutTransitionsEnabled();
    698     }
    699 
    700     public void setWakeAndUnlocking(boolean wakeAndUnlocking) {
    701         setUseFadingAnimations(wakeAndUnlocking);
    702         mWakeAndUnlocking = wakeAndUnlocking;
    703         updateLayoutTransitionsEnabled();
    704     }
    705 
    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 
    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 
    742     public void onNavigationButtonLongPress(View v) {
    743         mGestureHelper.onNavigationButtonLongPress(v);
    744     }
    745 
    746     public void onPanelExpandedChange(boolean expanded) {
    747         updateSlippery();
    748     }
    749 
    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 
    767     private void updateSlippery() {
    768         setSlippery(!isQuickStepSwipeUpEnabled() || mPanelView.isFullyExpanded());
    769     }
    770 
    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 
    792     public void setMenuVisibility(final boolean show) {
    793         setMenuVisibility(show, false);
    794     }
    795 
    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 
    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 
    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 
    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 
    880     public boolean isRotateButtonVisible() { return mShowRotateButton; }
    881 
    882     /**
    883      * @return the button at the given {@param x} and {@param y}.
    884      */
    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
    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 
    911     public void onDarkIntensityChange(float intensity) {
    912         if (mGestureHelper != null) {
    913             mGestureHelper.onDarkIntensityChange(intensity);
    914         }
    915     }
    916 
    917     @Override
    918     protected void onDraw(Canvas canvas) {
    919         mGestureHelper.onDraw(canvas);
    920         mDeadZone.onDraw(canvas);
    921         super.onDraw(canvas);
    922     }
    923 
    924     @Override
    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 
    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 
    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 
    963     public boolean needsReorient(int rotation) {
    964         return mCurrentRotation != rotation;
    965     }
    966 
    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 
    980     private void updateRecentsIcon() {
    981         mDockedIcon.setRotation(mDockedStackExists && mVertical ? 90 : 0);
    982         getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
    983         mBarTransitions.reapplyDarkIntensity();
    984     }
    985 
    986     public boolean isVertical() {
    987         return mVertical;
    988     }
    989 
    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 
   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
   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 
   1038     private void notifyVerticalChangedListener(boolean newVertical) {
   1039         if (mOnVerticalChangedListener != null) {
   1040             mOnVerticalChangedListener.onVerticalChanged(newVertical);
   1041         }
   1042     }
   1043 
   1044     @Override
   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      */
   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 
   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 
   1118     private void postCheckForInvalidLayout(final String how) {
   1119         mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget();
   1120     }
   1121 
   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
   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
   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 
   1154     private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
   1155         if (connectedToOverviewProxy) {
   1156             mRecentsOnboarding.onConnectedToLauncher();
   1157         } else {
   1158             mRecentsOnboarding.onDisconnectedFromLauncher();
   1159         }
   1160     }
   1161 
   1162     @Override
   1163     public void onPluginConnected(NavGesture plugin, Context context) {
   1164         mGestureHelper = plugin.getGestureHelper();
   1165         updateTaskSwitchHelper();
   1166     }
   1167 
   1168     @Override
   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 
   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
   1218     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
   1219         setPadding(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
   1220                 insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
   1221         return super.onApplyWindowInsets(insets);
   1222     }
   1223 
   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 {
   1237         void onVerticalChanged(boolean isVertical);
   1238     }
   1239 
   1240     private final Consumer<Boolean> mDockedListener = exists -> mHandler.post(() -> {
   1241         mDockedStackExists = exists;
   1242         updateRecentsIcon();
   1243     });
   1244 }
   1245