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