KeyguardHostView.java revision 0207c0992cb05d0acc60540748c8ae6985917abb
1/*
2 * Copyright (C) 2012 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.app.ActivityOptions;
22import android.app.AlertDialog;
23import android.app.admin.DevicePolicyManager;
24import android.appwidget.AppWidgetHost;
25import android.appwidget.AppWidgetHostView;
26import android.appwidget.AppWidgetManager;
27import android.appwidget.AppWidgetProviderInfo;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentSender;
31import android.content.pm.UserInfo;
32import android.content.res.Resources;
33import android.graphics.Canvas;
34import android.graphics.Rect;
35import android.os.Looper;
36import android.os.UserManager;
37import android.util.AttributeSet;
38import android.util.Log;
39import android.util.Slog;
40import android.view.KeyEvent;
41import android.view.LayoutInflater;
42import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewGroup;
45import android.view.WindowManager;
46import android.view.animation.AnimationUtils;
47import android.widget.RemoteViews.OnClickHandler;
48import android.widget.ViewFlipper;
49
50import com.android.internal.R;
51import com.android.internal.policy.impl.keyguard.KeyguardSecurityModel.SecurityMode;
52import com.android.internal.widget.LockPatternUtils;
53
54import java.io.File;
55import java.util.List;
56
57public class KeyguardHostView extends KeyguardViewBase {
58    private static final String TAG = "KeyguardViewHost";
59
60    // Use this to debug all of keyguard
61    public static boolean DEBUG;
62
63    // also referenced in SecuritySettings.java
64    static final int APPWIDGET_HOST_ID = 0x4B455947;
65    private static final String KEYGUARD_WIDGET_PREFS = "keyguard_widget_prefs";
66
67    private AppWidgetHost mAppWidgetHost;
68    private KeyguardWidgetPager mAppWidgetContainer;
69    private ViewFlipper mSecurityViewContainer;
70    private boolean mEnableMenuKey;
71    private boolean mIsVerifyUnlockOnly;
72    private boolean mEnableFallback; // TODO: This should get the value from KeyguardPatternView
73    private SecurityMode mCurrentSecuritySelection = SecurityMode.None;
74
75    protected Runnable mLaunchRunnable;
76
77    protected int mFailedAttempts;
78    private LockPatternUtils mLockPatternUtils;
79
80    private KeyguardSecurityModel mSecurityModel;
81
82    private Rect mTempRect = new Rect();
83    private KeyguardTransportControlView mTransportControl;
84
85    /*package*/ interface TransportCallback {
86        void hide();
87        void show();
88    }
89
90    /*package*/ interface UserSwitcherCallback {
91        void hideSecurityView(int duration);
92        void showSecurityView();
93    }
94
95    public KeyguardHostView(Context context) {
96        this(context, null);
97    }
98
99    public KeyguardHostView(Context context, AttributeSet attrs) {
100        super(context, attrs);
101        mLockPatternUtils = new LockPatternUtils(context);
102        mAppWidgetHost = new AppWidgetHost(
103                context, APPWIDGET_HOST_ID, mOnClickHandler, Looper.myLooper());
104        mSecurityModel = new KeyguardSecurityModel(context);
105
106        // The following enables the MENU key to work for testing automation
107        mEnableMenuKey = shouldEnableMenuKey();
108        setFocusable(true);
109        setFocusableInTouchMode(true);
110    }
111
112    @Override
113    public boolean onTouchEvent(MotionEvent ev) {
114        boolean result = super.onTouchEvent(ev);
115        mTempRect.set(0, 0, 0, 0);
116        offsetRectIntoDescendantCoords(mSecurityViewContainer, mTempRect);
117        ev.offsetLocation(mTempRect.left, mTempRect.top);
118        result = mSecurityViewContainer.dispatchTouchEvent(ev) || result;
119        ev.offsetLocation(-mTempRect.left, -mTempRect.top);
120        return result;
121    }
122
123    @Override
124    protected void dispatchDraw(Canvas canvas) {
125        super.dispatchDraw(canvas);
126        if (mViewMediatorCallback != null) {
127            mViewMediatorCallback.keyguardDoneDrawing();
128        }
129    }
130
131    private int getWidgetPosition(int id) {
132        final int children = mAppWidgetContainer.getChildCount();
133        for (int i = 0; i < children; i++) {
134            if (mAppWidgetContainer.getChildAt(i).getId() == id) {
135                return i;
136            }
137        }
138        return -1;
139    }
140
141    @Override
142    protected void onFinishInflate() {
143        mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container);
144        KeyguardWidgetRegion kgwr = (KeyguardWidgetRegion) findViewById(R.id.kg_widget_region);
145        kgwr.setVisibility(VISIBLE);
146        mSecurityViewContainer = (ViewFlipper) findViewById(R.id.view_flipper);
147
148        // This code manages showing/hiding the transport control. We keep it around and only
149        // add it to the hierarchy if it needs to be present.
150        mTransportControl =
151                (KeyguardTransportControlView) findViewById(R.id.keyguard_transport_control);
152        if (mTransportControl != null) {
153            mTransportControl.setKeyguardCallback(new TransportCallback() {
154                boolean mSticky = false;
155                @Override
156                public void hide() {
157                    int page = getWidgetPosition(R.id.keyguard_transport_control);
158                    if (page != -1 && !mSticky) {
159                        if (page == mAppWidgetContainer.getCurrentPage()) {
160                            // Switch back to clock view if music was showing.
161                            mAppWidgetContainer
162                                .setCurrentPage(getWidgetPosition(R.id.keyguard_status_view));
163                        }
164                        mAppWidgetContainer.removeView(mTransportControl);
165                        // XXX keep view attached to hierarchy so we still get show/hide events
166                        // from AudioManager
167                        KeyguardHostView.this.addView(mTransportControl);
168                        mTransportControl.setVisibility(View.GONE);
169                        showAppropriateWidgetPage();
170                    }
171                }
172
173                @Override
174                public void show() {
175                    if (getWidgetPosition(R.id.keyguard_transport_control) == -1) {
176                        KeyguardHostView.this.removeView(mTransportControl);
177                        mAppWidgetContainer.addView(mTransportControl,
178                                getWidgetPosition(R.id.keyguard_status_view) + 1);
179                        mTransportControl.setVisibility(View.VISIBLE);
180                        // Once shown, leave it showing
181                        mSticky = true;
182                        showAppropriateWidgetPage();
183                    }
184                }
185            });
186        }
187        updateSecurityViews();
188        setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK);
189    }
190
191    private void updateSecurityViews() {
192        int children = mSecurityViewContainer.getChildCount();
193        for (int i = 0; i < children; i++) {
194            updateSecurityView(mSecurityViewContainer.getChildAt(i));
195        }
196    }
197
198    private void updateSecurityView(View view) {
199        if (view instanceof KeyguardSecurityView) {
200            KeyguardSecurityView ksv = (KeyguardSecurityView) view;
201            ksv.setKeyguardCallback(mCallback);
202            ksv.setLockPatternUtils(mLockPatternUtils);
203        } else {
204            Log.w(TAG, "View " + view + " is not a KeyguardSecurityView");
205        }
206    }
207
208    void setLockPatternUtils(LockPatternUtils utils) {
209        mSecurityModel.setLockPatternUtils(utils);
210        mLockPatternUtils = utils;
211        updateSecurityViews();
212    }
213
214    @Override
215    protected void onAttachedToWindow() {
216        super.onAttachedToWindow();
217        mAppWidgetHost.startListening();
218        maybePopulateWidgets();
219    }
220
221    @Override
222    protected void onDetachedFromWindow() {
223        super.onDetachedFromWindow();
224        mAppWidgetHost.stopListening();
225    }
226
227    private AppWidgetHost getAppWidgetHost() {
228        return mAppWidgetHost;
229    }
230
231    void addWidget(AppWidgetHostView view) {
232        mAppWidgetContainer.addWidget(view);
233    }
234
235    private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() {
236
237        public void userActivity(long timeout) {
238            if (mViewMediatorCallback != null) {
239                mViewMediatorCallback.userActivity(timeout);
240            }
241        }
242
243        public void dismiss(boolean authenticated) {
244            showNextSecurityScreenOrFinish(authenticated);
245        }
246
247        public boolean isVerifyUnlockOnly() {
248            return mIsVerifyUnlockOnly;
249        }
250
251        public void reportSuccessfulUnlockAttempt() {
252            KeyguardUpdateMonitor.getInstance(mContext).clearFailedUnlockAttempts();
253            mLockPatternUtils.reportSuccessfulPasswordAttempt();
254        }
255
256        public void reportFailedUnlockAttempt() {
257            if (mCurrentSecuritySelection == SecurityMode.Biometric) {
258                KeyguardUpdateMonitor.getInstance(mContext).reportFailedBiometricUnlockAttempt();
259            } else {
260                KeyguardHostView.this.reportFailedUnlockAttempt();
261            }
262        }
263
264        public int getFailedAttempts() {
265            return KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts();
266        }
267
268        @Override
269        public void showBackupSecurity() {
270            KeyguardHostView.this.showBackupSecurity();
271        }
272
273        @Override
274        public void setOnDismissRunnable(Runnable runnable) {
275            KeyguardHostView.this.setOnDismissRunnable(runnable);
276        }
277
278    };
279
280    private void showDialog(String title, String message) {
281        final AlertDialog dialog = new AlertDialog.Builder(mContext)
282            .setTitle(title)
283            .setMessage(message)
284            .setNeutralButton(com.android.internal.R.string.ok, null)
285            .create();
286        if (!(mContext instanceof Activity)) {
287            dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
288        }
289        dialog.show();
290    }
291
292    @Override
293    public boolean dispatchKeyEvent(KeyEvent event) {
294        if (event.getAction() == KeyEvent.ACTION_UP
295                && event.getKeyCode() == KeyEvent.KEYCODE_BACK
296                && mCurrentSecuritySelection != SecurityMode.None) {
297            mCallback.dismiss(false);
298            return true;
299        }
300        return super.dispatchKeyEvent(event);
301    }
302
303    private void showTimeoutDialog() {
304        int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
305        int messageId = 0;
306
307        switch (mSecurityModel.getSecurityMode()) {
308            case Pattern:
309                messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message;
310                break;
311
312            case Password: {
313                    final boolean isPin = mLockPatternUtils.getKeyguardStoredPasswordQuality() ==
314                        DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
315                    messageId = isPin ? R.string.kg_too_many_failed_pin_attempts_dialog_message
316                            : R.string.kg_too_many_failed_password_attempts_dialog_message;
317                }
318                break;
319        }
320
321        if (messageId != 0) {
322            final String message = mContext.getString(messageId,
323                    KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(),
324                    timeoutInSeconds);
325            showDialog(null, message);
326        }
327    }
328
329    private void showAlmostAtWipeDialog(int attempts, int remaining) {
330        int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
331        String message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe,
332                attempts, remaining);
333        showDialog(null, message);
334    }
335
336    private void showWipeDialog(int attempts) {
337        String message = mContext.getString(R.string.kg_failed_attempts_now_wiping, attempts);
338        showDialog(null, message);
339    }
340
341    private void showAlmostAtAccountLoginDialog() {
342        final int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
343        final int count = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
344                - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
345        String message = mContext.getString(R.string.kg_failed_attempts_almost_at_login,
346                count, LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, timeoutInSeconds);
347        showDialog(null, message);
348    }
349
350    private void reportFailedUnlockAttempt() {
351        final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
352        final int failedAttempts = monitor.getFailedUnlockAttempts() + 1; // +1 for this time
353
354        if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
355
356        SecurityMode mode = mSecurityModel.getSecurityMode();
357        final boolean usingPattern = mode == KeyguardSecurityModel.SecurityMode.Pattern;
358
359        final int failedAttemptsBeforeWipe = mLockPatternUtils.getDevicePolicyManager()
360                .getMaximumFailedPasswordsForWipe(null, mLockPatternUtils.getCurrentUser());
361
362        final int failedAttemptWarning = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
363                - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
364
365        final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ?
366                (failedAttemptsBeforeWipe - failedAttempts)
367                : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
368
369        boolean showTimeout = false;
370        if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
371            // If we reach this code, it means the user has installed a DevicePolicyManager
372            // that requests device wipe after N attempts.  Once we get below the grace
373            // period, we'll post this dialog every time as a clear warning until the
374            // bombshell hits and the device is wiped.
375            if (remainingBeforeWipe > 0) {
376                showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe);
377            } else {
378                // Too many attempts. The device will be wiped shortly.
379                Slog.i(TAG, "Too many unlock attempts; device will be wiped!");
380                showWipeDialog(failedAttempts);
381            }
382        } else {
383            showTimeout =
384                (failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) == 0;
385            if (usingPattern && mEnableFallback) {
386                if (failedAttempts == failedAttemptWarning) {
387                    showAlmostAtAccountLoginDialog();
388                    showTimeout = false; // don't show both dialogs
389                } else if (failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) {
390                    mLockPatternUtils.setPermanentlyLocked(true);
391                    showSecurityScreen(SecurityMode.Account);
392                    // don't show timeout dialog because we show account unlock screen next
393                    showTimeout = false;
394                }
395            }
396        }
397        monitor.reportFailedUnlockAttempt();
398        mLockPatternUtils.reportFailedPasswordAttempt();
399        if (showTimeout) {
400            showTimeoutDialog();
401        }
402    }
403
404    /**
405     * Shows the backup security screen for the current security mode.  This could be used for
406     * password recovery screens but is currently only used for pattern unlock to show the
407     * account unlock screen and biometric unlock to show the user's normal unlock.
408     */
409    private void showBackupSecurity() {
410        SecurityMode currentMode = mSecurityModel.getAlternateFor(mSecurityModel.getSecurityMode());
411        showSecurityScreen(mSecurityModel.getBackupFor(currentMode));
412    }
413
414    private void showNextSecurityScreenOrFinish(boolean authenticated) {
415        boolean finish = false;
416        if (SecurityMode.None == mCurrentSecuritySelection) {
417            SecurityMode securityMode = mSecurityModel.getSecurityMode();
418            // Allow an alternate, such as biometric unlock
419            securityMode = mSecurityModel.getAlternateFor(securityMode);
420            if (SecurityMode.None == securityMode) {
421                finish = true; // no security required
422            } else {
423                showSecurityScreen(securityMode); // switch to the alternate security view
424            }
425        } else if (authenticated) {
426            switch (mCurrentSecuritySelection) {
427                case Pattern:
428                case Password:
429                case Account:
430                case Biometric:
431                    finish = true;
432                    break;
433
434                case SimPin:
435                case SimPuk:
436                    // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
437                    SecurityMode securityMode = mSecurityModel.getSecurityMode();
438                    if (securityMode != SecurityMode.None) {
439                        showSecurityScreen(securityMode);
440                    } else {
441                        finish = true;
442                    }
443                    break;
444
445                default:
446                    showSecurityScreen(SecurityMode.None);
447                    break;
448            }
449        } else {
450            // Not authenticated but we were asked to dismiss so go back to selector screen.
451            showSecurityScreen(SecurityMode.None);
452        }
453        if (finish) {
454            // If there's a pending runnable because the user interacted with a widget
455            // and we're leaving keyguard, then run it.
456            if (mLaunchRunnable != null) {
457                mLaunchRunnable.run();
458                mLaunchRunnable = null;
459            }
460            if (mViewMediatorCallback != null) {
461                mViewMediatorCallback.keyguardDone(true);
462            }
463        }
464    }
465
466    private OnClickHandler mOnClickHandler = new OnClickHandler() {
467        @Override
468        public boolean onClickHandler(final View view,
469                final android.app.PendingIntent pendingIntent,
470                final Intent fillInIntent) {
471            if (pendingIntent.isActivity()) {
472                setOnDismissRunnable(new Runnable() {
473                    public void run() {
474                        try {
475                              // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
476                              Context context = view.getContext();
477                              ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view,
478                                      0, 0,
479                                      view.getMeasuredWidth(), view.getMeasuredHeight());
480                              context.startIntentSender(
481                                      pendingIntent.getIntentSender(), fillInIntent,
482                                      Intent.FLAG_ACTIVITY_NEW_TASK,
483                                      Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle());
484                          } catch (IntentSender.SendIntentException e) {
485                              android.util.Log.e(TAG, "Cannot send pending intent: ", e);
486                          } catch (Exception e) {
487                              android.util.Log.e(TAG, "Cannot send pending intent due to " +
488                                      "unknown exception: ", e);
489                          }
490                    }
491                });
492
493                mCallback.dismiss(false);
494                return true;
495            } else {
496                return super.onClickHandler(view, pendingIntent, fillInIntent);
497            }
498        };
499    };
500
501    @Override
502    public void reset() {
503        mIsVerifyUnlockOnly = false;
504        mAppWidgetContainer.setCurrentPage(getWidgetPosition(R.id.keyguard_status_view));
505        requestFocus();
506    }
507
508    /**
509     *  Sets a runnable to run when keyguard is dismissed
510     * @param runnable
511     */
512    protected void setOnDismissRunnable(Runnable runnable) {
513        mLaunchRunnable = runnable;
514    }
515
516    private KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
517        final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
518        KeyguardSecurityView view = null;
519        final int children = mSecurityViewContainer.getChildCount();
520        for (int child = 0; child < children; child++) {
521            if (mSecurityViewContainer.getChildAt(child).getId() == securityViewIdForMode) {
522                view = ((KeyguardSecurityView)mSecurityViewContainer.getChildAt(child));
523                break;
524            }
525        }
526        if (view == null) {
527            final LayoutInflater inflater = LayoutInflater.from(mContext);
528            View v = inflater.inflate(getLayoutIdFor(securityMode), this, false);
529            mSecurityViewContainer.addView(v);
530            updateSecurityView(v);
531            view = (KeyguardSecurityView) v;
532        }
533        return view;
534    }
535
536    /**
537     * Switches to the given security view unless it's already being shown, in which case
538     * this is a no-op.
539     *
540     * @param securityMode
541     */
542    private void showSecurityScreen(SecurityMode securityMode) {
543
544        if (securityMode == mCurrentSecuritySelection) return;
545
546        KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection);
547        KeyguardSecurityView newView = getSecurityView(securityMode);
548
549        // Emulate Activity life cycle
550        oldView.onPause();
551        newView.onResume();
552
553        final boolean needsInput = newView.needsInput();
554        if (mViewMediatorCallback != null) {
555            mViewMediatorCallback.setNeedsInput(needsInput);
556        }
557
558        // Find and show this child.
559        final int childCount = mSecurityViewContainer.getChildCount();
560
561        // If we're go to/from the selector view, do flip animation, otherwise use fade animation.
562        final boolean doFlip = mCurrentSecuritySelection == SecurityMode.None
563                || securityMode == SecurityMode.None;
564        final int inAnimation = doFlip ? R.anim.keyguard_security_animate_in
565                : R.anim.keyguard_security_fade_in;
566        final int outAnimation = doFlip ? R.anim.keyguard_security_animate_out
567                : R.anim.keyguard_security_fade_out;
568
569        mSecurityViewContainer.setInAnimation(AnimationUtils.loadAnimation(mContext, inAnimation));
570        mSecurityViewContainer.setOutAnimation(AnimationUtils.loadAnimation(mContext, outAnimation));
571        final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
572        for (int i = 0; i < childCount; i++) {
573            if (mSecurityViewContainer.getChildAt(i).getId() == securityViewIdForMode) {
574                mSecurityViewContainer.setDisplayedChild(i);
575                break;
576            }
577        }
578
579
580        if (securityMode == SecurityMode.None) {
581            // Discard current runnable if we're switching back to the selector view
582            setOnDismissRunnable(null);
583            setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK);
584        } else {
585            setSystemUiVisibility(getSystemUiVisibility() & (~View.STATUS_BAR_DISABLE_BACK));
586        }
587
588        mCurrentSecuritySelection = securityMode;
589    }
590
591    @Override
592    public void onScreenTurnedOn() {
593        if (DEBUG) Log.d(TAG, "screen on");
594        showSecurityScreen(mCurrentSecuritySelection);
595        getSecurityView(mCurrentSecuritySelection).onResume();
596
597        // This is a an attempt to fix bug 7137389 where the device comes back on but the entire
598        // layout is blank but forcing a layout causes it to reappear (e.g. with with
599        // hierarchyviewer).
600        requestLayout();
601    }
602
603    @Override
604    public void onScreenTurnedOff() {
605        if (DEBUG) Log.d(TAG, "screen off");
606        showSecurityScreen(SecurityMode.None);
607        getSecurityView(mCurrentSecuritySelection).onPause();
608    }
609
610    @Override
611    public void show() {
612        onScreenTurnedOn();
613    }
614
615    private boolean isSecure() {
616        SecurityMode mode = mSecurityModel.getSecurityMode();
617        switch (mode) {
618            case Pattern:
619                return mLockPatternUtils.isLockPatternEnabled();
620            case Password:
621                return mLockPatternUtils.isLockPasswordEnabled();
622            case SimPin:
623            case SimPuk:
624            case Account:
625                return true;
626            case None:
627                return false;
628            default:
629                throw new IllegalStateException("Unknown security mode " + mode);
630        }
631    }
632
633    @Override
634    public void wakeWhenReadyTq(int keyCode) {
635        if (DEBUG) Log.d(TAG, "onWakeKey");
636        if (keyCode == KeyEvent.KEYCODE_MENU && isSecure()) {
637            if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU");
638            showSecurityScreen(SecurityMode.None);
639        } else {
640            if (DEBUG) Log.d(TAG, "poking wake lock immediately");
641        }
642        if (mViewMediatorCallback != null) {
643            mViewMediatorCallback.wakeUp();
644        }
645    }
646
647    @Override
648    public void verifyUnlock() {
649        SecurityMode securityMode = mSecurityModel.getSecurityMode();
650        if (securityMode == KeyguardSecurityModel.SecurityMode.None) {
651            if (mViewMediatorCallback != null) {
652                mViewMediatorCallback.keyguardDone(true);
653            }
654        } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern
655                && securityMode != KeyguardSecurityModel.SecurityMode.Password) {
656            // can only verify unlock when in pattern/password mode
657            if (mViewMediatorCallback != null) {
658                mViewMediatorCallback.keyguardDone(false);
659            }
660        } else {
661            // otherwise, go to the unlock screen, see if they can verify it
662            mIsVerifyUnlockOnly = true;
663            showSecurityScreen(securityMode);
664        }
665    }
666
667    private int getSecurityViewIdForMode(SecurityMode securityMode) {
668        switch (securityMode) {
669            case None: return R.id.keyguard_selector_view;
670            case Pattern: return R.id.keyguard_pattern_view;
671            case Password: return R.id.keyguard_password_view;
672            case Biometric: return R.id.keyguard_face_unlock_view;
673            case Account: return R.id.keyguard_account_view;
674            case SimPin: return R.id.keyguard_sim_pin_view;
675            case SimPuk: return R.id.keyguard_sim_puk_view;
676        }
677        return 0;
678    }
679
680    private int getLayoutIdFor(SecurityMode securityMode) {
681        switch (securityMode) {
682            case None: return R.layout.keyguard_selector_view;
683            case Pattern: return R.layout.keyguard_pattern_view;
684            case Password: return R.layout.keyguard_password_view;
685            case Biometric: return R.layout.keyguard_face_unlock_view;
686            case Account: return R.layout.keyguard_account_view;
687            case SimPin: return R.layout.keyguard_sim_pin_view;
688            case SimPuk: return R.layout.keyguard_sim_puk_view;
689            default:
690                throw new RuntimeException("No layout for securityMode " + securityMode);
691        }
692    }
693
694    private void addWidget(int appId) {
695        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
696        AppWidgetProviderInfo appWidgetInfo = appWidgetManager.getAppWidgetInfo(appId);
697        if (appWidgetInfo != null) {
698            AppWidgetHostView view = getAppWidgetHost().createView(mContext, appId, appWidgetInfo);
699            addWidget(view);
700        } else {
701            Log.w(TAG, "AppWidgetInfo was null; not adding widget id " + appId);
702        }
703    }
704
705    private void maybePopulateWidgets() {
706        DevicePolicyManager dpm =
707                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
708        if (dpm != null) {
709            final int currentUser = mLockPatternUtils.getCurrentUser();
710            final int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, currentUser);
711            if ((disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0) {
712                Log.v(TAG, "Keyguard widgets disabled because of device policy admin");
713                return;
714            }
715        }
716        inflateAndAddUserSelectorWidgetIfNecessary();
717
718        // Add status widget
719        int statusWidgetId = mLockPatternUtils.getStatusWidget();
720        if (statusWidgetId != -1) {
721            addWidget(statusWidgetId);
722            View newStatusWidget = mAppWidgetContainer.getChildAt(
723                    mAppWidgetContainer.getChildCount() - 1);
724
725            int oldStatusWidgetPosition = getWidgetPosition(R.id.keyguard_status_view);
726            mAppWidgetContainer.removeViewAt(oldStatusWidgetPosition);
727
728            // Re-add new status widget at position of old one
729            mAppWidgetContainer.removeView(newStatusWidget);
730            newStatusWidget.setId(R.id.keyguard_status_view);
731            mAppWidgetContainer.addView(newStatusWidget, oldStatusWidgetPosition);
732        }
733
734        // Add user-selected widget
735        final int[] widgets = mLockPatternUtils.getUserDefinedWidgets();
736        for (int i = 0; i < widgets.length; i++) {
737            if (widgets[i] != -1) {
738                addWidget(widgets[i]);
739            }
740        }
741        showAppropriateWidgetPage();
742    }
743
744    private void showAppropriateWidgetPage() {
745        int page = mAppWidgetContainer.indexOfChild(findViewById(R.id.keyguard_status_view));
746        if (mAppWidgetContainer.indexOfChild(mTransportControl) != -1) {
747            page = mAppWidgetContainer.indexOfChild(mTransportControl);
748        }
749        mAppWidgetContainer.setCurrentPage(page);
750    }
751
752    private void inflateAndAddUserSelectorWidgetIfNecessary() {
753        // if there are multiple users, we need to add the multi-user switcher widget to the
754        // keyguard.
755        UserManager mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
756        List<UserInfo> users = mUm.getUsers(true);
757
758        if (users.size() > 1) {
759            KeyguardWidgetFrame userSwitcher = (KeyguardWidgetFrame)
760                LayoutInflater.from(mContext).inflate(R.layout.keyguard_multi_user_selector_widget,
761                        mAppWidgetContainer, false);
762
763            // add the switcher in the first position
764            mAppWidgetContainer.addView(userSwitcher, getWidgetPosition(R.id.keyguard_status_view));
765            KeyguardMultiUserSelectorView multiUser = (KeyguardMultiUserSelectorView)
766                    userSwitcher.getChildAt(0);
767
768            UserSwitcherCallback callback = new UserSwitcherCallback() {
769                @Override
770                public void hideSecurityView(int duration) {
771                    mSecurityViewContainer.animate().alpha(0).setDuration(duration);
772                }
773
774                @Override
775                public void showSecurityView() {
776                    mSecurityViewContainer.setAlpha(1.0f);
777                }
778            };
779            multiUser.setCallback(callback);
780        }
781    }
782
783    @Override
784    public void cleanUp() {
785
786    }
787
788    /**
789     * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
790     * some cases where we wish to disable it, notably when the menu button placement or technology
791     * is prone to false positives.
792     *
793     * @return true if the menu key should be enabled
794     */
795    private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
796    private boolean shouldEnableMenuKey() {
797        final Resources res = getResources();
798        final boolean configDisabled = res.getBoolean(
799                com.android.internal.R.bool.config_disableMenuKeyInLockScreen);
800        final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
801        final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
802        return !configDisabled || isTestHarness || fileOverride;
803    }
804
805    @Override
806    public boolean onKeyDown(int keyCode, KeyEvent event) {
807        if (keyCode == KeyEvent.KEYCODE_MENU && mEnableMenuKey) {
808            showNextSecurityScreenOrFinish(false);
809            return true;
810        } else {
811            return super.onKeyDown(keyCode, event);
812        }
813    }
814
815}
816