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