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