Home | History | Annotate | Download | only in settings
      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.settings;
     18 
     19 import com.android.internal.widget.LockPatternUtils;
     20 import com.android.internal.widget.LockPatternView;
     21 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
     22 import com.android.internal.widget.LockPatternView.Cell;
     23 
     24 import android.app.Activity;
     25 import android.content.Intent;
     26 import android.os.CountDownTimer;
     27 import android.os.SystemClock;
     28 import android.os.Bundle;
     29 import android.widget.TextView;
     30 import android.view.Window;
     31 
     32 import java.util.List;
     33 
     34 /**
     35  * Launch this when you want the user to confirm their lock pattern.
     36  *
     37  * Sets an activity result of {@link Activity#RESULT_OK} when the user
     38  * successfully confirmed their pattern.
     39  */
     40 public class ConfirmLockPattern extends Activity {
     41 
     42     /**
     43      * Names of {@link CharSequence} fields within the originating {@link Intent}
     44      * that are used to configure the keyguard confirmation view's labeling.
     45      * The view will use the system-defined resource strings for any labels that
     46      * the caller does not supply.
     47      */
     48     public static final String HEADER_TEXT = "com.android.settings.ConfirmLockPattern.header";
     49     public static final String FOOTER_TEXT = "com.android.settings.ConfirmLockPattern.footer";
     50     public static final String HEADER_WRONG_TEXT = "com.android.settings.ConfirmLockPattern.header_wrong";
     51     public static final String FOOTER_WRONG_TEXT = "com.android.settings.ConfirmLockPattern.footer_wrong";
     52 
     53     // how long we wait to clear a wrong pattern
     54     private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
     55 
     56     private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts";
     57 
     58     private LockPatternView mLockPatternView;
     59     private LockPatternUtils mLockPatternUtils;
     60     private int mNumWrongConfirmAttempts;
     61     private CountDownTimer mCountdownTimer;
     62 
     63     private TextView mHeaderTextView;
     64     private TextView mFooterTextView;
     65 
     66     // caller-supplied text for various prompts
     67     private CharSequence mHeaderText;
     68     private CharSequence mFooterText;
     69     private CharSequence mHeaderWrongText;
     70     private CharSequence mFooterWrongText;
     71 
     72 
     73     private enum Stage {
     74         NeedToUnlock,
     75         NeedToUnlockWrong,
     76         LockedOut
     77     }
     78 
     79     @Override
     80     protected void onCreate(Bundle savedInstanceState) {
     81         super.onCreate(savedInstanceState);
     82 
     83         mLockPatternUtils = new LockPatternUtils(this);
     84 
     85         requestWindowFeature(Window.FEATURE_NO_TITLE);
     86         setContentView(R.layout.confirm_lock_pattern);
     87 
     88         mHeaderTextView = (TextView) findViewById(R.id.headerText);
     89         mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
     90         mFooterTextView = (TextView) findViewById(R.id.footerText);
     91 
     92         // make it so unhandled touch events within the unlock screen go to the
     93         // lock pattern view.
     94         final LinearLayoutWithDefaultTouchRecepient topLayout
     95                 = (LinearLayoutWithDefaultTouchRecepient) findViewById(
     96                 R.id.topLayout);
     97         topLayout.setDefaultTouchRecepient(mLockPatternView);
     98 
     99         Intent intent = getIntent();
    100         if (intent != null) {
    101             mHeaderText = intent.getCharSequenceExtra(HEADER_TEXT);
    102             mFooterText = intent.getCharSequenceExtra(FOOTER_TEXT);
    103             mHeaderWrongText = intent.getCharSequenceExtra(HEADER_WRONG_TEXT);
    104             mFooterWrongText = intent.getCharSequenceExtra(FOOTER_WRONG_TEXT);
    105         }
    106 
    107         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
    108         mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
    109         updateStage(Stage.NeedToUnlock);
    110 
    111         if (savedInstanceState != null) {
    112             mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS);
    113         } else {
    114             // on first launch, if no lock pattern is set, then finish with
    115             // success (don't want user to get stuck confirming something that
    116             // doesn't exist).
    117             if (!mLockPatternUtils.savedPatternExists()) {
    118                 setResult(RESULT_OK);
    119                 finish();
    120             }
    121         }
    122     }
    123 
    124     @Override
    125     protected void onSaveInstanceState(Bundle outState) {
    126         // deliberately not calling super since we are managing this in full
    127         outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts);
    128     }
    129 
    130     @Override
    131     protected void onPause() {
    132         super.onPause();
    133 
    134         if (mCountdownTimer != null) {
    135             mCountdownTimer.cancel();
    136         }
    137     }
    138 
    139     @Override
    140     protected void onResume() {
    141         super.onResume();
    142 
    143         // if the user is currently locked out, enforce it.
    144         long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
    145         if (deadline != 0) {
    146             handleAttemptLockout(deadline);
    147         }
    148     }
    149 
    150     private void updateStage(Stage stage) {
    151 
    152         switch (stage) {
    153             case NeedToUnlock:
    154                 if (mHeaderText != null) {
    155                     mHeaderTextView.setText(mHeaderText);
    156                 } else {
    157                     mHeaderTextView.setText(R.string.lockpattern_need_to_unlock);
    158                 }
    159                 if (mFooterText != null) {
    160                     mFooterTextView.setText(mFooterText);
    161                 } else {
    162                     mFooterTextView.setText(R.string.lockpattern_need_to_unlock_footer);
    163                 }
    164 
    165                 mLockPatternView.setEnabled(true);
    166                 mLockPatternView.enableInput();
    167                 break;
    168             case NeedToUnlockWrong:
    169                 if (mHeaderWrongText != null) {
    170                     mHeaderTextView.setText(mHeaderWrongText);
    171                 } else {
    172                     mHeaderTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
    173                 }
    174                 if (mFooterWrongText != null) {
    175                     mFooterTextView.setText(mFooterWrongText);
    176                 } else {
    177                     mFooterTextView.setText(R.string.lockpattern_need_to_unlock_wrong_footer);
    178                 }
    179 
    180                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
    181                 mLockPatternView.setEnabled(true);
    182                 mLockPatternView.enableInput();
    183                 break;
    184             case LockedOut:
    185                 mLockPatternView.clearPattern();
    186                 // enabled = false means: disable input, and have the
    187                 // appearance of being disabled.
    188                 mLockPatternView.setEnabled(false); // appearance of being disabled
    189                 break;
    190         }
    191     }
    192 
    193     private Runnable mClearPatternRunnable = new Runnable() {
    194         public void run() {
    195             mLockPatternView.clearPattern();
    196         }
    197     };
    198 
    199     // clear the wrong pattern unless they have started a new one
    200     // already
    201     private void postClearPatternRunnable() {
    202         mLockPatternView.removeCallbacks(mClearPatternRunnable);
    203         mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
    204     }
    205 
    206     /**
    207      * The pattern listener that responds according to a user confirming
    208      * an existing lock pattern.
    209      */
    210     private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener = new LockPatternView.OnPatternListener()  {
    211 
    212         public void onPatternStart() {
    213             mLockPatternView.removeCallbacks(mClearPatternRunnable);
    214         }
    215 
    216         public void onPatternCleared() {
    217             mLockPatternView.removeCallbacks(mClearPatternRunnable);
    218         }
    219 
    220         public void onPatternCellAdded(List<Cell> pattern) {
    221 
    222         }
    223 
    224         public void onPatternDetected(List<LockPatternView.Cell> pattern) {
    225             if (mLockPatternUtils.checkPattern(pattern)) {
    226                 setResult(RESULT_OK);
    227                 finish();
    228             } else {
    229                 if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL &&
    230                         ++mNumWrongConfirmAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
    231                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
    232                     handleAttemptLockout(deadline);
    233                 } else {
    234                     updateStage(Stage.NeedToUnlockWrong);
    235                     postClearPatternRunnable();
    236                 }
    237             }
    238         }
    239     };
    240 
    241 
    242     private void handleAttemptLockout(long elapsedRealtimeDeadline) {
    243         updateStage(Stage.LockedOut);
    244         long elapsedRealtime = SystemClock.elapsedRealtime();
    245         mCountdownTimer = new CountDownTimer(
    246                 elapsedRealtimeDeadline - elapsedRealtime,
    247                 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
    248 
    249             @Override
    250             public void onTick(long millisUntilFinished) {
    251                 mHeaderTextView.setText(R.string.lockpattern_too_many_failed_confirmation_attempts_header);
    252                 final int secondsCountdown = (int) (millisUntilFinished / 1000);
    253                 mFooterTextView.setText(getString(
    254                         R.string.lockpattern_too_many_failed_confirmation_attempts_footer,
    255                         secondsCountdown));
    256             }
    257 
    258             @Override
    259             public void onFinish() {
    260                 mNumWrongConfirmAttempts = 0;
    261                 updateStage(Stage.NeedToUnlock);
    262             }
    263         }.start();
    264     }
    265 }
    266