KeyguardViewManager.java revision ed80f3f01e0c9eba6258f5ba3de356e3de395df4
1/*
2 * Copyright (C) 2007 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.keyguard;
18
19import android.app.PendingIntent;
20import android.graphics.Bitmap;
21import android.graphics.drawable.BitmapDrawable;
22import com.android.internal.policy.IKeyguardShowCallback;
23import com.android.internal.widget.LockPatternUtils;
24
25import android.app.Activity;
26import android.app.ActivityManager;
27import android.appwidget.AppWidgetManager;
28import android.content.Context;
29import android.content.pm.ActivityInfo;
30import android.content.res.Configuration;
31import android.content.res.Resources;
32import android.graphics.Canvas;
33import android.graphics.ColorFilter;
34import android.graphics.PixelFormat;
35import android.graphics.PorterDuff;
36import android.graphics.Rect;
37import android.graphics.drawable.Drawable;
38import android.os.Bundle;
39import android.os.IBinder;
40import android.os.Parcelable;
41import android.os.RemoteException;
42import android.os.SystemProperties;
43import android.util.Log;
44import android.util.Slog;
45import android.util.SparseArray;
46import android.view.KeyEvent;
47import android.view.LayoutInflater;
48import android.view.MotionEvent;
49import android.view.View;
50import android.view.ViewGroup;
51import android.view.ViewManager;
52import android.view.WindowManager;
53import android.widget.FrameLayout;
54
55/**
56 * Manages creating, showing, hiding and resetting the keyguard.  Calls back
57 * via {@link KeyguardViewMediator.ViewMediatorCallback} to poke
58 * the wake lock and report that the keyguard is done, which is in turn,
59 * reported to this class by the current {@link KeyguardViewBase}.
60 */
61public class KeyguardViewManager {
62    private final static boolean DEBUG = KeyguardViewMediator.DEBUG;
63    private static String TAG = "KeyguardViewManager";
64    public static boolean USE_UPPER_CASE = true;
65    public final static String IS_SWITCHING_USER = "is_switching_user";
66
67    // Timeout used for keypresses
68    static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
69
70    private final Context mContext;
71    private final ViewManager mViewManager;
72    private final KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback;
73
74    private WindowManager.LayoutParams mWindowLayoutParams;
75    private boolean mNeedsInput = false;
76
77    private ViewManagerHost mKeyguardHost;
78    private KeyguardHostView mKeyguardView;
79
80    private boolean mScreenOn = false;
81    private LockPatternUtils mLockPatternUtils;
82
83    private KeyguardUpdateMonitorCallback mBackgroundChanger = new KeyguardUpdateMonitorCallback() {
84        @Override
85        public void onSetBackground(Bitmap bmp) {
86            mKeyguardHost.setCustomBackground(bmp != null ?
87                    new BitmapDrawable(mContext.getResources(), bmp) : null);
88            updateShowWallpaper(bmp == null);
89        }
90    };
91
92    public interface ShowListener {
93        void onShown(IBinder windowToken);
94    };
95
96    /**
97     * @param context Used to create views.
98     * @param viewManager Keyguard will be attached to this.
99     * @param callback Used to notify of changes.
100     * @param lockPatternUtils
101     */
102    public KeyguardViewManager(Context context, ViewManager viewManager,
103            KeyguardViewMediator.ViewMediatorCallback callback,
104            LockPatternUtils lockPatternUtils) {
105        mContext = context;
106        mViewManager = viewManager;
107        mViewMediatorCallback = callback;
108        mLockPatternUtils = lockPatternUtils;
109    }
110
111    /**
112     * Show the keyguard.  Will handle creating and attaching to the view manager
113     * lazily.
114     */
115    public synchronized void show(Bundle options) {
116        if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView);
117
118        boolean enableScreenRotation = shouldEnableScreenRotation();
119
120        maybeCreateKeyguardLocked(enableScreenRotation, false, options);
121        maybeEnableScreenRotation(enableScreenRotation);
122
123        // Disable common aspects of the system/status/navigation bars that are not appropriate or
124        // useful on any keyguard screen but can be re-shown by dialogs or SHOW_WHEN_LOCKED
125        // activities. Other disabled bits are handled by the KeyguardViewMediator talking
126        // directly to the status bar service.
127        int visFlags = View.STATUS_BAR_DISABLE_HOME;
128        if (shouldEnableTranslucentDecor()) {
129            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
130                                       | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
131        }
132        if (DEBUG) Log.v(TAG, "show:setSystemUiVisibility(" + Integer.toHexString(visFlags)+")");
133        mKeyguardHost.setSystemUiVisibility(visFlags);
134
135        mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
136        mKeyguardHost.setVisibility(View.VISIBLE);
137        mKeyguardView.show();
138        mKeyguardView.requestFocus();
139    }
140
141    private boolean shouldEnableScreenRotation() {
142        Resources res = mContext.getResources();
143        return SystemProperties.getBoolean("lockscreen.rot_override",false)
144                || res.getBoolean(R.bool.config_enableLockScreenRotation);
145    }
146
147    private boolean shouldEnableTranslucentDecor() {
148        Resources res = mContext.getResources();
149        return res.getBoolean(R.bool.config_enableLockScreenTranslucentDecor);
150    }
151
152    class ViewManagerHost extends FrameLayout {
153        private static final int BACKGROUND_COLOR = 0x70000000;
154
155        private Drawable mCustomBackground;
156
157        // This is a faster way to draw the background on devices without hardware acceleration
158        private final Drawable mBackgroundDrawable = new Drawable() {
159            @Override
160            public void draw(Canvas canvas) {
161                if (mCustomBackground != null) {
162                    final Rect bounds = mCustomBackground.getBounds();
163                    final int vWidth = getWidth();
164                    final int vHeight = getHeight();
165
166                    final int restore = canvas.save();
167                    canvas.translate(-(bounds.width() - vWidth) / 2,
168                            -(bounds.height() - vHeight) / 2);
169                    mCustomBackground.draw(canvas);
170                    canvas.restoreToCount(restore);
171                } else {
172                    canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC);
173                }
174            }
175
176            @Override
177            public void setAlpha(int alpha) {
178            }
179
180            @Override
181            public void setColorFilter(ColorFilter cf) {
182            }
183
184            @Override
185            public int getOpacity() {
186                return PixelFormat.TRANSLUCENT;
187            }
188        };
189
190        public ViewManagerHost(Context context) {
191            super(context);
192            setBackground(mBackgroundDrawable);
193        }
194
195        public void setCustomBackground(Drawable d) {
196            mCustomBackground = d;
197            if (d != null) {
198                d.setColorFilter(BACKGROUND_COLOR, PorterDuff.Mode.SRC_OVER);
199            }
200            computeCustomBackgroundBounds();
201            invalidate();
202        }
203
204        private void computeCustomBackgroundBounds() {
205            if (mCustomBackground == null) return; // Nothing to do
206            if (!isLaidOut()) return; // We'll do this later
207
208            final int bgWidth = mCustomBackground.getIntrinsicWidth();
209            final int bgHeight = mCustomBackground.getIntrinsicHeight();
210            final int vWidth = getWidth();
211            final int vHeight = getHeight();
212
213            final float bgAspect = (float) bgWidth / bgHeight;
214            final float vAspect = (float) vWidth / vHeight;
215
216            if (bgAspect > vAspect) {
217                mCustomBackground.setBounds(0, 0, (int) (vHeight * bgAspect), vHeight);
218            } else {
219                mCustomBackground.setBounds(0, 0, vWidth, (int) (vWidth / bgAspect));
220            }
221        }
222
223        @Override
224        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
225            super.onSizeChanged(w, h, oldw, oldh);
226            computeCustomBackgroundBounds();
227        }
228
229        @Override
230        protected void onConfigurationChanged(Configuration newConfig) {
231            super.onConfigurationChanged(newConfig);
232            if (mKeyguardHost.getVisibility() == View.VISIBLE) {
233                // only propagate configuration messages if we're currently showing
234                maybeCreateKeyguardLocked(shouldEnableScreenRotation(), true, null);
235            } else {
236                if (DEBUG) Log.v(TAG, "onConfigurationChanged: view not visible");
237            }
238        }
239
240        @Override
241        public boolean dispatchKeyEvent(KeyEvent event) {
242            if (mKeyguardView != null) {
243                // Always process back and menu keys, regardless of focus
244                if (event.getAction() == KeyEvent.ACTION_DOWN) {
245                    int keyCode = event.getKeyCode();
246                    if (keyCode == KeyEvent.KEYCODE_BACK && mKeyguardView.handleBackKey()) {
247                        return true;
248                    } else if (keyCode == KeyEvent.KEYCODE_MENU && mKeyguardView.handleMenuKey()) {
249                        return true;
250                    }
251                }
252                // Always process media keys, regardless of focus
253                if (mKeyguardView.dispatchKeyEvent(event)) {
254                    return true;
255                }
256            }
257            return super.dispatchKeyEvent(event);
258        }
259    }
260
261    SparseArray<Parcelable> mStateContainer = new SparseArray<Parcelable>();
262
263    private void maybeCreateKeyguardLocked(boolean enableScreenRotation, boolean force,
264            Bundle options) {
265        if (mKeyguardHost != null) {
266            mKeyguardHost.saveHierarchyState(mStateContainer);
267        }
268
269        if (mKeyguardHost == null) {
270            if (DEBUG) Log.d(TAG, "keyguard host is null, creating it...");
271
272            mKeyguardHost = new ViewManagerHost(mContext);
273
274            int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
275                    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
276                    | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN
277                    | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
278
279            if (!mNeedsInput) {
280                flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
281            }
282
283            final int stretch = ViewGroup.LayoutParams.MATCH_PARENT;
284            final int type = WindowManager.LayoutParams.TYPE_KEYGUARD;
285            WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
286                    stretch, stretch, type, flags, PixelFormat.TRANSLUCENT);
287            lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
288            lp.windowAnimations = R.style.Animation_LockScreen;
289            lp.screenOrientation = enableScreenRotation ?
290                    ActivityInfo.SCREEN_ORIENTATION_USER : ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
291
292            if (ActivityManager.isHighEndGfx()) {
293                lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
294                lp.privateFlags |=
295                        WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
296            }
297            lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY;
298            lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
299            lp.setTitle("Keyguard");
300            mWindowLayoutParams = lp;
301            mViewManager.addView(mKeyguardHost, lp);
302
303            KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mBackgroundChanger);
304        }
305
306        if (force || mKeyguardView == null) {
307            mKeyguardHost.setCustomBackground(null);
308            mKeyguardHost.removeAllViews();
309            inflateKeyguardView(options);
310            mKeyguardView.requestFocus();
311        }
312        updateUserActivityTimeoutInWindowLayoutParams();
313        mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
314
315        mKeyguardHost.restoreHierarchyState(mStateContainer);
316    }
317
318    private void inflateKeyguardView(Bundle options) {
319        View v = mKeyguardHost.findViewById(R.id.keyguard_host_view);
320        if (v != null) {
321            mKeyguardHost.removeView(v);
322        }
323        final LayoutInflater inflater = LayoutInflater.from(mContext);
324        View view = inflater.inflate(R.layout.keyguard_host_view, mKeyguardHost, true);
325        mKeyguardView = (KeyguardHostView) view.findViewById(R.id.keyguard_host_view);
326        mKeyguardView.setLockPatternUtils(mLockPatternUtils);
327        mKeyguardView.setViewMediatorCallback(mViewMediatorCallback);
328        mKeyguardView.initializeSwitchingUserState(options != null &&
329                options.getBoolean(IS_SWITCHING_USER));
330
331        // HACK
332        // The keyguard view will have set up window flags in onFinishInflate before we set
333        // the view mediator callback. Make sure it knows the correct IME state.
334        if (mViewMediatorCallback != null) {
335            KeyguardPasswordView kpv = (KeyguardPasswordView) mKeyguardView.findViewById(
336                    R.id.keyguard_password_view);
337
338            if (kpv != null) {
339                mViewMediatorCallback.setNeedsInput(kpv.needsInput());
340            }
341        }
342
343        if (options != null) {
344            int widgetToShow = options.getInt(LockPatternUtils.KEYGUARD_SHOW_APPWIDGET,
345                    AppWidgetManager.INVALID_APPWIDGET_ID);
346            if (widgetToShow != AppWidgetManager.INVALID_APPWIDGET_ID) {
347                mKeyguardView.goToWidget(widgetToShow);
348            }
349        }
350    }
351
352    public void updateUserActivityTimeout() {
353        updateUserActivityTimeoutInWindowLayoutParams();
354        mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
355    }
356
357    private void updateUserActivityTimeoutInWindowLayoutParams() {
358        // Use the user activity timeout requested by the keyguard view, if any.
359        if (mKeyguardView != null) {
360            long timeout = mKeyguardView.getUserActivityTimeout();
361            if (timeout >= 0) {
362                mWindowLayoutParams.userActivityTimeout = timeout;
363                return;
364            }
365        }
366
367        // Otherwise, use the default timeout.
368        mWindowLayoutParams.userActivityTimeout = KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS;
369    }
370
371    private void maybeEnableScreenRotation(boolean enableScreenRotation) {
372        // TODO: move this outside
373        if (enableScreenRotation) {
374            if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen On!");
375            mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
376        } else {
377            if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen Off!");
378            mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
379        }
380        mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
381    }
382
383    void updateShowWallpaper(boolean show) {
384        if (show) {
385            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
386        } else {
387            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
388        }
389
390        mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
391    }
392
393    public void setNeedsInput(boolean needsInput) {
394        mNeedsInput = needsInput;
395        if (mWindowLayoutParams != null) {
396            if (needsInput) {
397                mWindowLayoutParams.flags &=
398                    ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
399            } else {
400                mWindowLayoutParams.flags |=
401                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
402            }
403
404            try {
405                mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
406            } catch (java.lang.IllegalArgumentException e) {
407                // TODO: Ensure this method isn't called on views that are changing...
408                Log.w(TAG,"Can't update input method on " + mKeyguardHost + " window not attached");
409            }
410        }
411    }
412
413    /**
414     * Reset the state of the view.
415     */
416    public synchronized void reset(Bundle options) {
417        if (DEBUG) Log.d(TAG, "reset()");
418        // User might have switched, check if we need to go back to keyguard
419        // TODO: It's preferable to stay and show the correct lockscreen or unlock if none
420        maybeCreateKeyguardLocked(shouldEnableScreenRotation(), true, options);
421    }
422
423    public synchronized void onScreenTurnedOff() {
424        if (DEBUG) Log.d(TAG, "onScreenTurnedOff()");
425        mScreenOn = false;
426        if (mKeyguardView != null) {
427            mKeyguardView.onScreenTurnedOff();
428        }
429    }
430
431    public synchronized void onScreenTurnedOn(final IKeyguardShowCallback callback) {
432        if (DEBUG) Log.d(TAG, "onScreenTurnedOn()");
433        mScreenOn = true;
434
435        // If keyguard is not showing, we need to inform PhoneWindowManager with a null
436        // token so it doesn't wait for us to draw...
437        final IBinder token = isShowing() ? mKeyguardHost.getWindowToken() : null;
438
439        if (DEBUG && token == null) Slog.v(TAG, "send wm null token: "
440                + (mKeyguardHost == null ? "host was null" : "not showing"));
441
442        if (mKeyguardView != null) {
443            mKeyguardView.onScreenTurnedOn();
444
445            // Caller should wait for this window to be shown before turning
446            // on the screen.
447            if (callback != null) {
448                if (mKeyguardHost.getVisibility() == View.VISIBLE) {
449                    // Keyguard may be in the process of being shown, but not yet
450                    // updated with the window manager...  give it a chance to do so.
451                    mKeyguardHost.post(new Runnable() {
452                        @Override
453                        public void run() {
454                            try {
455                                callback.onShown(token);
456                            } catch (RemoteException e) {
457                                Slog.w(TAG, "Exception calling onShown():", e);
458                            }
459                        }
460                    });
461                } else {
462                    try {
463                        callback.onShown(token);
464                    } catch (RemoteException e) {
465                        Slog.w(TAG, "Exception calling onShown():", e);
466                    }
467                }
468            }
469        } else if (callback != null) {
470            try {
471                callback.onShown(token);
472            } catch (RemoteException e) {
473                Slog.w(TAG, "Exception calling onShown():", e);
474            }
475        }
476    }
477
478    public synchronized void verifyUnlock() {
479        if (DEBUG) Log.d(TAG, "verifyUnlock()");
480        show(null);
481        mKeyguardView.verifyUnlock();
482    }
483
484    /**
485     * Hides the keyguard view
486     */
487    public synchronized void hide() {
488        if (DEBUG) Log.d(TAG, "hide()");
489
490        if (mKeyguardHost != null) {
491            mKeyguardHost.setVisibility(View.GONE);
492
493            // We really only want to preserve keyguard state for configuration changes. Hence
494            // we should clear state of widgets (e.g. Music) when we hide keyguard so it can
495            // start with a fresh state when we return.
496            mStateContainer.clear();
497
498            // Don't do this right away, so we can let the view continue to animate
499            // as it goes away.
500            if (mKeyguardView != null) {
501                final KeyguardViewBase lastView = mKeyguardView;
502                mKeyguardView = null;
503                mKeyguardHost.postDelayed(new Runnable() {
504                    @Override
505                    public void run() {
506                        synchronized (KeyguardViewManager.this) {
507                            lastView.cleanUp();
508                            // Let go of any large bitmaps.
509                            mKeyguardHost.setCustomBackground(null);
510                            updateShowWallpaper(true);
511                            mKeyguardHost.removeView(lastView);
512                        }
513                    }
514                }, 500);
515            }
516        }
517    }
518
519    /**
520     * Dismisses the keyguard by going to the next screen or making it gone.
521     */
522    public synchronized void dismiss() {
523        if (mScreenOn) {
524            mKeyguardView.dismiss();
525        }
526    }
527
528    /**
529     * @return Whether the keyguard is showing
530     */
531    public synchronized boolean isShowing() {
532        return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE);
533    }
534
535    public void showAssistant() {
536        if (mKeyguardView != null) {
537            mKeyguardView.showAssistant();
538        }
539    }
540
541    public void dispatch(MotionEvent event) {
542        if (mKeyguardView != null) {
543            mKeyguardView.dispatch(event);
544        }
545    }
546
547    public void launchCamera() {
548        if (mKeyguardView != null) {
549            mKeyguardView.launchCamera();
550        }
551    }
552}
553