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