KeyguardViewManager.java revision 75931e05470992d51d135d09031902fd1b3fdbc7
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.internal.policy.impl.keyguard;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.content.Context;
22import android.content.pm.ActivityInfo;
23import android.content.res.Configuration;
24import android.content.res.Resources;
25import android.graphics.PixelFormat;
26import android.os.Bundle;
27import android.os.IBinder;
28import android.os.Parcelable;
29import android.os.SystemProperties;
30import android.util.Log;
31import android.util.Slog;
32import android.util.SparseArray;
33import android.view.KeyEvent;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.ViewGroup;
37import android.view.ViewManager;
38import android.view.WindowManager;
39import android.widget.FrameLayout;
40
41import com.android.internal.R;
42import com.android.internal.widget.LockPatternUtils;
43
44/**
45 * Manages creating, showing, hiding and resetting the keyguard.  Calls back
46 * via {@link KeyguardViewMediator.ViewMediatorCallback} to poke
47 * the wake lock and report that the keyguard is done, which is in turn,
48 * reported to this class by the current {@link KeyguardViewBase}.
49 */
50public class KeyguardViewManager {
51    private final static boolean DEBUG = KeyguardViewMediator.DEBUG;
52    private static String TAG = "KeyguardViewManager";
53    public static boolean USE_UPPER_CASE = true;
54
55    // Timeout used for keypresses
56    static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
57
58    private final Context mContext;
59    private final ViewManager mViewManager;
60    private final KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback;
61
62    private WindowManager.LayoutParams mWindowLayoutParams;
63    private boolean mNeedsInput = false;
64
65    private FrameLayout mKeyguardHost;
66    private KeyguardHostView mKeyguardView;
67
68    private boolean mScreenOn = false;
69    private LockPatternUtils mLockPatternUtils;
70
71    public interface ShowListener {
72        void onShown(IBinder windowToken);
73    };
74
75    /**
76     * @param context Used to create views.
77     * @param viewManager Keyguard will be attached to this.
78     * @param callback Used to notify of changes.
79     * @param lockPatternUtils
80     */
81    public KeyguardViewManager(Context context, ViewManager viewManager,
82            KeyguardViewMediator.ViewMediatorCallback callback,
83            LockPatternUtils lockPatternUtils) {
84        mContext = context;
85        mViewManager = viewManager;
86        mViewMediatorCallback = callback;
87        mLockPatternUtils = lockPatternUtils;
88    }
89
90    /**
91     * Show the keyguard.  Will handle creating and attaching to the view manager
92     * lazily.
93     */
94    public synchronized void show(Bundle options) {
95        if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView);
96
97        boolean enableScreenRotation = shouldEnableScreenRotation();
98
99        maybeCreateKeyguardLocked(enableScreenRotation, false, options);
100        maybeEnableScreenRotation(enableScreenRotation);
101
102        // Disable common aspects of the system/status/navigation bars that are not appropriate or
103        // useful on any keyguard screen but can be re-shown by dialogs or SHOW_WHEN_LOCKED
104        // activities. Other disabled bits are handled by the KeyguardViewMediator talking
105        // directly to the status bar service.
106        final int visFlags = View.STATUS_BAR_DISABLE_HOME;
107        if (DEBUG) Log.v(TAG, "show:setSystemUiVisibility(" + Integer.toHexString(visFlags)+")");
108        mKeyguardHost.setSystemUiVisibility(visFlags);
109
110        mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
111        mKeyguardHost.setVisibility(View.VISIBLE);
112        mKeyguardView.show();
113        mKeyguardView.requestFocus();
114    }
115
116    private boolean shouldEnableScreenRotation() {
117        Resources res = mContext.getResources();
118        return SystemProperties.getBoolean("lockscreen.rot_override",false)
119                || res.getBoolean(com.android.internal.R.bool.config_enableLockScreenRotation);
120    }
121
122    class ViewManagerHost extends FrameLayout {
123        public ViewManagerHost(Context context) {
124            super(context);
125        }
126
127        @Override
128        protected void onConfigurationChanged(Configuration newConfig) {
129            super.onConfigurationChanged(newConfig);
130            if (mKeyguardHost.getVisibility() == View.VISIBLE) {
131                // only propagate configuration messages if we're currently showing
132                maybeCreateKeyguardLocked(shouldEnableScreenRotation(), true, null);
133            } else {
134                if (DEBUG) Log.v(TAG, "onConfigurationChanged: view not visible");
135            }
136        }
137
138        @Override
139        public boolean dispatchKeyEvent(KeyEvent event) {
140            if (event.getAction() == KeyEvent.ACTION_DOWN && mKeyguardView != null) {
141                int keyCode = event.getKeyCode();
142                if (keyCode == KeyEvent.KEYCODE_BACK && mKeyguardView.handleBackKey()) {
143                    return true;
144                } else if (keyCode == KeyEvent.KEYCODE_MENU && mKeyguardView.handleMenuKey()) {
145                    return true;
146                }
147            }
148            return super.dispatchKeyEvent(event);
149        }
150    }
151
152    SparseArray<Parcelable> mStateContainer = new SparseArray<Parcelable>();
153
154    private void maybeCreateKeyguardLocked(boolean enableScreenRotation, boolean force,
155            Bundle options) {
156        final boolean isActivity = (mContext instanceof Activity); // for test activity
157
158        if (mKeyguardHost != null) {
159            mKeyguardHost.saveHierarchyState(mStateContainer);
160        }
161
162        if (mKeyguardHost == null) {
163            if (DEBUG) Log.d(TAG, "keyguard host is null, creating it...");
164
165            mKeyguardHost = new ViewManagerHost(mContext);
166
167            int flags = WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN
168                    | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
169
170            if (!mNeedsInput) {
171                flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
172            }
173            if (ActivityManager.isHighEndGfx()) {
174                flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
175            }
176
177            final int stretch = ViewGroup.LayoutParams.MATCH_PARENT;
178            final int type = isActivity ? WindowManager.LayoutParams.TYPE_APPLICATION
179                    : WindowManager.LayoutParams.TYPE_KEYGUARD;
180            WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
181                    stretch, stretch, type, flags, PixelFormat.TRANSLUCENT);
182            lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
183            lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen;
184            if (ActivityManager.isHighEndGfx()) {
185                lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
186                lp.privateFlags |=
187                        WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
188            }
189            lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY;
190            if (isActivity) {
191                lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
192            }
193            lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
194            lp.setTitle(isActivity ? "KeyguardMock" : "Keyguard");
195            mWindowLayoutParams = lp;
196            mViewManager.addView(mKeyguardHost, lp);
197        }
198
199        if (force || mKeyguardView == null) {
200            inflateKeyguardView(options);
201        }
202        updateUserActivityTimeoutInWindowLayoutParams();
203        mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
204
205        mKeyguardHost.restoreHierarchyState(mStateContainer);
206    }
207
208    private void inflateKeyguardView(Bundle options) {
209        View v = mKeyguardHost.findViewById(R.id.keyguard_host_view);
210        if (v != null) {
211            mKeyguardHost.removeView(v);
212        }
213        // TODO: Remove once b/7094175 is fixed
214        Slog.d(TAG, "inflateKeyguardView: b/7094175 mContext.config="
215                + mContext.getResources().getConfiguration());
216        final LayoutInflater inflater = LayoutInflater.from(mContext);
217        View view = inflater.inflate(R.layout.keyguard_host_view, mKeyguardHost, true);
218        mKeyguardView = (KeyguardHostView) view.findViewById(R.id.keyguard_host_view);
219        mKeyguardView.setLockPatternUtils(mLockPatternUtils);
220        mKeyguardView.setViewMediatorCallback(mViewMediatorCallback);
221
222        // HACK
223        // The keyguard view will have set up window flags in onFinishInflate before we set
224        // the view mediator callback. Make sure it knows the correct IME state.
225        if (mViewMediatorCallback != null) {
226            KeyguardPasswordView kpv = (KeyguardPasswordView) mKeyguardView.findViewById(
227                    R.id.keyguard_password_view);
228
229            if (kpv != null) {
230                mViewMediatorCallback.setNeedsInput(kpv.needsInput());
231            }
232        }
233
234        if (options != null) {
235            if (options.getBoolean(LockPatternUtils.KEYGUARD_SHOW_USER_SWITCHER)) {
236                mKeyguardView.goToUserSwitcher();
237            }
238            if (options.getBoolean(LockPatternUtils.KEYGUARD_SHOW_SECURITY_CHALLENGE)) {
239                mKeyguardView.showNextSecurityScreenIfPresent();
240            }
241        }
242    }
243
244    public void updateUserActivityTimeout() {
245        updateUserActivityTimeoutInWindowLayoutParams();
246        mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
247    }
248
249    private void updateUserActivityTimeoutInWindowLayoutParams() {
250        // Use the user activity timeout requested by the keyguard view, if any.
251        if (mKeyguardView != null) {
252            long timeout = mKeyguardView.getUserActivityTimeout();
253            if (timeout >= 0) {
254                mWindowLayoutParams.userActivityTimeout = timeout;
255                return;
256            }
257        }
258
259        // Otherwise, use the default timeout.
260        mWindowLayoutParams.userActivityTimeout = KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS;
261    }
262
263    private void maybeEnableScreenRotation(boolean enableScreenRotation) {
264        // TODO: move this outside
265        if (enableScreenRotation) {
266            if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen On!");
267            mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
268        } else {
269            if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen Off!");
270            mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
271        }
272        mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
273    }
274
275    public void setNeedsInput(boolean needsInput) {
276        mNeedsInput = needsInput;
277        if (mWindowLayoutParams != null) {
278            if (needsInput) {
279                mWindowLayoutParams.flags &=
280                    ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
281            } else {
282                mWindowLayoutParams.flags |=
283                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
284            }
285
286            try {
287                mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
288            } catch (java.lang.IllegalArgumentException e) {
289                // TODO: Ensure this method isn't called on views that are changing...
290                Log.w(TAG,"Can't update input method on " + mKeyguardHost + " window not attached");
291            }
292        }
293    }
294
295    /**
296     * Reset the state of the view.
297     */
298    public synchronized void reset(Bundle options) {
299        if (DEBUG) Log.d(TAG, "reset()");
300        // User might have switched, check if we need to go back to keyguard
301        // TODO: It's preferable to stay and show the correct lockscreen or unlock if none
302        maybeCreateKeyguardLocked(shouldEnableScreenRotation(), true, options);
303    }
304
305    public synchronized void onScreenTurnedOff() {
306        if (DEBUG) Log.d(TAG, "onScreenTurnedOff()");
307        mScreenOn = false;
308        if (mKeyguardView != null) {
309            mKeyguardView.onScreenTurnedOff();
310        }
311    }
312
313    public synchronized void onScreenTurnedOn(
314            final KeyguardViewManager.ShowListener showListener) {
315        if (DEBUG) Log.d(TAG, "onScreenTurnedOn()");
316        mScreenOn = true;
317        if (mKeyguardView != null) {
318            mKeyguardView.onScreenTurnedOn();
319
320            // Caller should wait for this window to be shown before turning
321            // on the screen.
322            if (showListener != null) {
323                if (mKeyguardHost.getVisibility() == View.VISIBLE) {
324                    // Keyguard may be in the process of being shown, but not yet
325                    // updated with the window manager...  give it a chance to do so.
326                    mKeyguardHost.post(new Runnable() {
327                        @Override
328                        public void run() {
329                            if (mKeyguardHost.getVisibility() == View.VISIBLE) {
330                                showListener.onShown(mKeyguardHost.getWindowToken());
331                            } else {
332                                showListener.onShown(null);
333                            }
334                        }
335                    });
336                } else {
337                    showListener.onShown(null);
338                }
339            }
340        } else if (showListener != null) {
341            showListener.onShown(null);
342        }
343    }
344
345    public synchronized void verifyUnlock() {
346        if (DEBUG) Log.d(TAG, "verifyUnlock()");
347        show(null);
348        mKeyguardView.verifyUnlock();
349    }
350
351    /**
352     * A key has woken the device.  We use this to potentially adjust the state
353     * of the lock screen based on the key.
354     *
355     * The 'Tq' suffix is per the documentation in {@link android.view.WindowManagerPolicy}.
356     * Be sure not to take any action that takes a long time; any significant
357     * action should be posted to a handler.
358     *
359     * @param keyCode The wake key.  May be {@link KeyEvent#KEYCODE_UNKNOWN} if waking
360     * for a reason other than a key press.
361     */
362    public boolean wakeWhenReadyTq(int keyCode) {
363        if (DEBUG) Log.d(TAG, "wakeWhenReady(" + keyCode + ")");
364        if (mKeyguardView != null) {
365            mKeyguardView.wakeWhenReadyTq(keyCode);
366            return true;
367        }
368        Log.w(TAG, "mKeyguardView is null in wakeWhenReadyTq");
369        return false;
370    }
371
372    /**
373     * Hides the keyguard view
374     */
375    public synchronized void hide() {
376        if (DEBUG) Log.d(TAG, "hide()");
377
378        if (mKeyguardHost != null) {
379            mKeyguardHost.setVisibility(View.GONE);
380
381            // We really only want to preserve keyguard state for configuration changes. Hence
382            // we should clear state of widgets (e.g. Music) when we hide keyguard so it can
383            // start with a fresh state when we return.
384            mStateContainer.clear();
385
386            // Don't do this right away, so we can let the view continue to animate
387            // as it goes away.
388            if (mKeyguardView != null) {
389                final KeyguardViewBase lastView = mKeyguardView;
390                mKeyguardView = null;
391                mKeyguardHost.postDelayed(new Runnable() {
392                    @Override
393                    public void run() {
394                        synchronized (KeyguardViewManager.this) {
395                            lastView.cleanUp();
396                            mKeyguardHost.removeView(lastView);
397                        }
398                    }
399                }, 500);
400            }
401        }
402    }
403
404    /**
405     * Dismisses the keyguard by going to the next screen or making it gone.
406     */
407    public synchronized void dismiss() {
408        if (mScreenOn) {
409            mKeyguardView.dismiss();
410        }
411    }
412
413    /**
414     * @return Whether the keyguard is showing
415     */
416    public synchronized boolean isShowing() {
417        return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE);
418    }
419}
420