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