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 android.animation.LayoutTransition;
     20 import android.animation.LayoutTransition.TransitionListener;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.TimeInterpolator;
     23 import android.animation.ValueAnimator;
     24 import android.annotation.DrawableRes;
     25 import android.app.ActivityManager;
     26 import android.app.StatusBarManager;
     27 import android.content.Context;
     28 import android.content.res.Configuration;
     29 import android.graphics.Point;
     30 import android.graphics.Rect;
     31 import android.os.Handler;
     32 import android.os.Message;
     33 import android.os.RemoteException;
     34 import android.util.AttributeSet;
     35 import android.util.Log;
     36 import android.util.SparseArray;
     37 import android.view.ContextThemeWrapper;
     38 import android.view.Display;
     39 import android.view.MotionEvent;
     40 import android.view.Surface;
     41 import android.view.View;
     42 import android.view.ViewGroup;
     43 import android.view.WindowManager;
     44 import android.view.inputmethod.InputMethodManager;
     45 import android.widget.FrameLayout;
     46 
     47 import com.android.settingslib.Utils;
     48 import com.android.systemui.Dependency;
     49 import com.android.systemui.DockedStackExistsListener;
     50 import com.android.systemui.R;
     51 import com.android.systemui.RecentsComponent;
     52 import com.android.systemui.plugins.PluginListener;
     53 import com.android.systemui.plugins.PluginManager;
     54 import com.android.systemui.plugins.statusbar.phone.NavGesture;
     55 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
     56 import com.android.systemui.stackdivider.Divider;
     57 import com.android.systemui.statusbar.policy.DeadZone;
     58 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
     59 
     60 import java.io.FileDescriptor;
     61 import java.io.PrintWriter;
     62 import java.util.function.Consumer;
     63 
     64 public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> {
     65     final static boolean DEBUG = false;
     66     final static String TAG = "StatusBar/NavBarView";
     67 
     68     // slippery nav bar when everything is disabled, e.g. during setup
     69     final static boolean SLIPPERY_WHEN_DISABLED = true;
     70 
     71     final static boolean ALTERNATE_CAR_MODE_UI = false;
     72 
     73     final Display mDisplay;
     74     View mCurrentView = null;
     75     View[] mRotatedViews = new View[4];
     76 
     77     boolean mVertical;
     78     private int mCurrentRotation = -1;
     79 
     80     boolean mShowMenu;
     81     boolean mShowAccessibilityButton;
     82     boolean mLongClickableAccessibilityButton;
     83     int mDisabledFlags = 0;
     84     int mNavigationIconHints = 0;
     85 
     86     private KeyButtonDrawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon;
     87     private KeyButtonDrawable mBackCarModeIcon, mBackLandCarModeIcon;
     88     private KeyButtonDrawable mBackAltCarModeIcon, mBackAltLandCarModeIcon;
     89     private KeyButtonDrawable mHomeDefaultIcon, mHomeCarModeIcon;
     90     private KeyButtonDrawable mRecentIcon;
     91     private KeyButtonDrawable mDockedIcon;
     92     private KeyButtonDrawable mImeIcon;
     93     private KeyButtonDrawable mMenuIcon;
     94     private KeyButtonDrawable mAccessibilityIcon;
     95 
     96     private GestureHelper mGestureHelper;
     97     private DeadZone mDeadZone;
     98     private final NavigationBarTransitions mBarTransitions;
     99 
    100     // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
    101     final static boolean WORKAROUND_INVALID_LAYOUT = true;
    102     final static int MSG_CHECK_INVALID_LAYOUT = 8686;
    103 
    104     // performs manual animation in sync with layout transitions
    105     private final NavTransitionListener mTransitionListener = new NavTransitionListener();
    106 
    107     private OnVerticalChangedListener mOnVerticalChangedListener;
    108     private boolean mLayoutTransitionsEnabled = true;
    109     private boolean mWakeAndUnlocking;
    110     private boolean mUseCarModeUi = false;
    111     private boolean mInCarMode = false;
    112     private boolean mDockedStackExists;
    113 
    114     private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
    115     private Configuration mConfiguration;
    116 
    117     private NavigationBarInflaterView mNavigationInflaterView;
    118     private RecentsComponent mRecentsComponent;
    119     private Divider mDivider;
    120 
    121     private class NavTransitionListener implements TransitionListener {
    122         private boolean mBackTransitioning;
    123         private boolean mHomeAppearing;
    124         private long mStartDelay;
    125         private long mDuration;
    126         private TimeInterpolator mInterpolator;
    127 
    128         @Override
    129         public void startTransition(LayoutTransition transition, ViewGroup container,
    130                 View view, int transitionType) {
    131             if (view.getId() == R.id.back) {
    132                 mBackTransitioning = true;
    133             } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
    134                 mHomeAppearing = true;
    135                 mStartDelay = transition.getStartDelay(transitionType);
    136                 mDuration = transition.getDuration(transitionType);
    137                 mInterpolator = transition.getInterpolator(transitionType);
    138             }
    139         }
    140 
    141         @Override
    142         public void endTransition(LayoutTransition transition, ViewGroup container,
    143                 View view, int transitionType) {
    144             if (view.getId() == R.id.back) {
    145                 mBackTransitioning = false;
    146             } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
    147                 mHomeAppearing = false;
    148             }
    149         }
    150 
    151         public void onBackAltCleared() {
    152             ButtonDispatcher backButton = getBackButton();
    153 
    154             // When dismissing ime during unlock, force the back button to run the same appearance
    155             // animation as home (if we catch this condition early enough).
    156             if (!mBackTransitioning && backButton.getVisibility() == VISIBLE
    157                     && mHomeAppearing && getHomeButton().getAlpha() == 0) {
    158                 getBackButton().setAlpha(0);
    159                 ValueAnimator a = ObjectAnimator.ofFloat(backButton, "alpha", 0, 1);
    160                 a.setStartDelay(mStartDelay);
    161                 a.setDuration(mDuration);
    162                 a.setInterpolator(mInterpolator);
    163                 a.start();
    164             }
    165         }
    166     }
    167 
    168     private final OnClickListener mImeSwitcherClickListener = new OnClickListener() {
    169         @Override
    170         public void onClick(View view) {
    171             mContext.getSystemService(InputMethodManager.class)
    172                     .showInputMethodPicker(true /* showAuxiliarySubtypes */);
    173         }
    174     };
    175 
    176     private class H extends Handler {
    177         public void handleMessage(Message m) {
    178             switch (m.what) {
    179                 case MSG_CHECK_INVALID_LAYOUT:
    180                     final String how = "" + m.obj;
    181                     final int w = getWidth();
    182                     final int h = getHeight();
    183                     final int vw = getCurrentView().getWidth();
    184                     final int vh = getCurrentView().getHeight();
    185 
    186                     if (h != vh || w != vw) {
    187                         Log.w(TAG, String.format(
    188                             "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)",
    189                             how, w, h, vw, vh));
    190                         if (WORKAROUND_INVALID_LAYOUT) {
    191                             requestLayout();
    192                         }
    193                     }
    194                     break;
    195             }
    196         }
    197     }
    198 
    199     public NavigationBarView(Context context, AttributeSet attrs) {
    200         super(context, attrs);
    201 
    202         mDisplay = ((WindowManager) context.getSystemService(
    203                 Context.WINDOW_SERVICE)).getDefaultDisplay();
    204 
    205         mVertical = false;
    206         mShowMenu = false;
    207 
    208         mShowAccessibilityButton = false;
    209         mLongClickableAccessibilityButton = false;
    210 
    211         mConfiguration = new Configuration();
    212         mConfiguration.updateFrom(context.getResources().getConfiguration());
    213         updateIcons(context, Configuration.EMPTY, mConfiguration);
    214 
    215         mBarTransitions = new NavigationBarTransitions(this);
    216 
    217         mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
    218         mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
    219         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
    220         mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
    221         mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
    222         mButtonDispatchers.put(R.id.accessibility_button,
    223                 new ButtonDispatcher(R.id.accessibility_button));
    224     }
    225 
    226     public BarTransitions getBarTransitions() {
    227         return mBarTransitions;
    228     }
    229 
    230     public LightBarTransitionsController getLightTransitionsController() {
    231         return mBarTransitions.getLightTransitionsController();
    232     }
    233 
    234     public void setComponents(RecentsComponent recentsComponent, Divider divider) {
    235         mRecentsComponent = recentsComponent;
    236         mDivider = divider;
    237         if (mGestureHelper instanceof NavigationBarGestureHelper) {
    238             ((NavigationBarGestureHelper) mGestureHelper).setComponents(
    239                     recentsComponent, divider, this);
    240         }
    241     }
    242 
    243     public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
    244         mOnVerticalChangedListener = onVerticalChangedListener;
    245         notifyVerticalChangedListener(mVertical);
    246     }
    247 
    248     @Override
    249     public boolean onTouchEvent(MotionEvent event) {
    250         if (mGestureHelper.onTouchEvent(event)) {
    251             return true;
    252         }
    253         return super.onTouchEvent(event);
    254     }
    255 
    256     @Override
    257     public boolean onInterceptTouchEvent(MotionEvent event) {
    258         return mGestureHelper.onInterceptTouchEvent(event);
    259     }
    260 
    261     public void abortCurrentGesture() {
    262         getHomeButton().abortCurrentGesture();
    263     }
    264 
    265     private H mHandler = new H();
    266 
    267     public View getCurrentView() {
    268         return mCurrentView;
    269     }
    270 
    271     public View[] getAllViews() {
    272         return mRotatedViews;
    273     }
    274 
    275     public ButtonDispatcher getRecentsButton() {
    276         return mButtonDispatchers.get(R.id.recent_apps);
    277     }
    278 
    279     public ButtonDispatcher getMenuButton() {
    280         return mButtonDispatchers.get(R.id.menu);
    281     }
    282 
    283     public ButtonDispatcher getBackButton() {
    284         return mButtonDispatchers.get(R.id.back);
    285     }
    286 
    287     public ButtonDispatcher getHomeButton() {
    288         return mButtonDispatchers.get(R.id.home);
    289     }
    290 
    291     public ButtonDispatcher getImeSwitchButton() {
    292         return mButtonDispatchers.get(R.id.ime_switcher);
    293     }
    294 
    295     public ButtonDispatcher getAccessibilityButton() {
    296         return mButtonDispatchers.get(R.id.accessibility_button);
    297     }
    298 
    299     public SparseArray<ButtonDispatcher> getButtonDispatchers() {
    300         return mButtonDispatchers;
    301     }
    302 
    303     private void updateCarModeIcons(Context ctx) {
    304         mBackCarModeIcon = getDrawable(ctx,
    305                 R.drawable.ic_sysbar_back_carmode, R.drawable.ic_sysbar_back_carmode);
    306         mBackLandCarModeIcon = mBackCarModeIcon;
    307         mBackAltCarModeIcon = getDrawable(ctx,
    308                 R.drawable.ic_sysbar_back_ime_carmode, R.drawable.ic_sysbar_back_ime_carmode);
    309         mBackAltLandCarModeIcon = mBackAltCarModeIcon;
    310         mHomeCarModeIcon = getDrawable(ctx,
    311                 R.drawable.ic_sysbar_home_carmode, R.drawable.ic_sysbar_home_carmode);
    312     }
    313 
    314     private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
    315         if (oldConfig.orientation != newConfig.orientation
    316                 || oldConfig.densityDpi != newConfig.densityDpi) {
    317             mDockedIcon = getDrawable(ctx,
    318                     R.drawable.ic_sysbar_docked, R.drawable.ic_sysbar_docked_dark);
    319         }
    320         if (oldConfig.densityDpi != newConfig.densityDpi
    321                 || oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) {
    322             mBackIcon = getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark);
    323             mBackLandIcon = mBackIcon;
    324             mBackAltIcon = getDrawable(ctx,
    325                     R.drawable.ic_sysbar_back_ime, R.drawable.ic_sysbar_back_ime_dark);
    326             mBackAltLandIcon = mBackAltIcon;
    327 
    328             mHomeDefaultIcon = getDrawable(ctx,
    329                     R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark);
    330             mRecentIcon = getDrawable(ctx,
    331                     R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
    332             mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark);
    333             mAccessibilityIcon = getDrawable(ctx, R.drawable.ic_sysbar_accessibility_button,
    334                     R.drawable.ic_sysbar_accessibility_button_dark);
    335 
    336             int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
    337             int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
    338             Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
    339             Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
    340             mImeIcon = getDrawable(darkContext, lightContext,
    341                     R.drawable.ic_ime_switcher_default, R.drawable.ic_ime_switcher_default);
    342 
    343             if (ALTERNATE_CAR_MODE_UI) {
    344                 updateCarModeIcons(ctx);
    345             }
    346         }
    347     }
    348 
    349     private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int lightIcon,
    350             @DrawableRes int darkIcon) {
    351         return getDrawable(ctx, ctx, lightIcon, darkIcon);
    352     }
    353 
    354     private KeyButtonDrawable getDrawable(Context darkContext, Context lightContext,
    355             @DrawableRes int lightIcon, @DrawableRes int darkIcon) {
    356         return KeyButtonDrawable.create(lightContext.getDrawable(lightIcon),
    357                 darkContext.getDrawable(darkIcon));
    358     }
    359 
    360     @Override
    361     public void setLayoutDirection(int layoutDirection) {
    362         // Reload all the icons
    363         updateIcons(getContext(), Configuration.EMPTY, mConfiguration);
    364 
    365         super.setLayoutDirection(layoutDirection);
    366     }
    367 
    368     public void notifyScreenOn() {
    369         setDisabledFlags(mDisabledFlags, true);
    370     }
    371 
    372     public void setNavigationIconHints(int hints) {
    373         setNavigationIconHints(hints, false);
    374     }
    375 
    376     private KeyButtonDrawable getBackIconWithAlt(boolean carMode, boolean landscape) {
    377         return landscape
    378                 ? carMode ? mBackAltLandCarModeIcon : mBackAltLandIcon
    379                 : carMode ? mBackAltCarModeIcon : mBackAltIcon;
    380     }
    381 
    382     private KeyButtonDrawable getBackIcon(boolean carMode, boolean landscape) {
    383         return landscape
    384                 ? carMode ? mBackLandCarModeIcon : mBackLandIcon
    385                 : carMode ? mBackCarModeIcon : mBackIcon;
    386     }
    387 
    388     public void setNavigationIconHints(int hints, boolean force) {
    389         if (!force && hints == mNavigationIconHints) return;
    390         final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
    391         if ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0 && !backAlt) {
    392             mTransitionListener.onBackAltCleared();
    393         }
    394         if (DEBUG) {
    395             android.widget.Toast.makeText(getContext(),
    396                 "Navigation icon hints = " + hints,
    397                 500).show();
    398         }
    399 
    400         mNavigationIconHints = hints;
    401 
    402         // We have to replace or restore the back and home button icons when exiting or entering
    403         // carmode, respectively. Recents are not available in CarMode in nav bar so change
    404         // to recent icon is not required.
    405         KeyButtonDrawable backIcon = (backAlt)
    406                 ? getBackIconWithAlt(mUseCarModeUi, mVertical)
    407                 : getBackIcon(mUseCarModeUi, mVertical);
    408 
    409         getBackButton().setImageDrawable(backIcon);
    410 
    411         updateRecentsIcon();
    412 
    413         if (mUseCarModeUi) {
    414             getHomeButton().setImageDrawable(mHomeCarModeIcon);
    415         } else {
    416             getHomeButton().setImageDrawable(mHomeDefaultIcon);
    417         }
    418 
    419         // The Accessibility button always overrides the appearance of the IME switcher
    420         final boolean showImeButton =
    421                 !mShowAccessibilityButton && ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN)
    422                         != 0);
    423         getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
    424         getImeSwitchButton().setImageDrawable(mImeIcon);
    425 
    426         // Update menu button in case the IME state has changed.
    427         setMenuVisibility(mShowMenu, true);
    428         getMenuButton().setImageDrawable(mMenuIcon);
    429 
    430         setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton);
    431         getAccessibilityButton().setImageDrawable(mAccessibilityIcon);
    432 
    433         setDisabledFlags(mDisabledFlags, true);
    434 
    435         mBarTransitions.reapplyDarkIntensity();
    436     }
    437 
    438     public void setDisabledFlags(int disabledFlags) {
    439         setDisabledFlags(disabledFlags, false);
    440     }
    441 
    442     public void setDisabledFlags(int disabledFlags, boolean force) {
    443         if (!force && mDisabledFlags == disabledFlags) return;
    444 
    445         mDisabledFlags = disabledFlags;
    446 
    447         final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
    448 
    449         // Always disable recents when alternate car mode UI is active.
    450         boolean disableRecent = mUseCarModeUi
    451                         || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
    452         final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
    453                 && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
    454 
    455         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
    456         if (navButtons != null) {
    457             LayoutTransition lt = navButtons.getLayoutTransition();
    458             if (lt != null) {
    459                 if (!lt.getTransitionListeners().contains(mTransitionListener)) {
    460                     lt.addTransitionListener(mTransitionListener);
    461                 }
    462             }
    463         }
    464         if (inLockTask() && disableRecent && !disableHome) {
    465             // Don't hide recents when in lock task, it is used for exiting.
    466             // Unless home is hidden, then in DPM locked mode and no exit available.
    467             disableRecent = false;
    468         }
    469 
    470         getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
    471         getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
    472         getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
    473     }
    474 
    475     private boolean inLockTask() {
    476         try {
    477             return ActivityManager.getService().isInLockTaskMode();
    478         } catch (RemoteException e) {
    479             return false;
    480         }
    481     }
    482 
    483     public void setLayoutTransitionsEnabled(boolean enabled) {
    484         mLayoutTransitionsEnabled = enabled;
    485         updateLayoutTransitionsEnabled();
    486     }
    487 
    488     public void setWakeAndUnlocking(boolean wakeAndUnlocking) {
    489         setUseFadingAnimations(wakeAndUnlocking);
    490         mWakeAndUnlocking = wakeAndUnlocking;
    491         updateLayoutTransitionsEnabled();
    492     }
    493 
    494     private void updateLayoutTransitionsEnabled() {
    495         boolean enabled = !mWakeAndUnlocking && mLayoutTransitionsEnabled;
    496         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
    497         LayoutTransition lt = navButtons.getLayoutTransition();
    498         if (lt != null) {
    499             if (enabled) {
    500                 lt.enableTransitionType(LayoutTransition.APPEARING);
    501                 lt.enableTransitionType(LayoutTransition.DISAPPEARING);
    502                 lt.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
    503                 lt.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
    504             } else {
    505                 lt.disableTransitionType(LayoutTransition.APPEARING);
    506                 lt.disableTransitionType(LayoutTransition.DISAPPEARING);
    507                 lt.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
    508                 lt.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
    509             }
    510         }
    511     }
    512 
    513     private void setUseFadingAnimations(boolean useFadingAnimations) {
    514         WindowManager.LayoutParams lp = (WindowManager.LayoutParams) ((ViewGroup) getParent())
    515                 .getLayoutParams();
    516         if (lp != null) {
    517             boolean old = lp.windowAnimations != 0;
    518             if (!old && useFadingAnimations) {
    519                 lp.windowAnimations = R.style.Animation_NavigationBarFadeIn;
    520             } else if (old && !useFadingAnimations) {
    521                 lp.windowAnimations = 0;
    522             } else {
    523                 return;
    524             }
    525             WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
    526             wm.updateViewLayout((View) getParent(), lp);
    527         }
    528     }
    529 
    530     public void setMenuVisibility(final boolean show) {
    531         setMenuVisibility(show, false);
    532     }
    533 
    534     public void setMenuVisibility(final boolean show, final boolean force) {
    535         if (!force && mShowMenu == show) return;
    536 
    537         mShowMenu = show;
    538 
    539         // Only show Menu if IME switcher and Accessibility button not shown.
    540         final boolean shouldShow = mShowMenu && !mShowAccessibilityButton &&
    541                 ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
    542 
    543         getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
    544     }
    545 
    546     public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
    547         mShowAccessibilityButton = visible;
    548         mLongClickableAccessibilityButton = longClickable;
    549         if (visible) {
    550             // Accessibility button overrides Menu and IME switcher buttons.
    551             setMenuVisibility(false, true);
    552             getImeSwitchButton().setVisibility(View.INVISIBLE);
    553         }
    554 
    555         getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
    556         getAccessibilityButton().setLongClickable(longClickable);
    557     }
    558 
    559     @Override
    560     public void onFinishInflate() {
    561         mNavigationInflaterView = (NavigationBarInflaterView) findViewById(
    562                 R.id.navigation_inflater);
    563         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
    564 
    565         getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
    566 
    567         DockedStackExistsListener.register(mDockedListener);
    568         updateRotatedViews();
    569     }
    570 
    571     private void updateRotatedViews() {
    572         mRotatedViews[Surface.ROTATION_0] =
    573                 mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
    574         mRotatedViews[Surface.ROTATION_270] =
    575                 mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
    576 
    577         updateCurrentView();
    578     }
    579 
    580     public boolean needsReorient(int rotation) {
    581         return mCurrentRotation != rotation;
    582     }
    583 
    584     private void updateCurrentView() {
    585         final int rot = mDisplay.getRotation();
    586         for (int i=0; i<4; i++) {
    587             mRotatedViews[i].setVisibility(View.GONE);
    588         }
    589         mCurrentView = mRotatedViews[rot];
    590         mCurrentView.setVisibility(View.VISIBLE);
    591         mNavigationInflaterView.setAlternativeOrder(rot == Surface.ROTATION_90);
    592         for (int i = 0; i < mButtonDispatchers.size(); i++) {
    593             mButtonDispatchers.valueAt(i).setCurrentView(mCurrentView);
    594         }
    595         updateLayoutTransitionsEnabled();
    596         mCurrentRotation = rot;
    597     }
    598 
    599     private void updateRecentsIcon() {
    600         getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
    601         mBarTransitions.reapplyDarkIntensity();
    602     }
    603 
    604     public boolean isVertical() {
    605         return mVertical;
    606     }
    607 
    608     public void reorient() {
    609         updateCurrentView();
    610 
    611         mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);
    612 
    613         ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
    614         mDeadZone.setDisplayRotation(mCurrentRotation);
    615 
    616         // force the low profile & disabled states into compliance
    617         mBarTransitions.init();
    618         setDisabledFlags(mDisabledFlags, true /* force */);
    619         setMenuVisibility(mShowMenu, true /* force */);
    620 
    621         if (DEBUG) {
    622             Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
    623         }
    624 
    625         updateTaskSwitchHelper();
    626         setNavigationIconHints(mNavigationIconHints, true);
    627 
    628         getHomeButton().setVertical(mVertical);
    629     }
    630 
    631     private void updateTaskSwitchHelper() {
    632         if (mGestureHelper == null) return;
    633         boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
    634         mGestureHelper.setBarState(mVertical, isRtl);
    635     }
    636 
    637     @Override
    638     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    639         if (DEBUG) Log.d(TAG, String.format(
    640                     "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh));
    641 
    642         final boolean newVertical = w > 0 && h > w;
    643         if (newVertical != mVertical) {
    644             mVertical = newVertical;
    645             //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n"));
    646             reorient();
    647             notifyVerticalChangedListener(newVertical);
    648         }
    649 
    650         postCheckForInvalidLayout("sizeChanged");
    651         super.onSizeChanged(w, h, oldw, oldh);
    652     }
    653 
    654     private void notifyVerticalChangedListener(boolean newVertical) {
    655         if (mOnVerticalChangedListener != null) {
    656             mOnVerticalChangedListener.onVerticalChanged(newVertical);
    657         }
    658     }
    659 
    660     @Override
    661     protected void onConfigurationChanged(Configuration newConfig) {
    662         super.onConfigurationChanged(newConfig);
    663         boolean uiCarModeChanged = updateCarMode(newConfig);
    664         updateTaskSwitchHelper();
    665         updateIcons(getContext(), mConfiguration, newConfig);
    666         updateRecentsIcon();
    667         if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
    668                 || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
    669             // If car mode or density changes, we need to reset the icons.
    670             setNavigationIconHints(mNavigationIconHints, true);
    671         }
    672         mConfiguration.updateFrom(newConfig);
    673     }
    674 
    675     /**
    676      * If the configuration changed, update the carmode and return that it was updated.
    677      */
    678     private boolean updateCarMode(Configuration newConfig) {
    679         boolean uiCarModeChanged = false;
    680         if (newConfig != null) {
    681             int uiMode = newConfig.uiMode & Configuration.UI_MODE_TYPE_MASK;
    682             final boolean isCarMode = (uiMode == Configuration.UI_MODE_TYPE_CAR);
    683 
    684             if (isCarMode != mInCarMode) {
    685                 mInCarMode = isCarMode;
    686                 if (ALTERNATE_CAR_MODE_UI) {
    687                     mUseCarModeUi = isCarMode;
    688                     uiCarModeChanged = true;
    689                 } else {
    690                     // Don't use car mode behavior if ALTERNATE_CAR_MODE_UI not set.
    691                     mUseCarModeUi = false;
    692                 }
    693             }
    694         }
    695         return uiCarModeChanged;
    696     }
    697 
    698     /*
    699     @Override
    700     protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
    701         if (DEBUG) Log.d(TAG, String.format(
    702                     "onLayout: %s (%d,%d,%d,%d)",
    703                     changed?"changed":"notchanged", left, top, right, bottom));
    704         super.onLayout(changed, left, top, right, bottom);
    705     }
    706 
    707     // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else
    708     // fails, any touch on the display will fix the layout.
    709     @Override
    710     public boolean onInterceptTouchEvent(MotionEvent ev) {
    711         if (DEBUG) Log.d(TAG, "onInterceptTouchEvent: " + ev.toString());
    712         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    713             postCheckForInvalidLayout("touch");
    714         }
    715         return super.onInterceptTouchEvent(ev);
    716     }
    717     */
    718 
    719 
    720     private String getResourceName(int resId) {
    721         if (resId != 0) {
    722             final android.content.res.Resources res = getContext().getResources();
    723             try {
    724                 return res.getResourceName(resId);
    725             } catch (android.content.res.Resources.NotFoundException ex) {
    726                 return "(unknown)";
    727             }
    728         } else {
    729             return "(null)";
    730         }
    731     }
    732 
    733     private void postCheckForInvalidLayout(final String how) {
    734         mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget();
    735     }
    736 
    737     private static String visibilityToString(int vis) {
    738         switch (vis) {
    739             case View.INVISIBLE:
    740                 return "INVISIBLE";
    741             case View.GONE:
    742                 return "GONE";
    743             default:
    744                 return "VISIBLE";
    745         }
    746     }
    747 
    748     @Override
    749     protected void onAttachedToWindow() {
    750         super.onAttachedToWindow();
    751         reorient();
    752         onPluginDisconnected(null); // Create default gesture helper
    753         Dependency.get(PluginManager.class).addPluginListener(this,
    754                 NavGesture.class, false /* Only one */);
    755     }
    756 
    757     @Override
    758     protected void onDetachedFromWindow() {
    759         super.onDetachedFromWindow();
    760         Dependency.get(PluginManager.class).removePluginListener(this);
    761         if (mGestureHelper != null) {
    762             mGestureHelper.destroy();
    763         }
    764     }
    765 
    766     @Override
    767     public void onPluginConnected(NavGesture plugin, Context context) {
    768         mGestureHelper = plugin.getGestureHelper();
    769         updateTaskSwitchHelper();
    770     }
    771 
    772     @Override
    773     public void onPluginDisconnected(NavGesture plugin) {
    774         NavigationBarGestureHelper defaultHelper = new NavigationBarGestureHelper(getContext());
    775         defaultHelper.setComponents(mRecentsComponent, mDivider, this);
    776         if (mGestureHelper != null) {
    777             mGestureHelper.destroy();
    778         }
    779         mGestureHelper = defaultHelper;
    780         updateTaskSwitchHelper();
    781     }
    782 
    783     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    784         pw.println("NavigationBarView {");
    785         final Rect r = new Rect();
    786         final Point size = new Point();
    787         mDisplay.getRealSize(size);
    788 
    789         pw.println(String.format("      this: " + StatusBar.viewInfo(this)
    790                         + " " + visibilityToString(getVisibility())));
    791 
    792         getWindowVisibleDisplayFrame(r);
    793         final boolean offscreen = r.right > size.x || r.bottom > size.y;
    794         pw.println("      window: "
    795                 + r.toShortString()
    796                 + " " + visibilityToString(getWindowVisibility())
    797                 + (offscreen ? " OFFSCREEN!" : ""));
    798 
    799         pw.println(String.format("      mCurrentView: id=%s (%dx%d) %s",
    800                         getResourceName(getCurrentView().getId()),
    801                         getCurrentView().getWidth(), getCurrentView().getHeight(),
    802                         visibilityToString(getCurrentView().getVisibility())));
    803 
    804         pw.println(String.format("      disabled=0x%08x vertical=%s menu=%s",
    805                         mDisabledFlags,
    806                         mVertical ? "true" : "false",
    807                         mShowMenu ? "true" : "false"));
    808 
    809         dumpButton(pw, "back", getBackButton());
    810         dumpButton(pw, "home", getHomeButton());
    811         dumpButton(pw, "rcnt", getRecentsButton());
    812         dumpButton(pw, "menu", getMenuButton());
    813         dumpButton(pw, "a11y", getAccessibilityButton());
    814 
    815         pw.println("    }");
    816     }
    817 
    818     private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
    819         pw.print("      " + caption + ": ");
    820         if (button == null) {
    821             pw.print("null");
    822         } else {
    823             pw.print(visibilityToString(button.getVisibility())
    824                     + " alpha=" + button.getAlpha()
    825                     );
    826         }
    827         pw.println();
    828     }
    829 
    830     public interface OnVerticalChangedListener {
    831         void onVerticalChanged(boolean isVertical);
    832     }
    833 
    834     private final Consumer<Boolean> mDockedListener = exists -> mHandler.post(() -> {
    835         mDockedStackExists = exists;
    836         updateRecentsIcon();
    837     });
    838 }
    839