2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings;
19 import com.android.internal.logging.MetricsLogger;
20 import com.android.internal.widget.LockPatternUtils;
21 import com.android.internal.widget.LockPatternView;
22 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
23 import com.android.internal.widget.LockPatternChecker;
24 import com.android.internal.widget.LockPatternView.Cell;
26 import android.annotation.Nullable;
27 import android.app.Activity;
28 import android.content.Intent;
29 import android.os.CountDownTimer;
30 import android.os.SystemClock;
31 import android.os.AsyncTask;
32 import android.os.Bundle;
33 import android.os.UserHandle;
34 import android.os.storage.StorageManager;
35 import android.view.MenuItem;
36 import android.widget.TextView;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.view.ViewGroup;
41 import java.util.List;
44 * Launch this when you want the user to confirm their lock pattern.
46 * Sets an activity result of {@link Activity#RESULT_OK} when the user
47 * successfully confirmed their pattern.
49 public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
51 public static class InternalActivity extends ConfirmLockPattern {
61 public Intent getIntent() {
62 Intent modIntent = new Intent(super.getIntent());
63 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
68 protected boolean isValidFragment(String fragmentName) {
69 if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true;
73 public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment {
75 // how long we wait to clear a wrong pattern
76 private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
78 private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts";
80 private LockPatternView mLockPatternView;
81 private LockPatternUtils mLockPatternUtils;
82 private AsyncTask<?, ?, ?> mPendingLockCheck;
83 private int mNumWrongConfirmAttempts;
84 private CountDownTimer mCountdownTimer;
86 private TextView mHeaderTextView;
87 private TextView mDetailsTextView;
88 private TextView mErrorTextView;
89 private View mLeftSpacerLandscape;
90 private View mRightSpacerLandscape;
92 // caller-supplied text for various prompts
93 private CharSequence mHeaderText;
94 private CharSequence mDetailsText;
96 // required constructor for fragments
97 public ConfirmLockPatternFragment() {
102 public void onCreate(Bundle savedInstanceState) {
103 super.onCreate(savedInstanceState);
104 mLockPatternUtils = new LockPatternUtils(getActivity());
108 public View onCreateView(LayoutInflater inflater, ViewGroup container,
109 Bundle savedInstanceState) {
110 View view = inflater.inflate(R.layout.confirm_lock_pattern, null);
111 mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
112 mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
113 mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
114 mErrorTextView = (TextView) view.findViewById(R.id.errorText);
115 mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer);
116 mRightSpacerLandscape = view.findViewById(R.id.rightSpacer);
118 // make it so unhandled touch events within the unlock screen go to the
119 // lock pattern view.
120 final LinearLayoutWithDefaultTouchRecepient topLayout
121 = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
122 topLayout.setDefaultTouchRecepient(mLockPatternView);
124 Intent intent = getActivity().getIntent();
125 if (intent != null) {
126 mHeaderText = intent.getCharSequenceExtra(
127 ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
128 mDetailsText = intent.getCharSequenceExtra(
129 ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
132 mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
133 mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
134 updateStage(Stage.NeedToUnlock);
136 if (savedInstanceState != null) {
137 mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS);
139 // on first launch, if no lock pattern is set, then finish with
140 // success (don't want user to get stuck confirming something that
142 if (!mLockPatternUtils.isLockPatternEnabled(UserHandle.myUserId())) {
143 getActivity().setResult(Activity.RESULT_OK);
144 getActivity().finish();
151 public void onSaveInstanceState(Bundle outState) {
152 // deliberately not calling super since we are managing this in full
153 outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts);
157 public void onPause() {
160 if (mCountdownTimer != null) {
161 mCountdownTimer.cancel();
163 if (mPendingLockCheck != null) {
164 mPendingLockCheck.cancel(false);
165 mPendingLockCheck = null;
170 protected int getMetricsCategory() {
171 return MetricsLogger.CONFIRM_LOCK_PATTERN;
175 public void onResume() {
178 // if the user is currently locked out, enforce it.
179 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(UserHandle.myUserId());
181 handleAttemptLockout(deadline);
182 } else if (!mLockPatternView.isEnabled()) {
183 // The deadline has passed, but the timer was cancelled. Or the pending lock
184 // check was cancelled. Need to clean up.
185 mNumWrongConfirmAttempts = 0;
186 updateStage(Stage.NeedToUnlock);
190 private void updateStage(Stage stage) {
193 if (mHeaderText != null) {
194 mHeaderTextView.setText(mHeaderText);
196 mHeaderTextView.setText(R.string.lockpassword_confirm_your_pattern_header);
198 if (mDetailsText != null) {
199 mDetailsTextView.setText(mDetailsText);
201 mDetailsTextView.setText(
202 R.string.lockpassword_confirm_your_pattern_generic);
204 mErrorTextView.setText("");
206 mLockPatternView.setEnabled(true);
207 mLockPatternView.enableInput();
208 mLockPatternView.clearPattern();
210 case NeedToUnlockWrong:
211 mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
213 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
214 mLockPatternView.setEnabled(true);
215 mLockPatternView.enableInput();
218 mLockPatternView.clearPattern();
219 // enabled = false means: disable input, and have the
220 // appearance of being disabled.
221 mLockPatternView.setEnabled(false); // appearance of being disabled
225 // Always announce the header for accessibility. This is a no-op
226 // when accessibility is disabled.
227 mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
230 private Runnable mClearPatternRunnable = new Runnable() {
232 mLockPatternView.clearPattern();
236 // clear the wrong pattern unless they have started a new one
238 private void postClearPatternRunnable() {
239 mLockPatternView.removeCallbacks(mClearPatternRunnable);
240 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
244 protected void authenticationSucceeded() {
245 Intent intent = new Intent();
246 getActivity().setResult(Activity.RESULT_OK, intent);
247 getActivity().finish();
251 public void onFingerprintIconVisibilityChanged(boolean visible) {
252 if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) {
254 // In landscape, adjust spacing depending on fingerprint icon visibility.
255 mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
256 mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
261 * The pattern listener that responds according to a user confirming
262 * an existing lock pattern.
264 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
265 = new LockPatternView.OnPatternListener() {
267 public void onPatternStart() {
268 mLockPatternView.removeCallbacks(mClearPatternRunnable);
271 public void onPatternCleared() {
272 mLockPatternView.removeCallbacks(mClearPatternRunnable);
275 public void onPatternCellAdded(List<Cell> pattern) {
279 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
280 mLockPatternView.setEnabled(false);
281 if (mPendingLockCheck != null) {
282 mPendingLockCheck.cancel(false);
285 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
286 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
287 Intent intent = new Intent();
288 if (verifyChallenge) {
289 if (isInternalActivity()) {
290 startVerifyPattern(pattern, intent);
294 startCheckPattern(pattern, intent);
298 onPatternChecked(pattern, false, intent, 0);
301 private boolean isInternalActivity() {
302 return getActivity() instanceof ConfirmLockPattern.InternalActivity;
305 private void startVerifyPattern(final List<LockPatternView.Cell> pattern,
306 final Intent intent) {
307 long challenge = getActivity().getIntent().getLongExtra(
308 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
309 mPendingLockCheck = LockPatternChecker.verifyPattern(
313 UserHandle.myUserId(),
314 new LockPatternChecker.OnVerifyCallback() {
316 public void onVerified(byte[] token, int timeoutMs) {
317 mPendingLockCheck = null;
318 boolean matched = false;
322 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
325 onPatternChecked(pattern, matched, intent, timeoutMs);
330 private void startCheckPattern(final List<LockPatternView.Cell> pattern,
331 final Intent intent) {
332 if (pattern.size() <= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
333 onPatternChecked(pattern, false, intent, 0);
337 mPendingLockCheck = LockPatternChecker.checkPattern(
340 UserHandle.myUserId(),
341 new LockPatternChecker.OnCheckCallback() {
343 public void onChecked(boolean matched, int timeoutMs) {
344 mPendingLockCheck = null;
345 if (matched && isInternalActivity()) {
346 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
347 StorageManager.CRYPT_TYPE_PATTERN);
348 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
349 LockPatternUtils.patternToString(pattern));
351 onPatternChecked(pattern, matched, intent, timeoutMs);
356 private void onPatternChecked(List<LockPatternView.Cell> pattern,
357 boolean matched, Intent intent, int timeoutMs) {
358 mLockPatternView.setEnabled(true);
360 getActivity().setResult(Activity.RESULT_OK, intent);
361 getActivity().finish();
364 long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
365 UserHandle.myUserId(), timeoutMs);
366 handleAttemptLockout(deadline);
368 updateStage(Stage.NeedToUnlockWrong);
369 postClearPatternRunnable();
376 private void handleAttemptLockout(long elapsedRealtimeDeadline) {
377 updateStage(Stage.LockedOut);
378 long elapsedRealtime = SystemClock.elapsedRealtime();
379 mCountdownTimer = new CountDownTimer(
380 elapsedRealtimeDeadline - elapsedRealtime,
381 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
384 public void onTick(long millisUntilFinished) {
385 final int secondsCountdown = (int) (millisUntilFinished / 1000);
386 mErrorTextView.setText(getString(
387 R.string.lockpattern_too_many_failed_confirmation_attempts,
392 public void onFinish() {
393 mNumWrongConfirmAttempts = 0;
394 updateStage(Stage.NeedToUnlock);