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