• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate(Bundle savedInstanceState)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
onSaveInstanceState(Bundle outState)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
onPause()131     protected void onPause() {
132         super.onPause();
133 
134         if (mCountdownTimer != null) {
135             mCountdownTimer.cancel();
136         }
137     }
138 
139     @Override
onResume()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 
updateStage(Stage stage)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
postClearPatternRunnable()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 
handleAttemptLockout(long elapsedRealtimeDeadline)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