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