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