NavigationBarView.java revision e932e30eadb5978591316ffe3d21d60604c0cd91
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.statusbar.phone;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.LayoutTransition;
22import android.app.StatusBarManager;
23import android.content.Context;
24import android.content.res.Resources;
25import android.graphics.Point;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
28import android.graphics.drawable.GradientDrawable;
29import android.graphics.drawable.GradientDrawable.Orientation;
30import android.os.Handler;
31import android.os.Message;
32import android.os.ServiceManager;
33import android.util.AttributeSet;
34import android.util.Log;
35import android.view.Display;
36import android.view.MotionEvent;
37import android.view.Surface;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.WindowManager;
41import android.view.animation.AccelerateInterpolator;
42import android.widget.ImageView;
43import android.widget.LinearLayout;
44
45import com.android.internal.statusbar.IStatusBarService;
46import com.android.systemui.R;
47import com.android.systemui.statusbar.BaseStatusBar;
48import com.android.systemui.statusbar.DelegateViewHelper;
49import com.android.systemui.statusbar.policy.DeadZone;
50
51import java.io.FileDescriptor;
52import java.io.PrintWriter;
53
54public class NavigationBarView extends LinearLayout {
55    final static boolean DEBUG = false;
56    final static String TAG = "PhoneStatusBar/NavigationBarView";
57
58    final static boolean NAVBAR_ALWAYS_AT_RIGHT = true;
59
60    // slippery nav bar when everything is disabled, e.g. during setup
61    final static boolean SLIPPERY_WHEN_DISABLED= true;
62
63    final static boolean ANIMATE_HIDE_TRANSITION = false; // turned off because it introduces unsightly delay when videos goes to full screen
64
65    protected IStatusBarService mBarService;
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 mHidden, mLowProfile, 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 DelegateViewHelper mDelegateHelper;
83    private DeadZone mDeadZone;
84    private final BarTransitions mBarTransitions;
85    private final Drawable mTransparent;
86    private final Drawable mTransparentVertical;
87
88    // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
89    final static boolean WORKAROUND_INVALID_LAYOUT = true;
90    final static int MSG_CHECK_INVALID_LAYOUT = 8686;
91
92    private class H extends Handler {
93        public void handleMessage(Message m) {
94            switch (m.what) {
95                case MSG_CHECK_INVALID_LAYOUT:
96                    final String how = "" + m.obj;
97                    final int w = getWidth();
98                    final int h = getHeight();
99                    final int vw = mCurrentView.getWidth();
100                    final int vh = mCurrentView.getHeight();
101
102                    if (h != vh || w != vw) {
103                        Log.w(TAG, String.format(
104                            "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)",
105                            how, w, h, vw, vh));
106                        if (WORKAROUND_INVALID_LAYOUT) {
107                            requestLayout();
108                        }
109                    }
110                    break;
111            }
112        }
113    }
114
115    public NavigationBarView(Context context, AttributeSet attrs) {
116        super(context, attrs);
117
118        mHidden = false;
119
120        mDisplay = ((WindowManager)context.getSystemService(
121                Context.WINDOW_SERVICE)).getDefaultDisplay();
122        mBarService = IStatusBarService.Stub.asInterface(
123                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
124
125        final Resources res = mContext.getResources();
126        mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
127        mVertical = false;
128        mShowMenu = false;
129        mDelegateHelper = new DelegateViewHelper(this);
130
131        getIcons(res);
132
133        final int[] gradientColors = new int[] {
134                res.getColor(R.color.navigation_bar_background_transparent_start),
135                res.getColor(R.color.navigation_bar_background_transparent_end)
136        };
137        mTransparent = new GradientDrawable(Orientation.BOTTOM_TOP, gradientColors);
138        mTransparentVertical = new GradientDrawable(Orientation.RIGHT_LEFT, gradientColors);
139        mBarTransitions = new BarTransitions(context, this, mTransparent);
140    }
141
142    public BarTransitions getBarTransitions() {
143        return mBarTransitions;
144    }
145
146    public void setDelegateView(View view) {
147        mDelegateHelper.setDelegateView(view);
148    }
149
150    public void setBar(BaseStatusBar phoneStatusBar) {
151        mDelegateHelper.setBar(phoneStatusBar);
152    }
153
154    @Override
155    public boolean onTouchEvent(MotionEvent event) {
156        if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
157            mDeadZone.poke(event);
158        }
159        if (mDelegateHelper != null) {
160            boolean ret = mDelegateHelper.onInterceptTouchEvent(event);
161            if (ret) return true;
162        }
163        return super.onTouchEvent(event);
164    }
165
166    @Override
167    public boolean onInterceptTouchEvent(MotionEvent event) {
168        return mDelegateHelper.onInterceptTouchEvent(event);
169    }
170
171    private H mHandler = new H();
172
173    public View getRecentsButton() {
174        return mCurrentView.findViewById(R.id.recent_apps);
175    }
176
177    public View getMenuButton() {
178        return mCurrentView.findViewById(R.id.menu);
179    }
180
181    public View getBackButton() {
182        return mCurrentView.findViewById(R.id.back);
183    }
184
185    public View getHomeButton() {
186        return mCurrentView.findViewById(R.id.home);
187    }
188
189    // for when home is disabled, but search isn't
190    public View getSearchLight() {
191        return mCurrentView.findViewById(R.id.search_light);
192    }
193
194    private void getIcons(Resources res) {
195        mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back);
196        mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land);
197        mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime);
198        mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime);
199        mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent);
200        mRecentLandIcon = res.getDrawable(R.drawable.ic_sysbar_recent_land);
201    }
202
203    @Override
204    public void setLayoutDirection(int layoutDirection) {
205        getIcons(mContext.getResources());
206
207        super.setLayoutDirection(layoutDirection);
208    }
209
210    public void notifyScreenOn(boolean screenOn) {
211        mScreenOn = screenOn;
212        setDisabledFlags(mDisabledFlags, true);
213    }
214
215    View.OnTouchListener mLightsOutListener = new View.OnTouchListener() {
216        @Override
217        public boolean onTouch(View v, MotionEvent ev) {
218            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
219                // even though setting the systemUI visibility below will turn these views
220                // on, we need them to come up faster so that they can catch this motion
221                // event
222                setLowProfile(false, false, false);
223
224                try {
225                    mBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE);
226                } catch (android.os.RemoteException ex) {
227                }
228            }
229            return false;
230        }
231    };
232
233    public void setNavigationIconHints(int hints) {
234        setNavigationIconHints(hints, false);
235    }
236
237    public void setNavigationIconHints(int hints, boolean force) {
238        if (!force && hints == mNavigationIconHints) return;
239
240        if (DEBUG) {
241            android.widget.Toast.makeText(mContext,
242                "Navigation icon hints = " + hints,
243                500).show();
244        }
245
246        mNavigationIconHints = hints;
247
248        getBackButton().setAlpha(
249            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f);
250        getHomeButton().setAlpha(
251            (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f);
252        getRecentsButton().setAlpha(
253            (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f);
254
255        ((ImageView)getBackButton()).setImageDrawable(
256            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT))
257                ? (mVertical ? mBackAltLandIcon : mBackAltIcon)
258                : (mVertical ? mBackLandIcon : mBackIcon));
259
260        ((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon);
261
262        setDisabledFlags(mDisabledFlags, true);
263    }
264
265    public void setDisabledFlags(int disabledFlags) {
266        setDisabledFlags(disabledFlags, false);
267    }
268
269    public void setDisabledFlags(int disabledFlags, boolean force) {
270        if (!force && mDisabledFlags == disabledFlags) return;
271
272        mDisabledFlags = disabledFlags;
273
274        final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
275        final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
276        final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
277                && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
278        final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0);
279
280        if (SLIPPERY_WHEN_DISABLED) {
281            setSlippery(disableHome && disableRecent && disableBack && disableSearch);
282        }
283
284        if (!mScreenOn && mCurrentView != null) {
285            ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons);
286            LayoutTransition lt = navButtons == null ? null : navButtons.getLayoutTransition();
287            if (lt != null) {
288                lt.disableTransitionType(
289                        LayoutTransition.CHANGE_APPEARING | LayoutTransition.CHANGE_DISAPPEARING |
290                        LayoutTransition.APPEARING | LayoutTransition.DISAPPEARING);
291            }
292        }
293
294        getBackButton()   .setVisibility(disableBack       ? View.INVISIBLE : View.VISIBLE);
295        getHomeButton()   .setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
296        getRecentsButton().setVisibility(disableRecent     ? View.INVISIBLE : View.VISIBLE);
297
298        getSearchLight().setVisibility((disableHome && !disableSearch) ? View.VISIBLE : View.GONE);
299    }
300
301    public void setSlippery(boolean newSlippery) {
302        WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
303        if (lp != null) {
304            boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0;
305            if (!oldSlippery && newSlippery) {
306                lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
307            } else if (oldSlippery && !newSlippery) {
308                lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
309            } else {
310                return;
311            }
312            WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
313            wm.updateViewLayout(this, lp);
314        }
315    }
316
317    public void setMenuVisibility(final boolean show) {
318        setMenuVisibility(show, false);
319    }
320
321    public void setMenuVisibility(final boolean show, final boolean force) {
322        if (!force && mShowMenu == show) return;
323
324        mShowMenu = show;
325
326        getMenuButton().setVisibility(mShowMenu ? View.VISIBLE : View.INVISIBLE);
327    }
328
329    public void setLowProfile(final boolean lightsOut) {
330        setLowProfile(lightsOut, true, false);
331    }
332
333    public void setLowProfile(final boolean lightsOut, final boolean animate, final boolean force) {
334        if (!force && lightsOut == mLowProfile) return;
335
336        mLowProfile = lightsOut;
337
338        if (DEBUG) Log.d(TAG, "setting lights " + (lightsOut?"out":"on"));
339
340        final View navButtons = mCurrentView.findViewById(R.id.nav_buttons);
341        final View lowLights = mCurrentView.findViewById(R.id.lights_out);
342
343        // ok, everyone, stop it right there
344        navButtons.animate().cancel();
345        lowLights.animate().cancel();
346
347        if (!animate) {
348            navButtons.setAlpha(lightsOut ? 0f : 1f);
349
350            lowLights.setAlpha(lightsOut ? 1f : 0f);
351            lowLights.setVisibility(lightsOut ? View.VISIBLE : View.GONE);
352        } else {
353            navButtons.animate()
354                .alpha(lightsOut ? 0f : 1f)
355                .setDuration(lightsOut ? 750 : 250)
356                .start();
357
358            lowLights.setOnTouchListener(mLightsOutListener);
359            if (lowLights.getVisibility() == View.GONE) {
360                lowLights.setAlpha(0f);
361                lowLights.setVisibility(View.VISIBLE);
362            }
363            lowLights.animate()
364                .alpha(lightsOut ? 1f : 0f)
365                .setDuration(lightsOut ? 750 : 250)
366                .setInterpolator(new AccelerateInterpolator(2.0f))
367                .setListener(lightsOut ? null : new AnimatorListenerAdapter() {
368                    @Override
369                    public void onAnimationEnd(Animator _a) {
370                        lowLights.setVisibility(View.GONE);
371                    }
372                })
373                .start();
374        }
375    }
376
377    public void setHidden(final boolean hide) {
378        if (hide == mHidden) return;
379
380        mHidden = hide;
381        Log.d(TAG,
382            (hide ? "HIDING" : "SHOWING") + " navigation bar");
383
384        // bring up the lights no matter what
385        setLowProfile(false);
386    }
387
388    @Override
389    public void onFinishInflate() {
390        mRotatedViews[Surface.ROTATION_0] =
391        mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
392
393        mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
394
395        mRotatedViews[Surface.ROTATION_270] = NAVBAR_ALWAYS_AT_RIGHT
396                                                ? findViewById(R.id.rot90)
397                                                : findViewById(R.id.rot270);
398
399        mCurrentView = mRotatedViews[Surface.ROTATION_0];
400    }
401
402    public boolean isVertical() {
403        return mVertical;
404    }
405
406    public void reorient() {
407        final int rot = mDisplay.getRotation();
408        for (int i=0; i<4; i++) {
409            mRotatedViews[i].setVisibility(View.GONE);
410        }
411        mCurrentView = mRotatedViews[rot];
412        mCurrentView.setVisibility(View.VISIBLE);
413
414        mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);
415
416        // force the low profile & disabled states into compliance
417        setLowProfile(mLowProfile, false, true /* force */);
418        setDisabledFlags(mDisabledFlags, true /* force */);
419        setMenuVisibility(mShowMenu, true /* force */);
420
421        if (DEBUG) {
422            Log.d(TAG, "reorient(): rot=" + mDisplay.getRotation());
423        }
424
425        setNavigationIconHints(mNavigationIconHints, true);
426        mBarTransitions.setTransparent(mVertical ? mTransparentVertical : mTransparent);
427    }
428
429    @Override
430    protected void onLayout(boolean changed, int l, int t, int r, int b) {
431        super.onLayout(changed, l, t, r, b);
432        mDelegateHelper.setInitialTouchRegion(getHomeButton(), getBackButton(), getRecentsButton());
433    }
434
435    @Override
436    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
437        if (DEBUG) Log.d(TAG, String.format(
438                    "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh));
439
440        final boolean newVertical = w > 0 && h > w;
441        if (newVertical != mVertical) {
442            mVertical = newVertical;
443            //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n"));
444            reorient();
445        }
446
447        postCheckForInvalidLayout("sizeChanged");
448        super.onSizeChanged(w, h, oldw, oldh);
449    }
450
451    /*
452    @Override
453    protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
454        if (DEBUG) Log.d(TAG, String.format(
455                    "onLayout: %s (%d,%d,%d,%d)",
456                    changed?"changed":"notchanged", left, top, right, bottom));
457        super.onLayout(changed, left, top, right, bottom);
458    }
459
460    // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else
461    // fails, any touch on the display will fix the layout.
462    @Override
463    public boolean onInterceptTouchEvent(MotionEvent ev) {
464        if (DEBUG) Log.d(TAG, "onInterceptTouchEvent: " + ev.toString());
465        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
466            postCheckForInvalidLayout("touch");
467        }
468        return super.onInterceptTouchEvent(ev);
469    }
470    */
471
472
473    private String getResourceName(int resId) {
474        if (resId != 0) {
475            final android.content.res.Resources res = mContext.getResources();
476            try {
477                return res.getResourceName(resId);
478            } catch (android.content.res.Resources.NotFoundException ex) {
479                return "(unknown)";
480            }
481        } else {
482            return "(null)";
483        }
484    }
485
486    private void postCheckForInvalidLayout(final String how) {
487        mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget();
488    }
489
490    private static String visibilityToString(int vis) {
491        switch (vis) {
492            case View.INVISIBLE:
493                return "INVISIBLE";
494            case View.GONE:
495                return "GONE";
496            default:
497                return "VISIBLE";
498        }
499    }
500
501    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
502        pw.println("NavigationBarView {");
503        final Rect r = new Rect();
504        final Point size = new Point();
505        mDisplay.getRealSize(size);
506
507        pw.println(String.format("      this: " + PhoneStatusBar.viewInfo(this)
508                        + " " + visibilityToString(getVisibility())));
509
510        getWindowVisibleDisplayFrame(r);
511        final boolean offscreen = r.right > size.x || r.bottom > size.y;
512        pw.println("      window: "
513                + r.toShortString()
514                + " " + visibilityToString(getWindowVisibility())
515                + (offscreen ? " OFFSCREEN!" : ""));
516
517        pw.println(String.format("      mCurrentView: id=%s (%dx%d) %s",
518                        getResourceName(mCurrentView.getId()),
519                        mCurrentView.getWidth(), mCurrentView.getHeight(),
520                        visibilityToString(mCurrentView.getVisibility())));
521
522        pw.println(String.format("      disabled=0x%08x vertical=%s hidden=%s low=%s menu=%s",
523                        mDisabledFlags,
524                        mVertical ? "true" : "false",
525                        mHidden ? "true" : "false",
526                        mLowProfile ? "true" : "false",
527                        mShowMenu ? "true" : "false"));
528
529        final View back = getBackButton();
530        final View home = getHomeButton();
531        final View recent = getRecentsButton();
532        final View menu = getMenuButton();
533
534        pw.println("      back: "
535                + PhoneStatusBar.viewInfo(back)
536                + " " + visibilityToString(back.getVisibility())
537                );
538        pw.println("      home: "
539                + PhoneStatusBar.viewInfo(home)
540                + " " + visibilityToString(home.getVisibility())
541                );
542        pw.println("      rcnt: "
543                + PhoneStatusBar.viewInfo(recent)
544                + " " + visibilityToString(recent.getVisibility())
545                );
546        pw.println("      menu: "
547                + PhoneStatusBar.viewInfo(menu)
548                + " " + visibilityToString(menu.getVisibility())
549                );
550        pw.println("    }");
551    }
552
553}
554