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