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