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