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 android.os.UserHandle;
20 import com.android.internal.logging.MetricsLogger;
21 import com.android.internal.widget.LockPatternUtils;
22 import com.android.internal.widget.LockPatternView;
23 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
24 import com.android.internal.widget.LockPatternChecker;
25 import com.android.internal.widget.LockPatternView.Cell;
26 import com.android.settingslib.animation.AppearAnimationCreator;
27 import com.android.settingslib.animation.AppearAnimationUtils;
28 import com.android.settingslib.animation.DisappearAnimationUtils;
30 import android.app.Activity;
31 import android.content.Intent;
32 import android.os.CountDownTimer;
33 import android.os.SystemClock;
34 import android.os.AsyncTask;
35 import android.os.Bundle;
36 import android.os.storage.StorageManager;
37 import android.view.animation.AnimationUtils;
38 import android.view.animation.Interpolator;
39 import android.widget.TextView;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 import android.view.ViewGroup;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.List;
49 * Launch this when you want the user to confirm their lock pattern.
51 * Sets an activity result of {@link Activity#RESULT_OK} when the user
52 * successfully confirmed their pattern.
54 public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
56 public static class InternalActivity extends ConfirmLockPattern {
66 public Intent getIntent() {
67 Intent modIntent = new Intent(super.getIntent());
68 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
73 protected boolean isValidFragment(String fragmentName) {
74 if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true;
78 public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
79 implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener {
81 // how long we wait to clear a wrong pattern
82 private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
84 private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
86 private LockPatternView mLockPatternView;
87 private LockPatternUtils mLockPatternUtils;
88 private AsyncTask<?, ?, ?> mPendingLockCheck;
89 private CredentialCheckResultTracker mCredentialCheckResultTracker;
90 private boolean mDisappearing = false;
91 private CountDownTimer mCountdownTimer;
93 private TextView mHeaderTextView;
94 private TextView mDetailsTextView;
95 private TextView mErrorTextView;
96 private View mLeftSpacerLandscape;
97 private View mRightSpacerLandscape;
99 // caller-supplied text for various prompts
100 private CharSequence mHeaderText;
101 private CharSequence mDetailsText;
103 private AppearAnimationUtils mAppearAnimationUtils;
104 private DisappearAnimationUtils mDisappearAnimationUtils;
106 private int mEffectiveUserId;
108 // required constructor for fragments
109 public ConfirmLockPatternFragment() {
114 public void onCreate(Bundle savedInstanceState) {
115 super.onCreate(savedInstanceState);
116 mLockPatternUtils = new LockPatternUtils(getActivity());
117 mEffectiveUserId = Utils.getEffectiveUserId(getActivity());
121 public View onCreateView(LayoutInflater inflater, ViewGroup container,
122 Bundle savedInstanceState) {
123 View view = inflater.inflate(R.layout.confirm_lock_pattern, null);
124 mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
125 mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
126 mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
127 mErrorTextView = (TextView) view.findViewById(R.id.errorText);
128 mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer);
129 mRightSpacerLandscape = view.findViewById(R.id.rightSpacer);
131 // make it so unhandled touch events within the unlock screen go to the
132 // lock pattern view.
133 final LinearLayoutWithDefaultTouchRecepient topLayout
134 = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
135 topLayout.setDefaultTouchRecepient(mLockPatternView);
137 Intent intent = getActivity().getIntent();
138 if (intent != null) {
139 mHeaderText = intent.getCharSequenceExtra(
140 ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
141 mDetailsText = intent.getCharSequenceExtra(
142 ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
145 mLockPatternView.setTactileFeedbackEnabled(
146 mLockPatternUtils.isTactileFeedbackEnabled());
147 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
149 mLockPatternView.setLockPatternSize(mLockPatternUtils.getLockPatternSize());
150 mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
151 updateStage(Stage.NeedToUnlock);
153 if (savedInstanceState == null) {
154 // on first launch, if no lock pattern is set, then finish with
155 // success (don't want user to get stuck confirming something that
157 if (!mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
158 getActivity().setResult(Activity.RESULT_OK);
159 getActivity().finish();
162 mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
163 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */,
164 1.3f /* delayScale */, AnimationUtils.loadInterpolator(
165 getContext(), android.R.interpolator.linear_out_slow_in));
166 mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
167 125, 4f /* translationScale */,
168 0.3f /* delayScale */, AnimationUtils.loadInterpolator(
169 getContext(), android.R.interpolator.fast_out_linear_in),
170 new AppearAnimationUtils.RowTranslationScaler() {
172 public float getRowTranslationScale(int row, int numRows) {
173 return (float)(numRows - row) / numRows;
176 setAccessibilityTitle(mHeaderTextView.getText());
178 mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
179 .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
180 if (mCredentialCheckResultTracker == null) {
181 mCredentialCheckResultTracker = new CredentialCheckResultTracker();
182 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
183 FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
190 public void onSaveInstanceState(Bundle outState) {
191 // deliberately not calling super since we are managing this in full
195 public void onPause() {
198 if (mCountdownTimer != null) {
199 mCountdownTimer.cancel();
201 mCredentialCheckResultTracker.setListener(null);
205 protected int getMetricsCategory() {
206 return MetricsLogger.CONFIRM_LOCK_PATTERN;
210 public void onResume() {
213 // if the user is currently locked out, enforce it.
214 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
216 mCredentialCheckResultTracker.clearResult();
217 handleAttemptLockout(deadline);
218 } else if (!mLockPatternView.isEnabled()) {
219 // The deadline has passed, but the timer was cancelled. Or the pending lock
220 // check was cancelled. Need to clean up.
221 updateStage(Stage.NeedToUnlock);
223 mCredentialCheckResultTracker.setListener(this);
227 public void prepareEnterAnimation() {
228 super.prepareEnterAnimation();
229 mHeaderTextView.setAlpha(0f);
230 mCancelButton.setAlpha(0f);
231 mLockPatternView.setAlpha(0f);
232 mDetailsTextView.setAlpha(0f);
233 mFingerprintIcon.setAlpha(0f);
236 private Object[][] getActiveViews() {
237 ArrayList<ArrayList<Object>> result = new ArrayList<>();
238 result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView)));
239 result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView)));
240 if (mCancelButton.getVisibility() == View.VISIBLE) {
241 result.add(new ArrayList<Object>(Collections.singletonList(mCancelButton)));
243 LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates();
244 for (int i = 0; i < cellStates.length; i++) {
245 ArrayList<Object> row = new ArrayList<>();
246 for (int j = 0; j < cellStates[i].length; j++) {
247 row.add(cellStates[i][j]);
251 if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
252 result.add(new ArrayList<Object>(Collections.singletonList(mFingerprintIcon)));
254 Object[][] resultArr = new Object[result.size()][cellStates[0].length];
255 for (int i = 0; i < result.size(); i++) {
256 ArrayList<Object> row = result.get(i);
257 for (int j = 0; j < row.size(); j++) {
258 resultArr[i][j] = row.get(j);
265 public void startEnterAnimation() {
266 super.startEnterAnimation();
267 mLockPatternView.setAlpha(1f);
268 mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
271 private void updateStage(Stage stage) {
274 if (mHeaderText != null) {
275 mHeaderTextView.setText(mHeaderText);
277 mHeaderTextView.setText(R.string.lockpassword_confirm_your_pattern_header);
279 if (mDetailsText != null) {
280 mDetailsTextView.setText(mDetailsText);
282 mDetailsTextView.setText(
283 R.string.lockpassword_confirm_your_pattern_generic);
285 mErrorTextView.setText("");
287 mLockPatternView.setEnabled(true);
288 mLockPatternView.enableInput();
289 mLockPatternView.clearPattern();
291 case NeedToUnlockWrong:
292 mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
294 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
295 mLockPatternView.setEnabled(true);
296 mLockPatternView.enableInput();
299 mLockPatternView.clearPattern();
300 // enabled = false means: disable input, and have the
301 // appearance of being disabled.
302 mLockPatternView.setEnabled(false); // appearance of being disabled
306 // Always announce the header for accessibility. This is a no-op
307 // when accessibility is disabled.
308 mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
311 private Runnable mClearPatternRunnable = new Runnable() {
313 mLockPatternView.clearPattern();
317 // clear the wrong pattern unless they have started a new one
319 private void postClearPatternRunnable() {
320 mLockPatternView.removeCallbacks(mClearPatternRunnable);
321 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
325 protected void authenticationSucceeded() {
326 mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
329 private void startDisappearAnimation(final Intent intent) {
333 mDisappearing = true;
335 if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
336 mLockPatternView.clearPattern();
337 mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
341 // Bail if there is no active activity.
342 if (getActivity() == null || getActivity().isFinishing()) {
346 getActivity().setResult(RESULT_OK, intent);
347 getActivity().finish();
348 getActivity().overridePendingTransition(
349 R.anim.confirm_credential_close_enter,
350 R.anim.confirm_credential_close_exit);
354 getActivity().setResult(RESULT_OK, intent);
355 getActivity().finish();
360 public void onFingerprintIconVisibilityChanged(boolean visible) {
361 if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) {
363 // In landscape, adjust spacing depending on fingerprint icon visibility.
364 mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
365 mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
370 * The pattern listener that responds according to a user confirming
371 * an existing lock pattern.
373 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
374 = new LockPatternView.OnPatternListener() {
376 public void onPatternStart() {
377 mLockPatternView.removeCallbacks(mClearPatternRunnable);
380 public void onPatternCleared() {
381 mLockPatternView.removeCallbacks(mClearPatternRunnable);
384 public void onPatternCellAdded(List<Cell> pattern) {
388 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
389 if (mPendingLockCheck != null || mDisappearing) {
393 mLockPatternView.setEnabled(false);
395 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
396 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
397 Intent intent = new Intent();
398 if (verifyChallenge) {
399 if (isInternalActivity()) {
400 startVerifyPattern(pattern, intent);
404 startCheckPattern(pattern, intent);
408 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
411 private boolean isInternalActivity() {
412 return getActivity() instanceof ConfirmLockPattern.InternalActivity;
415 private void startVerifyPattern(final List<LockPatternView.Cell> pattern,
416 final Intent intent) {
417 final int localEffectiveUserId = mEffectiveUserId;
418 long challenge = getActivity().getIntent().getLongExtra(
419 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
420 mPendingLockCheck = LockPatternChecker.verifyPattern(
424 localEffectiveUserId,
425 new LockPatternChecker.OnVerifyCallback() {
427 public void onVerified(byte[] token, int timeoutMs) {
428 mPendingLockCheck = null;
429 boolean matched = false;
433 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
436 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
437 localEffectiveUserId);
442 private void startCheckPattern(final List<LockPatternView.Cell> pattern,
443 final Intent intent) {
444 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
445 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
449 final int localEffectiveUserId = mEffectiveUserId;
450 mPendingLockCheck = LockPatternChecker.checkPattern(
453 localEffectiveUserId,
454 new LockPatternChecker.OnCheckCallback() {
456 public void onChecked(boolean matched, int timeoutMs) {
457 mPendingLockCheck = null;
458 if (matched && isInternalActivity()) {
459 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
460 StorageManager.CRYPT_TYPE_PATTERN);
461 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
462 mLockPatternUtils.patternToString(pattern));
464 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
465 localEffectiveUserId);
471 private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
472 int effectiveUserId) {
473 mLockPatternView.setEnabled(true);
475 startDisappearAnimation(intent);
478 long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
479 effectiveUserId, timeoutMs);
480 handleAttemptLockout(deadline);
482 updateStage(Stage.NeedToUnlockWrong);
483 postClearPatternRunnable();
489 public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
490 int effectiveUserId) {
491 onPatternChecked(matched, intent, timeoutMs, effectiveUserId);
494 private void handleAttemptLockout(long elapsedRealtimeDeadline) {
495 updateStage(Stage.LockedOut);
496 long elapsedRealtime = SystemClock.elapsedRealtime();
497 mCountdownTimer = new CountDownTimer(
498 elapsedRealtimeDeadline - elapsedRealtime,
499 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
502 public void onTick(long millisUntilFinished) {
503 final int secondsCountdown = (int) (millisUntilFinished / 1000);
504 mErrorTextView.setText(getString(
505 R.string.lockpattern_too_many_failed_confirmation_attempts,
510 public void onFinish() {
511 updateStage(Stage.NeedToUnlock);
517 public void createAnimation(Object obj, long delay,
518 long duration, float translationY, final boolean appearing,
519 Interpolator interpolator,
520 final Runnable finishListener) {
521 if (obj instanceof LockPatternView.CellState) {
522 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
523 mLockPatternView.startCellStateAnimation(animatedCell,
524 1f, appearing ? 1f : 0f, /* alpha */
525 appearing ? translationY : 0f, /* startTranslation */
526 appearing ? 0f : translationY, /* endTranslation */
527 appearing ? 0f : 1f, 1f /* scale */,
528 delay, duration, interpolator, finishListener);
530 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
531 appearing, interpolator, finishListener);