Home | History | Annotate | Download | only in keyguard_obsolete
      1 /*
      2  * Copyright (C) 2008 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 
     17 package com.android.internal.policy.impl.keyguard_obsolete;
     18 
     19 import android.content.Context;
     20 import android.content.res.Configuration;
     21 import android.os.CountDownTimer;
     22 import android.os.SystemClock;
     23 import android.security.KeyStore;
     24 import android.view.LayoutInflater;
     25 import android.view.View;
     26 import android.view.MotionEvent;
     27 import android.widget.Button;
     28 import android.util.Log;
     29 import com.android.internal.R;
     30 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
     31 import com.android.internal.widget.LockPatternUtils;
     32 import com.android.internal.widget.LockPatternView;
     33 import com.android.internal.widget.LockPatternView.Cell;
     34 
     35 import java.util.List;
     36 
     37 /**
     38  * This is the screen that shows the 9 circle unlock widget and instructs
     39  * the user how to unlock their device, or make an emergency call.
     40  */
     41 class PatternUnlockScreen extends LinearLayoutWithDefaultTouchRecepient
     42         implements KeyguardScreen {
     43 
     44     private static final boolean DEBUG = false;
     45     private static final String TAG = "UnlockScreen";
     46 
     47     // how long before we clear the wrong pattern
     48     private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
     49 
     50     // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
     51     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
     52 
     53     // how long we stay awake after the user hits the first dot.
     54     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000;
     55 
     56     // how many cells the user has to cross before we poke the wakelock
     57     private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
     58 
     59     private int mFailedPatternAttemptsSinceLastTimeout = 0;
     60     private int mTotalFailedPatternAttempts = 0;
     61     private CountDownTimer mCountdownTimer = null;
     62 
     63     private LockPatternUtils mLockPatternUtils;
     64     private KeyguardUpdateMonitor mUpdateMonitor;
     65     private KeyguardScreenCallback mCallback;
     66 
     67     /**
     68      * whether there is a fallback option available when the pattern is forgotten.
     69      */
     70     private boolean mEnableFallback;
     71 
     72     private KeyguardStatusViewManager mKeyguardStatusViewManager;
     73     private LockPatternView mLockPatternView;
     74 
     75     /**
     76      * Keeps track of the last time we poked the wake lock during dispatching
     77      * of the touch event, initalized to something gauranteed to make us
     78      * poke it when the user starts drawing the pattern.
     79      * @see #dispatchTouchEvent(android.view.MotionEvent)
     80      */
     81     private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
     82 
     83     /**
     84      * Useful for clearing out the wrong pattern after a delay
     85      */
     86     private Runnable mCancelPatternRunnable = new Runnable() {
     87         public void run() {
     88             mLockPatternView.clearPattern();
     89         }
     90     };
     91 
     92     private final OnClickListener mForgotPatternClick = new OnClickListener() {
     93         public void onClick(View v) {
     94             mCallback.forgotPattern(true);
     95         }
     96     };
     97 
     98     private Button mForgotPatternButton;
     99     private int mCreationOrientation;
    100 
    101     enum FooterMode {
    102         Normal,
    103         ForgotLockPattern,
    104         VerifyUnlocked
    105     }
    106 
    107     private void hideForgotPatternButton() {
    108         mForgotPatternButton.setVisibility(View.GONE);
    109     }
    110 
    111     private void showForgotPatternButton() {
    112         mForgotPatternButton.setVisibility(View.VISIBLE);
    113     }
    114 
    115     private void updateFooter(FooterMode mode) {
    116         switch (mode) {
    117             case Normal:
    118                 if (DEBUG) Log.d(TAG, "mode normal");
    119                 hideForgotPatternButton();
    120                 break;
    121             case ForgotLockPattern:
    122                 if (DEBUG) Log.d(TAG, "mode ForgotLockPattern");
    123                 showForgotPatternButton();
    124                 break;
    125             case VerifyUnlocked:
    126                 if (DEBUG) Log.d(TAG, "mode VerifyUnlocked");
    127                 hideForgotPatternButton();
    128         }
    129     }
    130 
    131     /**
    132      * @param context The context.
    133      * @param configuration
    134      * @param lockPatternUtils Used to lookup lock pattern settings.
    135      * @param updateMonitor Used to lookup state affecting keyguard.
    136      * @param callback Used to notify the manager when we're done, etc.
    137      * @param totalFailedAttempts The current number of failed attempts.
    138      * @param enableFallback True if a backup unlock option is available when the user has forgotten
    139      *        their pattern (e.g they have a google account so we can show them the account based
    140      *        backup option).
    141      */
    142     PatternUnlockScreen(Context context,
    143                  Configuration configuration, LockPatternUtils lockPatternUtils,
    144                  KeyguardUpdateMonitor updateMonitor,
    145                  KeyguardScreenCallback callback,
    146                  int totalFailedAttempts) {
    147         super(context);
    148         mLockPatternUtils = lockPatternUtils;
    149         mUpdateMonitor = updateMonitor;
    150         mCallback = callback;
    151         mTotalFailedPatternAttempts = totalFailedAttempts;
    152         mFailedPatternAttemptsSinceLastTimeout =
    153             totalFailedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
    154 
    155         if (DEBUG) Log.d(TAG,
    156             "UnlockScreen() ctor: totalFailedAttempts="
    157                  + totalFailedAttempts + ", mFailedPat...="
    158                  + mFailedPatternAttemptsSinceLastTimeout
    159                  );
    160 
    161         mCreationOrientation = configuration.orientation;
    162 
    163         LayoutInflater inflater = LayoutInflater.from(context);
    164 
    165         if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) {
    166             Log.d(TAG, "portrait mode");
    167             inflater.inflate(R.layout.keyguard_screen_unlock_portrait, this, true);
    168         } else {
    169             Log.d(TAG, "landscape mode");
    170             inflater.inflate(R.layout.keyguard_screen_unlock_landscape, this, true);
    171         }
    172 
    173         mKeyguardStatusViewManager = new KeyguardStatusViewManager(this, mUpdateMonitor,
    174                 mLockPatternUtils, mCallback, true);
    175 
    176         mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
    177 
    178         mForgotPatternButton = (Button) findViewById(R.id.forgotPatternButton);
    179         mForgotPatternButton.setText(R.string.lockscreen_forgot_pattern_button_text);
    180         mForgotPatternButton.setOnClickListener(mForgotPatternClick);
    181 
    182         // make it so unhandled touch events within the unlock screen go to the
    183         // lock pattern view.
    184         setDefaultTouchRecepient(mLockPatternView);
    185 
    186         mLockPatternView.setSaveEnabled(false);
    187         mLockPatternView.setFocusable(false);
    188         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
    189 
    190         // stealth mode will be the same for the life of this screen
    191         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled());
    192 
    193         // vibrate mode will be the same for the life of this screen
    194         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
    195 
    196         // assume normal footer mode for now
    197         updateFooter(FooterMode.Normal);
    198 
    199         setFocusableInTouchMode(true);
    200     }
    201 
    202     public void setEnableFallback(boolean state) {
    203         if (DEBUG) Log.d(TAG, "setEnableFallback(" + state + ")");
    204         mEnableFallback = state;
    205     }
    206 
    207     @Override
    208     public boolean dispatchTouchEvent(MotionEvent ev) {
    209         // as long as the user is entering a pattern (i.e sending a touch
    210         // event that was handled by this screen), keep poking the
    211         // wake lock so that the screen will stay on.
    212         final boolean result = super.dispatchTouchEvent(ev);
    213         if (result &&
    214                 ((SystemClock.elapsedRealtime() - mLastPokeTime)
    215                         >  (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
    216             mLastPokeTime = SystemClock.elapsedRealtime();
    217         }
    218         return result;
    219     }
    220 
    221     @Override
    222     protected void onAttachedToWindow() {
    223         super.onAttachedToWindow();
    224         if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
    225             Log.v(TAG, "***** PATTERN ATTACHED TO WINDOW");
    226             Log.v(TAG, "Cur orient=" + mCreationOrientation
    227                     + ", new config=" + getResources().getConfiguration());
    228         }
    229         if (getResources().getConfiguration().orientation != mCreationOrientation) {
    230             mCallback.recreateMe(getResources().getConfiguration());
    231         }
    232     }
    233 
    234 
    235     /** {@inheritDoc} */
    236     @Override
    237     protected void onConfigurationChanged(Configuration newConfig) {
    238         super.onConfigurationChanged(newConfig);
    239         if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
    240             Log.v(TAG, "***** PATTERN CONFIGURATION CHANGED");
    241             Log.v(TAG, "Cur orient=" + mCreationOrientation
    242                     + ", new config=" + getResources().getConfiguration());
    243         }
    244         if (newConfig.orientation != mCreationOrientation) {
    245             mCallback.recreateMe(newConfig);
    246         }
    247     }
    248 
    249     /** {@inheritDoc} */
    250     public void onKeyboardChange(boolean isKeyboardOpen) {}
    251 
    252     /** {@inheritDoc} */
    253     public boolean needsInput() {
    254         return false;
    255     }
    256 
    257     /** {@inheritDoc} */
    258     public void onPause() {
    259         if (mCountdownTimer != null) {
    260             mCountdownTimer.cancel();
    261             mCountdownTimer = null;
    262         }
    263         mKeyguardStatusViewManager.onPause();
    264     }
    265 
    266     /** {@inheritDoc} */
    267     public void onResume() {
    268         // reset status
    269         mKeyguardStatusViewManager.onResume();
    270 
    271         // reset lock pattern
    272         mLockPatternView.enableInput();
    273         mLockPatternView.setEnabled(true);
    274         mLockPatternView.clearPattern();
    275 
    276         // show "forgot pattern?" button if we have an alternate authentication method
    277         if (mCallback.doesFallbackUnlockScreenExist()) {
    278             showForgotPatternButton();
    279         } else {
    280             hideForgotPatternButton();
    281         }
    282 
    283         // if the user is currently locked out, enforce it.
    284         long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
    285         if (deadline != 0) {
    286             handleAttemptLockout(deadline);
    287         }
    288 
    289         // the footer depends on how many total attempts the user has failed
    290         if (mCallback.isVerifyUnlockOnly()) {
    291             updateFooter(FooterMode.VerifyUnlocked);
    292         } else if (mEnableFallback &&
    293                 (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
    294             updateFooter(FooterMode.ForgotLockPattern);
    295         } else {
    296             updateFooter(FooterMode.Normal);
    297         }
    298 
    299     }
    300 
    301     /** {@inheritDoc} */
    302     public void cleanUp() {
    303         if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
    304         mUpdateMonitor.removeCallback(this);
    305         mLockPatternUtils = null;
    306         mUpdateMonitor = null;
    307         mCallback = null;
    308         mLockPatternView.setOnPatternListener(null);
    309     }
    310 
    311     @Override
    312     public void onWindowFocusChanged(boolean hasWindowFocus) {
    313         super.onWindowFocusChanged(hasWindowFocus);
    314         if (hasWindowFocus) {
    315             // when timeout dialog closes we want to update our state
    316             onResume();
    317         }
    318     }
    319 
    320     private class UnlockPatternListener
    321             implements LockPatternView.OnPatternListener {
    322 
    323         public void onPatternStart() {
    324             mLockPatternView.removeCallbacks(mCancelPatternRunnable);
    325         }
    326 
    327         public void onPatternCleared() {
    328         }
    329 
    330         public void onPatternCellAdded(List<Cell> pattern) {
    331             // To guard against accidental poking of the wakelock, look for
    332             // the user actually trying to draw a pattern of some minimal length.
    333             if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
    334                 mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
    335             } else {
    336                 // Give just a little extra time if they hit one of the first few dots
    337                 mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS);
    338             }
    339         }
    340 
    341         public void onPatternDetected(List<LockPatternView.Cell> pattern) {
    342             if (mLockPatternUtils.checkPattern(pattern)) {
    343                 mLockPatternView
    344                         .setDisplayMode(LockPatternView.DisplayMode.Correct);
    345                 mKeyguardStatusViewManager.setInstructionText("");
    346                 mKeyguardStatusViewManager.updateStatusLines(true);
    347                 mCallback.keyguardDone(true);
    348                 mCallback.reportSuccessfulUnlockAttempt();
    349                 KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern));
    350             } else {
    351                 boolean reportFailedAttempt = false;
    352                 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
    353                     mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
    354                 }
    355                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
    356                 if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
    357                     mTotalFailedPatternAttempts++;
    358                     mFailedPatternAttemptsSinceLastTimeout++;
    359                     reportFailedAttempt = true;
    360                 }
    361                 if (mFailedPatternAttemptsSinceLastTimeout
    362                         >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
    363                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
    364                     handleAttemptLockout(deadline);
    365                 } else {
    366                     // TODO mUnlockIcon.setVisibility(View.VISIBLE);
    367                     mKeyguardStatusViewManager.setInstructionText(
    368                             getContext().getString(R.string.lockscreen_pattern_wrong));
    369                     mKeyguardStatusViewManager.updateStatusLines(true);
    370                     mLockPatternView.postDelayed(
    371                             mCancelPatternRunnable,
    372                             PATTERN_CLEAR_TIMEOUT_MS);
    373                 }
    374 
    375                 // Because the following can result in cleanUp() being called on this screen,
    376                 // member variables reset in cleanUp() shouldn't be accessed after this call.
    377                 if (reportFailedAttempt) {
    378                     mCallback.reportFailedUnlockAttempt();
    379                 }
    380             }
    381         }
    382     }
    383 
    384     private void handleAttemptLockout(long elapsedRealtimeDeadline) {
    385         mLockPatternView.clearPattern();
    386         mLockPatternView.setEnabled(false);
    387         long elapsedRealtime = SystemClock.elapsedRealtime();
    388         mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
    389 
    390             @Override
    391             public void onTick(long millisUntilFinished) {
    392                 int secondsRemaining = (int) (millisUntilFinished / 1000);
    393                 mKeyguardStatusViewManager.setInstructionText(getContext().getString(
    394                         R.string.lockscreen_too_many_failed_attempts_countdown,
    395                         secondsRemaining));
    396                 mKeyguardStatusViewManager.updateStatusLines(true);
    397             }
    398 
    399             @Override
    400             public void onFinish() {
    401                 mLockPatternView.setEnabled(true);
    402                 mKeyguardStatusViewManager.setInstructionText(getContext().getString(
    403                         R.string.lockscreen_pattern_instructions));
    404                 mKeyguardStatusViewManager.updateStatusLines(true);
    405                 // TODO mUnlockIcon.setVisibility(View.VISIBLE);
    406                 mFailedPatternAttemptsSinceLastTimeout = 0;
    407                 if (mEnableFallback) {
    408                     updateFooter(FooterMode.ForgotLockPattern);
    409                 } else {
    410                     updateFooter(FooterMode.Normal);
    411                 }
    412             }
    413         }.start();
    414     }
    415 
    416 }
    417