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