• 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.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
onCreate(Bundle savedInstanceState)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
getIntent()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
isValidFragment(String fragmentName)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
ConfirmLockPatternFragment()107         public ConfirmLockPatternFragment() {
108 
109         }
110 
111         @Override
onCreate(Bundle savedInstanceState)112         public void onCreate(Bundle savedInstanceState) {
113             super.onCreate(savedInstanceState);
114             mLockPatternUtils = new LockPatternUtils(getActivity());
115         }
116 
117         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onSaveInstanceState(Bundle outState)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
onPause()164         public void onPause() {
165             super.onPause();
166 
167             if (mCountdownTimer != null) {
168                 mCountdownTimer.cancel();
169             }
170         }
171 
172         @Override
onResume()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 
updateStage(Stage stage)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
postClearPatternRunnable()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 
handleAttemptLockout(long elapsedRealtimeDeadline)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