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