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