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