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
17package com.android.systemui.statusbar.phone;
18
19import android.animation.LayoutTransition;
20import android.animation.LayoutTransition.TransitionListener;
21import android.animation.ObjectAnimator;
22import android.animation.TimeInterpolator;
23import android.animation.ValueAnimator;
24import android.annotation.DrawableRes;
25import android.app.ActivityManager;
26import android.app.StatusBarManager;
27import android.content.Context;
28import android.content.res.Configuration;
29import android.graphics.Point;
30import android.graphics.Rect;
31import android.os.Handler;
32import android.os.Message;
33import android.os.RemoteException;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.util.SparseArray;
37import android.view.ContextThemeWrapper;
38import android.view.Display;
39import android.view.MotionEvent;
40import android.view.Surface;
41import android.view.View;
42import android.view.ViewGroup;
43import android.view.WindowManager;
44import android.view.inputmethod.InputMethodManager;
45import android.widget.FrameLayout;
46
47import com.android.settingslib.Utils;
48import com.android.systemui.Dependency;
49import com.android.systemui.DockedStackExistsListener;
50import com.android.systemui.R;
51import com.android.systemui.RecentsComponent;
52import com.android.systemui.plugins.PluginListener;
53import com.android.systemui.plugins.PluginManager;
54import com.android.systemui.plugins.statusbar.phone.NavGesture;
55import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
56import com.android.systemui.stackdivider.Divider;
57import com.android.systemui.statusbar.policy.DeadZone;
58import com.android.systemui.statusbar.policy.KeyButtonDrawable;
59
60import java.io.FileDescriptor;
61import java.io.PrintWriter;
62
63public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> {
64    final static boolean DEBUG = false;
65    final static String TAG = "StatusBar/NavBarView";
66
67    // slippery nav bar when everything is disabled, e.g. during setup
68    final static boolean SLIPPERY_WHEN_DISABLED = true;
69
70    final static boolean ALTERNATE_CAR_MODE_UI = false;
71
72    final Display mDisplay;
73    View mCurrentView = null;
74    View[] mRotatedViews = new View[4];
75
76    boolean mVertical;
77    boolean mScreenOn;
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            mBackIcon = getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark);
322            mBackLandIcon = mBackIcon;
323            mBackAltIcon = getDrawable(ctx,
324                    R.drawable.ic_sysbar_back_ime, R.drawable.ic_sysbar_back_ime_dark);
325            mBackAltLandIcon = mBackAltIcon;
326
327            mHomeDefaultIcon = getDrawable(ctx,
328                    R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark);
329            mRecentIcon = getDrawable(ctx,
330                    R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
331            mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark);
332            mAccessibilityIcon = getDrawable(ctx, R.drawable.ic_sysbar_accessibility_button,
333                    R.drawable.ic_sysbar_accessibility_button_dark);
334
335            int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
336            int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
337            Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
338            Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
339            mImeIcon = getDrawable(darkContext, lightContext,
340                    R.drawable.ic_ime_switcher_default, R.drawable.ic_ime_switcher_default);
341
342            if (ALTERNATE_CAR_MODE_UI) {
343                updateCarModeIcons(ctx);
344            }
345        }
346    }
347
348    private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int lightIcon,
349            @DrawableRes int darkIcon) {
350        return getDrawable(ctx, ctx, lightIcon, darkIcon);
351    }
352
353    private KeyButtonDrawable getDrawable(Context darkContext, Context lightContext,
354            @DrawableRes int lightIcon, @DrawableRes int darkIcon) {
355        return KeyButtonDrawable.create(lightContext.getDrawable(lightIcon),
356                darkContext.getDrawable(darkIcon));
357    }
358
359    @Override
360    public void setLayoutDirection(int layoutDirection) {
361        // Reload all the icons
362        updateIcons(getContext(), Configuration.EMPTY, mConfiguration);
363
364        super.setLayoutDirection(layoutDirection);
365    }
366
367    public void notifyScreenOn(boolean screenOn) {
368        mScreenOn = screenOn;
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        updateRotatedViews();
564        mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
565
566        getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
567
568        DockedStackExistsListener.register(exists -> mHandler.post(() -> {
569            mDockedStackExists = exists;
570            updateRecentsIcon();
571        }));
572    }
573
574    void updateRotatedViews() {
575        mRotatedViews[Surface.ROTATION_0] =
576                mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
577        mRotatedViews[Surface.ROTATION_270] =
578                mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
579
580        updateCurrentView();
581    }
582
583    public boolean needsReorient(int rotation) {
584        return mCurrentRotation != rotation;
585    }
586
587    private void updateCurrentView() {
588        final int rot = mDisplay.getRotation();
589        for (int i=0; i<4; i++) {
590            mRotatedViews[i].setVisibility(View.GONE);
591        }
592        mCurrentView = mRotatedViews[rot];
593        mCurrentView.setVisibility(View.VISIBLE);
594        mNavigationInflaterView.setAlternativeOrder(rot == Surface.ROTATION_90);
595        for (int i = 0; i < mButtonDispatchers.size(); i++) {
596            mButtonDispatchers.valueAt(i).setCurrentView(mCurrentView);
597        }
598        updateLayoutTransitionsEnabled();
599        mCurrentRotation = rot;
600    }
601
602    private void updateRecentsIcon() {
603        getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
604        mBarTransitions.reapplyDarkIntensity();
605    }
606
607    public boolean isVertical() {
608        return mVertical;
609    }
610
611    public void reorient() {
612        updateCurrentView();
613
614        mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);
615        ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
616        mDeadZone.setDisplayRotation(mCurrentRotation);
617
618        // force the low profile & disabled states into compliance
619        mBarTransitions.init();
620        setDisabledFlags(mDisabledFlags, true /* force */);
621        setMenuVisibility(mShowMenu, true /* force */);
622
623        if (DEBUG) {
624            Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
625        }
626
627        updateTaskSwitchHelper();
628        setNavigationIconHints(mNavigationIconHints, true);
629
630        getHomeButton().setVertical(mVertical);
631    }
632
633    public void onKeyguardOccludedChanged(boolean keyguardOccluded) {
634    }
635
636    private void updateTaskSwitchHelper() {
637        if (mGestureHelper == null) return;
638        boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
639        mGestureHelper.setBarState(mVertical, isRtl);
640    }
641
642    @Override
643    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
644        if (DEBUG) Log.d(TAG, String.format(
645                    "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh));
646
647        final boolean newVertical = w > 0 && h > w;
648        if (newVertical != mVertical) {
649            mVertical = newVertical;
650            //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n"));
651            reorient();
652            notifyVerticalChangedListener(newVertical);
653        }
654
655        postCheckForInvalidLayout("sizeChanged");
656        super.onSizeChanged(w, h, oldw, oldh);
657    }
658
659    private void notifyVerticalChangedListener(boolean newVertical) {
660        if (mOnVerticalChangedListener != null) {
661            mOnVerticalChangedListener.onVerticalChanged(newVertical);
662        }
663    }
664
665    @Override
666    protected void onConfigurationChanged(Configuration newConfig) {
667        super.onConfigurationChanged(newConfig);
668        boolean uiCarModeChanged = updateCarMode(newConfig);
669        updateTaskSwitchHelper();
670        updateIcons(getContext(), mConfiguration, newConfig);
671        updateRecentsIcon();
672        if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi) {
673            // If car mode or density changes, we need to reset the icons.
674            setNavigationIconHints(mNavigationIconHints, true);
675        }
676        mConfiguration.updateFrom(newConfig);
677    }
678
679    /**
680     * If the configuration changed, update the carmode and return that it was updated.
681     */
682    private boolean updateCarMode(Configuration newConfig) {
683        boolean uiCarModeChanged = false;
684        if (newConfig != null) {
685            int uiMode = newConfig.uiMode & Configuration.UI_MODE_TYPE_MASK;
686            final boolean isCarMode = (uiMode == Configuration.UI_MODE_TYPE_CAR);
687
688            if (isCarMode != mInCarMode) {
689                mInCarMode = isCarMode;
690                getHomeButton().setCarMode(isCarMode);
691
692                if (ALTERNATE_CAR_MODE_UI) {
693                    mUseCarModeUi = isCarMode;
694                    uiCarModeChanged = true;
695                } else {
696                    // Don't use car mode behavior if ALTERNATE_CAR_MODE_UI not set.
697                    mUseCarModeUi = false;
698                }
699            }
700        }
701        return uiCarModeChanged;
702    }
703
704    /*
705    @Override
706    protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
707        if (DEBUG) Log.d(TAG, String.format(
708                    "onLayout: %s (%d,%d,%d,%d)",
709                    changed?"changed":"notchanged", left, top, right, bottom));
710        super.onLayout(changed, left, top, right, bottom);
711    }
712
713    // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else
714    // fails, any touch on the display will fix the layout.
715    @Override
716    public boolean onInterceptTouchEvent(MotionEvent ev) {
717        if (DEBUG) Log.d(TAG, "onInterceptTouchEvent: " + ev.toString());
718        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
719            postCheckForInvalidLayout("touch");
720        }
721        return super.onInterceptTouchEvent(ev);
722    }
723    */
724
725
726    private String getResourceName(int resId) {
727        if (resId != 0) {
728            final android.content.res.Resources res = getContext().getResources();
729            try {
730                return res.getResourceName(resId);
731            } catch (android.content.res.Resources.NotFoundException ex) {
732                return "(unknown)";
733            }
734        } else {
735            return "(null)";
736        }
737    }
738
739    private void postCheckForInvalidLayout(final String how) {
740        mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget();
741    }
742
743    private static String visibilityToString(int vis) {
744        switch (vis) {
745            case View.INVISIBLE:
746                return "INVISIBLE";
747            case View.GONE:
748                return "GONE";
749            default:
750                return "VISIBLE";
751        }
752    }
753
754    @Override
755    protected void onAttachedToWindow() {
756        super.onAttachedToWindow();
757        onPluginDisconnected(null); // Create default gesture helper
758        Dependency.get(PluginManager.class).addPluginListener(this,
759                NavGesture.class, false /* Only one */);
760    }
761
762    @Override
763    protected void onDetachedFromWindow() {
764        super.onDetachedFromWindow();
765        Dependency.get(PluginManager.class).removePluginListener(this);
766        if (mGestureHelper != null) {
767            mGestureHelper.destroy();
768        }
769    }
770
771    @Override
772    public void onPluginConnected(NavGesture plugin, Context context) {
773        mGestureHelper = plugin.getGestureHelper();
774        updateTaskSwitchHelper();
775    }
776
777    @Override
778    public void onPluginDisconnected(NavGesture plugin) {
779        NavigationBarGestureHelper defaultHelper = new NavigationBarGestureHelper(getContext());
780        defaultHelper.setComponents(mRecentsComponent, mDivider, this);
781        if (mGestureHelper != null) {
782            mGestureHelper.destroy();
783        }
784        mGestureHelper = defaultHelper;
785        updateTaskSwitchHelper();
786    }
787
788    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
789        pw.println("NavigationBarView {");
790        final Rect r = new Rect();
791        final Point size = new Point();
792        mDisplay.getRealSize(size);
793
794        pw.println(String.format("      this: " + StatusBar.viewInfo(this)
795                        + " " + visibilityToString(getVisibility())));
796
797        getWindowVisibleDisplayFrame(r);
798        final boolean offscreen = r.right > size.x || r.bottom > size.y;
799        pw.println("      window: "
800                + r.toShortString()
801                + " " + visibilityToString(getWindowVisibility())
802                + (offscreen ? " OFFSCREEN!" : ""));
803
804        pw.println(String.format("      mCurrentView: id=%s (%dx%d) %s",
805                        getResourceName(getCurrentView().getId()),
806                        getCurrentView().getWidth(), getCurrentView().getHeight(),
807                        visibilityToString(getCurrentView().getVisibility())));
808
809        pw.println(String.format("      disabled=0x%08x vertical=%s menu=%s",
810                        mDisabledFlags,
811                        mVertical ? "true" : "false",
812                        mShowMenu ? "true" : "false"));
813
814        dumpButton(pw, "back", getBackButton());
815        dumpButton(pw, "home", getHomeButton());
816        dumpButton(pw, "rcnt", getRecentsButton());
817        dumpButton(pw, "menu", getMenuButton());
818        dumpButton(pw, "a11y", getAccessibilityButton());
819
820        pw.println("    }");
821    }
822
823    private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
824        pw.print("      " + caption + ": ");
825        if (button == null) {
826            pw.print("null");
827        } else {
828            pw.print(visibilityToString(button.getVisibility())
829                    + " alpha=" + button.getAlpha()
830                    );
831        }
832        pw.println();
833    }
834
835    public interface OnVerticalChangedListener {
836        void onVerticalChanged(boolean isVertical);
837    }
838
839}
840