NavigationBarView.java revision 6c9df5054a25f179ea7359a1a5e59e7d5d8da122
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.LayoutTransition;
20import android.app.ActivityManagerNative;
21import android.app.StatusBarManager;
22import android.app.admin.DevicePolicyManager;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.res.Resources;
28import android.graphics.Point;
29import android.graphics.Rect;
30import android.graphics.drawable.Drawable;
31import android.os.Handler;
32import android.os.Message;
33import android.os.RemoteException;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.view.Display;
37import android.view.MotionEvent;
38import android.view.Surface;
39import android.view.View;
40import android.view.View.OnClickListener;
41import android.view.ViewGroup;
42import android.view.WindowManager;
43import android.view.accessibility.AccessibilityManager;
44import android.widget.ImageView;
45import android.widget.LinearLayout;
46
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
52import java.io.FileDescriptor;
53import java.io.PrintWriter;
54
55public class NavigationBarView extends LinearLayout {
56    private static final int CAMERA_BUTTON_FADE_DURATION = 200;
57    final static boolean DEBUG = false;
58    final static String TAG = "PhoneStatusBar/NavigationBarView";
59
60    final static boolean NAVBAR_ALWAYS_AT_RIGHT = true;
61
62    // slippery nav bar when everything is disabled, e.g. during setup
63    final static boolean SLIPPERY_WHEN_DISABLED = true;
64
65    final Display mDisplay;
66    View mCurrentView = null;
67    View[] mRotatedViews = new View[4];
68
69    int mBarSize;
70    boolean mVertical;
71    boolean mScreenOn;
72
73    boolean mShowMenu;
74    int mDisabledFlags = 0;
75    int mNavigationIconHints = 0;
76
77    private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon;
78    private Drawable mRecentIcon;
79    private Drawable mRecentLandIcon;
80
81    private DelegateViewHelper mDelegateHelper;
82    private DeadZone mDeadZone;
83    private final NavigationBarTransitions mBarTransitions;
84
85    // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
86    final static boolean WORKAROUND_INVALID_LAYOUT = true;
87    final static int MSG_CHECK_INVALID_LAYOUT = 8686;
88
89    // used to disable the camera icon in navbar when disabled by DPM
90    private boolean mCameraDisabledByDpm;
91
92    private final OnTouchListener mCameraTouchListener = new OnTouchListener() {
93        @Override
94        public boolean onTouch(View cameraButtonView, MotionEvent event) {
95            View searchLight = getSearchLight();
96            switch (event.getAction()) {
97                case MotionEvent.ACTION_DOWN:
98                    // disable search gesture while interacting with camera
99                    mDelegateHelper.setDisabled(true);
100                    cameraButtonView.animate().alpha(0.0f).setDuration(CAMERA_BUTTON_FADE_DURATION);
101                    if (searchLight != null) {
102                        searchLight.animate().alpha(0.0f).setDuration(CAMERA_BUTTON_FADE_DURATION);
103                    }
104                    break;
105                case MotionEvent.ACTION_UP:
106                case MotionEvent.ACTION_CANCEL:
107                    mDelegateHelper.setDisabled(false);
108                    cameraButtonView.animate().alpha(1.0f).setDuration(CAMERA_BUTTON_FADE_DURATION);
109                    if (searchLight != null) {
110                        searchLight.animate().alpha(1.0f).setDuration(CAMERA_BUTTON_FADE_DURATION);
111                    }
112                    break;
113            }
114            return KeyguardTouchDelegate.getInstance(getContext()).dispatch(event);
115        }
116    };
117
118    private class H extends Handler {
119        public void handleMessage(Message m) {
120            switch (m.what) {
121                case MSG_CHECK_INVALID_LAYOUT:
122                    final String how = "" + m.obj;
123                    final int w = getWidth();
124                    final int h = getHeight();
125                    final int vw = mCurrentView.getWidth();
126                    final int vh = mCurrentView.getHeight();
127
128                    if (h != vh || w != vw) {
129                        Log.w(TAG, String.format(
130                            "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)",
131                            how, w, h, vw, vh));
132                        if (WORKAROUND_INVALID_LAYOUT) {
133                            requestLayout();
134                        }
135                    }
136                    break;
137            }
138        }
139    }
140
141    public NavigationBarView(Context context, AttributeSet attrs) {
142        super(context, attrs);
143
144        mDisplay = ((WindowManager)context.getSystemService(
145                Context.WINDOW_SERVICE)).getDefaultDisplay();
146
147        final Resources res = mContext.getResources();
148        mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
149        mVertical = false;
150        mShowMenu = false;
151        mDelegateHelper = new DelegateViewHelper(this);
152
153        getIcons(res);
154
155        mBarTransitions = new NavigationBarTransitions(this);
156
157        mCameraDisabledByDpm = isCameraDisabledByDpm();
158        watchForDevicePolicyChanges();
159    }
160
161    private void watchForDevicePolicyChanges() {
162        final IntentFilter filter = new IntentFilter();
163        filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
164        mContext.registerReceiver(new BroadcastReceiver() {
165            public void onReceive(Context context, Intent intent) {
166                post(new Runnable() {
167                    @Override
168                    public void run() {
169                        mCameraDisabledByDpm = isCameraDisabledByDpm();
170                    }
171                });
172            }
173        }, filter);
174    }
175
176    public BarTransitions getBarTransitions() {
177        return mBarTransitions;
178    }
179
180    public void setDelegateView(View view) {
181        mDelegateHelper.setDelegateView(view);
182    }
183
184    public void setBar(BaseStatusBar phoneStatusBar) {
185        mDelegateHelper.setBar(phoneStatusBar);
186    }
187
188    @Override
189    public boolean onTouchEvent(MotionEvent event) {
190        if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
191            mDeadZone.poke(event);
192        }
193        if (mDelegateHelper != null) {
194            boolean ret = mDelegateHelper.onInterceptTouchEvent(event);
195            if (ret) return true;
196        }
197        return super.onTouchEvent(event);
198    }
199
200    @Override
201    public boolean onInterceptTouchEvent(MotionEvent event) {
202        return mDelegateHelper.onInterceptTouchEvent(event);
203    }
204
205    private H mHandler = new H();
206
207    public View getCurrentView() {
208        return mCurrentView;
209    }
210
211    public View getRecentsButton() {
212        return mCurrentView.findViewById(R.id.recent_apps);
213    }
214
215    public View getMenuButton() {
216        return mCurrentView.findViewById(R.id.menu);
217    }
218
219    public View getBackButton() {
220        return mCurrentView.findViewById(R.id.back);
221    }
222
223    public View getHomeButton() {
224        return mCurrentView.findViewById(R.id.home);
225    }
226
227    // for when home is disabled, but search isn't
228    public View getSearchLight() {
229        return mCurrentView.findViewById(R.id.search_light);
230    }
231
232    // shown when keyguard is visible and camera is available
233    public View getCameraButton() {
234        return mCurrentView.findViewById(R.id.camera_button);
235    }
236
237    private void getIcons(Resources res) {
238        mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back);
239        mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land);
240        mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime);
241        mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime);
242        mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent);
243        mRecentLandIcon = res.getDrawable(R.drawable.ic_sysbar_recent_land);
244    }
245
246    @Override
247    public void setLayoutDirection(int layoutDirection) {
248        getIcons(mContext.getResources());
249
250        super.setLayoutDirection(layoutDirection);
251    }
252
253    public void notifyScreenOn(boolean screenOn) {
254        mScreenOn = screenOn;
255        setDisabledFlags(mDisabledFlags, true);
256    }
257
258    public void setNavigationIconHints(int hints) {
259        setNavigationIconHints(hints, false);
260    }
261
262    public void setNavigationIconHints(int hints, boolean force) {
263        if (!force && hints == mNavigationIconHints) return;
264
265        if (DEBUG) {
266            android.widget.Toast.makeText(mContext,
267                "Navigation icon hints = " + hints,
268                500).show();
269        }
270
271        mNavigationIconHints = hints;
272
273        getBackButton().setAlpha(
274            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f);
275        getHomeButton().setAlpha(
276            (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f);
277        getRecentsButton().setAlpha(
278            (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f);
279
280        ((ImageView)getBackButton()).setImageDrawable(
281            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT))
282                ? (mVertical ? mBackAltLandIcon : mBackAltIcon)
283                : (mVertical ? mBackLandIcon : mBackIcon));
284
285        ((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon);
286
287        setDisabledFlags(mDisabledFlags, true);
288    }
289
290    public void setDisabledFlags(int disabledFlags) {
291        setDisabledFlags(disabledFlags, false);
292    }
293
294    public void setDisabledFlags(int disabledFlags, boolean force) {
295        if (!force && mDisabledFlags == disabledFlags) return;
296
297        mDisabledFlags = disabledFlags;
298
299        final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
300        final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
301        final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
302                && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
303        final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0);
304
305        if (SLIPPERY_WHEN_DISABLED) {
306            setSlippery(disableHome && disableRecent && disableBack && disableSearch);
307        }
308
309        if (!mScreenOn && mCurrentView != null) {
310            ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons);
311            LayoutTransition lt = navButtons == null ? null : navButtons.getLayoutTransition();
312            if (lt != null) {
313                lt.disableTransitionType(
314                        LayoutTransition.CHANGE_APPEARING | LayoutTransition.CHANGE_DISAPPEARING |
315                        LayoutTransition.APPEARING | LayoutTransition.DISAPPEARING);
316            }
317        }
318
319        getBackButton()   .setVisibility(disableBack       ? View.INVISIBLE : View.VISIBLE);
320        getHomeButton()   .setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
321        getRecentsButton().setVisibility(disableRecent     ? View.INVISIBLE : View.VISIBLE);
322
323        final boolean shouldShowSearch = disableHome && !disableSearch;
324        getSearchLight().setVisibility(shouldShowSearch ? View.VISIBLE : View.GONE);
325        final View cameraButton = getCameraButton();
326        if (cameraButton != null) {
327            cameraButton.setVisibility(
328                    shouldShowSearch && !mCameraDisabledByDpm ? View.VISIBLE : View.GONE);
329        }
330    }
331
332    private boolean isCameraDisabledByDpm() {
333        final DevicePolicyManager dpm =
334                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
335        if (dpm != null) {
336            try {
337                final int userId = ActivityManagerNative.getDefault().getCurrentUser().id;
338                final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
339                final  boolean disabledBecauseKeyguardSecure =
340                        (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
341                        && KeyguardTouchDelegate.getInstance(getContext()).isSecure();
342                return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
343            } catch (RemoteException e) {
344                Log.e(TAG, "Can't get userId", e);
345            }
346        }
347        return false;
348    }
349
350    public void setSlippery(boolean newSlippery) {
351        WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
352        if (lp != null) {
353            boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0;
354            if (!oldSlippery && newSlippery) {
355                lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
356            } else if (oldSlippery && !newSlippery) {
357                lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
358            } else {
359                return;
360            }
361            WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
362            wm.updateViewLayout(this, lp);
363        }
364    }
365
366    public void setMenuVisibility(final boolean show) {
367        setMenuVisibility(show, false);
368    }
369
370    public void setMenuVisibility(final boolean show, final boolean force) {
371        if (!force && mShowMenu == show) return;
372
373        mShowMenu = show;
374
375        getMenuButton().setVisibility(mShowMenu ? View.VISIBLE : View.INVISIBLE);
376    }
377
378    @Override
379    public void onFinishInflate() {
380        mRotatedViews[Surface.ROTATION_0] =
381        mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
382
383        mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
384
385        mRotatedViews[Surface.ROTATION_270] = NAVBAR_ALWAYS_AT_RIGHT
386                                                ? findViewById(R.id.rot90)
387                                                : findViewById(R.id.rot270);
388
389        mCurrentView = mRotatedViews[Surface.ROTATION_0];
390
391
392        final AccessibilityManager accessibilityManager =
393                (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
394        if (accessibilityManager.isEnabled()) {
395            // In accessibility mode, we add a simple click handler since swipe is tough to
396            // trigger near screen edges.
397            View camera = getCameraButton();
398            View searchLight = getSearchLight();
399            if (camera != null || searchLight != null) {
400                OnClickListener listener = new OnClickListener() {
401                    @Override
402                    public void onClick(View v) {
403                        launchForAccessibilityClick(v);
404                    }
405                };
406                if (camera != null) {
407                    camera.setOnClickListener(listener);
408                }
409                if (searchLight != null) {
410                    searchLight.setOnClickListener(listener);
411                }
412            }
413        } else {
414            // Add a touch handler for camera icon for all view orientations.
415            for (int i = 0; i < mRotatedViews.length; i++) {
416                View cameraButton = mRotatedViews[i].findViewById(R.id.camera_button);
417                if (cameraButton != null) {
418                    cameraButton.setOnTouchListener(mCameraTouchListener);
419                }
420            }
421        }
422    }
423
424    protected void launchForAccessibilityClick(View v) {
425        if (v == getCameraButton()) {
426            KeyguardTouchDelegate.getInstance(getContext()).launchCamera();
427        } else if (v == getSearchLight()) {
428            KeyguardTouchDelegate.getInstance(getContext()).showAssistant();
429        }
430    }
431
432    public boolean isVertical() {
433        return mVertical;
434    }
435
436    public void reorient() {
437        final int rot = mDisplay.getRotation();
438        for (int i=0; i<4; i++) {
439            mRotatedViews[i].setVisibility(View.GONE);
440        }
441        mCurrentView = mRotatedViews[rot];
442        mCurrentView.setVisibility(View.VISIBLE);
443
444        mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);
445
446        // force the low profile & disabled states into compliance
447        mBarTransitions.init(mVertical);
448        setDisabledFlags(mDisabledFlags, true /* force */);
449        setMenuVisibility(mShowMenu, true /* force */);
450
451        if (DEBUG) {
452            Log.d(TAG, "reorient(): rot=" + mDisplay.getRotation());
453        }
454
455        setNavigationIconHints(mNavigationIconHints, true);
456    }
457
458    @Override
459    protected void onLayout(boolean changed, int l, int t, int r, int b) {
460        super.onLayout(changed, l, t, r, b);
461        mDelegateHelper.setInitialTouchRegion(getHomeButton(), getBackButton(), getRecentsButton());
462    }
463
464    @Override
465    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
466        if (DEBUG) Log.d(TAG, String.format(
467                    "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh));
468
469        final boolean newVertical = w > 0 && h > w;
470        if (newVertical != mVertical) {
471            mVertical = newVertical;
472            //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n"));
473            reorient();
474        }
475
476        postCheckForInvalidLayout("sizeChanged");
477        super.onSizeChanged(w, h, oldw, oldh);
478    }
479
480    /*
481    @Override
482    protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
483        if (DEBUG) Log.d(TAG, String.format(
484                    "onLayout: %s (%d,%d,%d,%d)",
485                    changed?"changed":"notchanged", left, top, right, bottom));
486        super.onLayout(changed, left, top, right, bottom);
487    }
488
489    // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else
490    // fails, any touch on the display will fix the layout.
491    @Override
492    public boolean onInterceptTouchEvent(MotionEvent ev) {
493        if (DEBUG) Log.d(TAG, "onInterceptTouchEvent: " + ev.toString());
494        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
495            postCheckForInvalidLayout("touch");
496        }
497        return super.onInterceptTouchEvent(ev);
498    }
499    */
500
501
502    private String getResourceName(int resId) {
503        if (resId != 0) {
504            final android.content.res.Resources res = mContext.getResources();
505            try {
506                return res.getResourceName(resId);
507            } catch (android.content.res.Resources.NotFoundException ex) {
508                return "(unknown)";
509            }
510        } else {
511            return "(null)";
512        }
513    }
514
515    private void postCheckForInvalidLayout(final String how) {
516        mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget();
517    }
518
519    private static String visibilityToString(int vis) {
520        switch (vis) {
521            case View.INVISIBLE:
522                return "INVISIBLE";
523            case View.GONE:
524                return "GONE";
525            default:
526                return "VISIBLE";
527        }
528    }
529
530    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
531        pw.println("NavigationBarView {");
532        final Rect r = new Rect();
533        final Point size = new Point();
534        mDisplay.getRealSize(size);
535
536        pw.println(String.format("      this: " + PhoneStatusBar.viewInfo(this)
537                        + " " + visibilityToString(getVisibility())));
538
539        getWindowVisibleDisplayFrame(r);
540        final boolean offscreen = r.right > size.x || r.bottom > size.y;
541        pw.println("      window: "
542                + r.toShortString()
543                + " " + visibilityToString(getWindowVisibility())
544                + (offscreen ? " OFFSCREEN!" : ""));
545
546        pw.println(String.format("      mCurrentView: id=%s (%dx%d) %s",
547                        getResourceName(mCurrentView.getId()),
548                        mCurrentView.getWidth(), mCurrentView.getHeight(),
549                        visibilityToString(mCurrentView.getVisibility())));
550
551        pw.println(String.format("      disabled=0x%08x vertical=%s menu=%s",
552                        mDisabledFlags,
553                        mVertical ? "true" : "false",
554                        mShowMenu ? "true" : "false"));
555
556        final View back = getBackButton();
557        final View home = getHomeButton();
558        final View recent = getRecentsButton();
559        final View menu = getMenuButton();
560
561        pw.println("      back: "
562                + PhoneStatusBar.viewInfo(back)
563                + " " + visibilityToString(back.getVisibility())
564                );
565        pw.println("      home: "
566                + PhoneStatusBar.viewInfo(home)
567                + " " + visibilityToString(home.getVisibility())
568                );
569        pw.println("      rcnt: "
570                + PhoneStatusBar.viewInfo(recent)
571                + " " + visibilityToString(recent.getVisibility())
572                );
573        pw.println("      menu: "
574                + PhoneStatusBar.viewInfo(menu)
575                + " " + visibilityToString(menu.getVisibility())
576                );
577        pw.println("    }");
578    }
579
580}
581