Home | History | Annotate | Download | only in keyguard
      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 package com.android.keyguard;
     17 
     18 import android.accounts.Account;
     19 import android.accounts.AccountManager;
     20 import android.accounts.AccountManagerCallback;
     21 import android.accounts.AccountManagerFuture;
     22 import android.accounts.AuthenticatorException;
     23 import android.accounts.OperationCanceledException;
     24 import android.content.Context;
     25 import android.graphics.Rect;
     26 import android.graphics.drawable.Drawable;
     27 import android.os.Bundle;
     28 import android.os.CountDownTimer;
     29 import android.os.SystemClock;
     30 import android.os.UserHandle;
     31 import android.util.AttributeSet;
     32 import android.util.Log;
     33 import android.view.MotionEvent;
     34 import android.view.View;
     35 import android.widget.Button;
     36 import android.widget.LinearLayout;
     37 
     38 import com.android.internal.widget.LockPatternUtils;
     39 import com.android.internal.widget.LockPatternView;
     40 
     41 import java.io.IOException;
     42 import java.util.List;
     43 
     44 public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView {
     45 
     46     private static final String TAG = "SecurityPatternView";
     47     private static final boolean DEBUG = false;
     48 
     49     // how long before we clear the wrong pattern
     50     private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
     51 
     52     // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
     53     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
     54 
     55     // how long we stay awake after the user hits the first dot.
     56     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000;
     57 
     58     // how many cells the user has to cross before we poke the wakelock
     59     private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
     60 
     61     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     62 
     63     private CountDownTimer mCountdownTimer = null;
     64     private LockPatternUtils mLockPatternUtils;
     65     private LockPatternView mLockPatternView;
     66     private Button mForgotPatternButton;
     67     private KeyguardSecurityCallback mCallback;
     68     private boolean mEnableFallback;
     69 
     70     /**
     71      * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
     72      * Initialized to something guaranteed to make us poke the wakelock when the user starts
     73      * drawing the pattern.
     74      * @see #dispatchTouchEvent(android.view.MotionEvent)
     75      */
     76     private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
     77 
     78     /**
     79      * Useful for clearing out the wrong pattern after a delay
     80      */
     81     private Runnable mCancelPatternRunnable = new Runnable() {
     82         public void run() {
     83             mLockPatternView.clearPattern();
     84         }
     85     };
     86     private Rect mTempRect = new Rect();
     87     private SecurityMessageDisplay mSecurityMessageDisplay;
     88     private View mEcaView;
     89     private Drawable mBouncerFrame;
     90 
     91     enum FooterMode {
     92         Normal,
     93         ForgotLockPattern,
     94         VerifyUnlocked
     95     }
     96 
     97     public KeyguardPatternView(Context context) {
     98         this(context, null);
     99     }
    100 
    101     public KeyguardPatternView(Context context, AttributeSet attrs) {
    102         super(context, attrs);
    103         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
    104     }
    105 
    106     public void setKeyguardCallback(KeyguardSecurityCallback callback) {
    107         mCallback = callback;
    108     }
    109 
    110     public void setLockPatternUtils(LockPatternUtils utils) {
    111         mLockPatternUtils = utils;
    112     }
    113 
    114     @Override
    115     protected void onFinishInflate() {
    116         super.onFinishInflate();
    117         mLockPatternUtils = mLockPatternUtils == null
    118                 ? new LockPatternUtils(mContext) : mLockPatternUtils;
    119 
    120         mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView);
    121         mLockPatternView.setSaveEnabled(false);
    122         mLockPatternView.setFocusable(false);
    123         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
    124 
    125         // stealth mode will be the same for the life of this screen
    126         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled());
    127 
    128         // vibrate mode will be the same for the life of this screen
    129         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
    130 
    131         mForgotPatternButton = (Button) findViewById(R.id.forgot_password_button);
    132         // note: some configurations don't have an emergency call area
    133         if (mForgotPatternButton != null) {
    134             mForgotPatternButton.setText(R.string.kg_forgot_pattern_button_text);
    135             mForgotPatternButton.setOnClickListener(new OnClickListener() {
    136                 public void onClick(View v) {
    137                     mCallback.showBackupSecurity();
    138                 }
    139             });
    140         }
    141 
    142         setFocusableInTouchMode(true);
    143 
    144         maybeEnableFallback(mContext);
    145         mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
    146         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
    147         View bouncerFrameView = findViewById(R.id.keyguard_bouncer_frame);
    148         if (bouncerFrameView != null) {
    149             mBouncerFrame = bouncerFrameView.getBackground();
    150         }
    151     }
    152 
    153     private void updateFooter(FooterMode mode) {
    154         if (mForgotPatternButton == null) return; // no ECA? no footer
    155 
    156         switch (mode) {
    157             case Normal:
    158                 if (DEBUG) Log.d(TAG, "mode normal");
    159                 mForgotPatternButton.setVisibility(View.GONE);
    160                 break;
    161             case ForgotLockPattern:
    162                 if (DEBUG) Log.d(TAG, "mode ForgotLockPattern");
    163                 mForgotPatternButton.setVisibility(View.VISIBLE);
    164                 break;
    165             case VerifyUnlocked:
    166                 if (DEBUG) Log.d(TAG, "mode VerifyUnlocked");
    167                 mForgotPatternButton.setVisibility(View.GONE);
    168         }
    169     }
    170 
    171     @Override
    172     public boolean onTouchEvent(MotionEvent ev) {
    173         boolean result = super.onTouchEvent(ev);
    174         // as long as the user is entering a pattern (i.e sending a touch event that was handled
    175         // by this screen), keep poking the wake lock so that the screen will stay on.
    176         final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime;
    177         if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
    178             mLastPokeTime = SystemClock.elapsedRealtime();
    179         }
    180         mTempRect.set(0, 0, 0, 0);
    181         offsetRectIntoDescendantCoords(mLockPatternView, mTempRect);
    182         ev.offsetLocation(mTempRect.left, mTempRect.top);
    183         result = mLockPatternView.dispatchTouchEvent(ev) || result;
    184         ev.offsetLocation(-mTempRect.left, -mTempRect.top);
    185         return result;
    186     }
    187 
    188     public void reset() {
    189         // reset lock pattern
    190         mLockPatternView.enableInput();
    191         mLockPatternView.setEnabled(true);
    192         mLockPatternView.clearPattern();
    193 
    194         // if the user is currently locked out, enforce it.
    195         long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
    196         if (deadline != 0) {
    197             handleAttemptLockout(deadline);
    198         } else {
    199             displayDefaultSecurityMessage();
    200         }
    201 
    202         // the footer depends on how many total attempts the user has failed
    203         if (mCallback.isVerifyUnlockOnly()) {
    204             updateFooter(FooterMode.VerifyUnlocked);
    205         } else if (mEnableFallback &&
    206                 (mKeyguardUpdateMonitor.getFailedUnlockAttempts()
    207                         >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
    208             updateFooter(FooterMode.ForgotLockPattern);
    209         } else {
    210             updateFooter(FooterMode.Normal);
    211         }
    212 
    213     }
    214 
    215     private void displayDefaultSecurityMessage() {
    216         if (mKeyguardUpdateMonitor.getMaxBiometricUnlockAttemptsReached()) {
    217             mSecurityMessageDisplay.setMessage(R.string.faceunlock_multiple_failures, true);
    218         } else {
    219             mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false);
    220         }
    221     }
    222 
    223     @Override
    224     public void showUsabilityHint() {
    225     }
    226 
    227     /** TODO: hook this up */
    228     public void cleanUp() {
    229         if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
    230         mLockPatternUtils = null;
    231         mLockPatternView.setOnPatternListener(null);
    232     }
    233 
    234     @Override
    235     public void onWindowFocusChanged(boolean hasWindowFocus) {
    236         super.onWindowFocusChanged(hasWindowFocus);
    237         if (hasWindowFocus) {
    238             // when timeout dialog closes we want to update our state
    239             reset();
    240         }
    241     }
    242 
    243     private class UnlockPatternListener implements LockPatternView.OnPatternListener {
    244 
    245         public void onPatternStart() {
    246             mLockPatternView.removeCallbacks(mCancelPatternRunnable);
    247         }
    248 
    249         public void onPatternCleared() {
    250         }
    251 
    252         public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
    253             // To guard against accidental poking of the wakelock, look for
    254             // the user actually trying to draw a pattern of some minimal length.
    255             if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
    256                 mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
    257             } else {
    258                 // Give just a little extra time if they hit one of the first few dots
    259                 mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS);
    260             }
    261         }
    262 
    263         public void onPatternDetected(List<LockPatternView.Cell> pattern) {
    264             if (mLockPatternUtils.checkPattern(pattern)) {
    265                 mCallback.reportSuccessfulUnlockAttempt();
    266                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
    267                 mCallback.dismiss(true);
    268             } else {
    269                 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
    270                     mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
    271                 }
    272                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
    273                 boolean registeredAttempt =
    274                         pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL;
    275                 if (registeredAttempt) {
    276                     mCallback.reportFailedUnlockAttempt();
    277                 }
    278                 int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts();
    279                 if (registeredAttempt &&
    280                         0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
    281                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
    282                     handleAttemptLockout(deadline);
    283                 } else {
    284                     mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true);
    285                     mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
    286                 }
    287             }
    288         }
    289     }
    290 
    291     private void maybeEnableFallback(Context context) {
    292         // Ask the account manager if we have an account that can be used as a
    293         // fallback in case the user forgets his pattern.
    294         AccountAnalyzer accountAnalyzer = new AccountAnalyzer(AccountManager.get(context));
    295         accountAnalyzer.start();
    296     }
    297 
    298     private class AccountAnalyzer implements AccountManagerCallback<Bundle> {
    299         private final AccountManager mAccountManager;
    300         private final Account[] mAccounts;
    301         private int mAccountIndex;
    302 
    303         private AccountAnalyzer(AccountManager accountManager) {
    304             mAccountManager = accountManager;
    305             mAccounts = accountManager.getAccountsByTypeAsUser("com.google",
    306                     new UserHandle(mLockPatternUtils.getCurrentUser()));
    307         }
    308 
    309         private void next() {
    310             // if we are ready to enable the fallback or if we depleted the list of accounts
    311             // then finish and get out
    312             if (mEnableFallback || mAccountIndex >= mAccounts.length) {
    313                 return;
    314             }
    315 
    316             // lookup the confirmCredentials intent for the current account
    317             mAccountManager.confirmCredentialsAsUser(mAccounts[mAccountIndex], null, null, this,
    318                     null, new UserHandle(mLockPatternUtils.getCurrentUser()));
    319         }
    320 
    321         public void start() {
    322             mEnableFallback = false;
    323             mAccountIndex = 0;
    324             next();
    325         }
    326 
    327         public void run(AccountManagerFuture<Bundle> future) {
    328             try {
    329                 Bundle result = future.getResult();
    330                 if (result.getParcelable(AccountManager.KEY_INTENT) != null) {
    331                     mEnableFallback = true;
    332                 }
    333             } catch (OperationCanceledException e) {
    334                 // just skip the account if we are unable to query it
    335             } catch (IOException e) {
    336                 // just skip the account if we are unable to query it
    337             } catch (AuthenticatorException e) {
    338                 // just skip the account if we are unable to query it
    339             } finally {
    340                 mAccountIndex++;
    341                 next();
    342             }
    343         }
    344     }
    345 
    346     private void handleAttemptLockout(long elapsedRealtimeDeadline) {
    347         mLockPatternView.clearPattern();
    348         mLockPatternView.setEnabled(false);
    349         final long elapsedRealtime = SystemClock.elapsedRealtime();
    350         if (mEnableFallback) {
    351             updateFooter(FooterMode.ForgotLockPattern);
    352         }
    353 
    354         mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
    355 
    356             @Override
    357             public void onTick(long millisUntilFinished) {
    358                 final int secondsRemaining = (int) (millisUntilFinished / 1000);
    359                 mSecurityMessageDisplay.setMessage(
    360                         R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
    361             }
    362 
    363             @Override
    364             public void onFinish() {
    365                 mLockPatternView.setEnabled(true);
    366                 displayDefaultSecurityMessage();
    367                 // TODO mUnlockIcon.setVisibility(View.VISIBLE);
    368                 if (mEnableFallback) {
    369                     updateFooter(FooterMode.ForgotLockPattern);
    370                 } else {
    371                     updateFooter(FooterMode.Normal);
    372                 }
    373             }
    374 
    375         }.start();
    376     }
    377 
    378     @Override
    379     public boolean needsInput() {
    380         return false;
    381     }
    382 
    383     @Override
    384     public void onPause() {
    385         if (mCountdownTimer != null) {
    386             mCountdownTimer.cancel();
    387             mCountdownTimer = null;
    388         }
    389     }
    390 
    391     @Override
    392     public void onResume(int reason) {
    393         reset();
    394     }
    395 
    396     @Override
    397     public KeyguardSecurityCallback getCallback() {
    398         return mCallback;
    399     }
    400 
    401     @Override
    402     public void showBouncer(int duration) {
    403         KeyguardSecurityViewHelper.
    404                 showBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
    405     }
    406 
    407     @Override
    408     public void hideBouncer(int duration) {
    409         KeyguardSecurityViewHelper.
    410                 hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
    411     }
    412 }
    413