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