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