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.app.Activity;
20 import android.content.Intent;
21 import android.os.AsyncTask;
22 import android.os.Bundle;
23 import android.os.CountDownTimer;
24 import android.os.SystemClock;
25 import android.os.UserManager;
26 import android.os.storage.StorageManager;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.animation.AnimationUtils;
31 import android.view.animation.Interpolator;
32 import android.widget.TextView;
34 import com.android.internal.logging.MetricsProto.MetricsEvent;
35 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
36 import com.android.internal.widget.LockPatternChecker;
37 import com.android.internal.widget.LockPatternUtils;
38 import com.android.internal.widget.LockPatternView;
39 import com.android.internal.widget.LockPatternView.Cell;
40 import com.android.settingslib.animation.AppearAnimationCreator;
41 import com.android.settingslib.animation.AppearAnimationUtils;
42 import com.android.settingslib.animation.DisappearAnimationUtils;
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 AsyncTask<?, ?, ?> mPendingLockCheck;
88 private CredentialCheckResultTracker mCredentialCheckResultTracker;
89 private boolean mDisappearing = false;
90 private CountDownTimer mCountdownTimer;
92 private TextView mHeaderTextView;
93 private TextView mDetailsTextView;
94 private View mLeftSpacerLandscape;
95 private View mRightSpacerLandscape;
97 // caller-supplied text for various prompts
98 private CharSequence mHeaderText;
99 private CharSequence mDetailsText;
101 private AppearAnimationUtils mAppearAnimationUtils;
102 private DisappearAnimationUtils mDisappearAnimationUtils;
104 // required constructor for fragments
105 public ConfirmLockPatternFragment() {
110 public void onCreate(Bundle savedInstanceState) {
111 super.onCreate(savedInstanceState);
115 public View onCreateView(LayoutInflater inflater, ViewGroup container,
116 Bundle savedInstanceState) {
117 View view = inflater.inflate(R.layout.confirm_lock_pattern, null);
118 mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
119 mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
120 mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
121 mErrorTextView = (TextView) view.findViewById(R.id.errorText);
122 mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer);
123 mRightSpacerLandscape = view.findViewById(R.id.rightSpacer);
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);
131 Intent intent = getActivity().getIntent();
132 if (intent != null) {
133 mHeaderText = intent.getCharSequenceExtra(
134 ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
135 mDetailsText = intent.getCharSequenceExtra(
136 ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
139 mLockPatternView.setTactileFeedbackEnabled(
140 mLockPatternUtils.isTactileFeedbackEnabled());
141 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
143 mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
144 updateStage(Stage.NeedToUnlock);
146 if (savedInstanceState == null) {
147 // on first launch, if no lock pattern is set, then finish with
148 // success (don't want user to get stuck confirming something that
150 if (!mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
151 getActivity().setResult(Activity.RESULT_OK);
152 getActivity().finish();
155 mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
156 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */,
157 1.3f /* delayScale */, AnimationUtils.loadInterpolator(
158 getContext(), android.R.interpolator.linear_out_slow_in));
159 mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
160 125, 4f /* translationScale */,
161 0.3f /* delayScale */, AnimationUtils.loadInterpolator(
162 getContext(), android.R.interpolator.fast_out_linear_in),
163 new AppearAnimationUtils.RowTranslationScaler() {
165 public float getRowTranslationScale(int row, int numRows) {
166 return (float)(numRows - row) / numRows;
169 setAccessibilityTitle(mHeaderTextView.getText());
171 mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
172 .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
173 if (mCredentialCheckResultTracker == null) {
174 mCredentialCheckResultTracker = new CredentialCheckResultTracker();
175 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
176 FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
183 public void onSaveInstanceState(Bundle outState) {
184 // deliberately not calling super since we are managing this in full
188 public void onPause() {
191 if (mCountdownTimer != null) {
192 mCountdownTimer.cancel();
194 mCredentialCheckResultTracker.setListener(null);
198 protected int getMetricsCategory() {
199 return MetricsEvent.CONFIRM_LOCK_PATTERN;
203 public void onResume() {
206 // if the user is currently locked out, enforce it.
207 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
209 mCredentialCheckResultTracker.clearResult();
210 handleAttemptLockout(deadline);
211 } else if (!mLockPatternView.isEnabled()) {
212 // The deadline has passed, but the timer was cancelled. Or the pending lock
213 // check was cancelled. Need to clean up.
214 updateStage(Stage.NeedToUnlock);
216 mCredentialCheckResultTracker.setListener(this);
220 protected void onShowError() {
224 public void prepareEnterAnimation() {
225 super.prepareEnterAnimation();
226 mHeaderTextView.setAlpha(0f);
227 mCancelButton.setAlpha(0f);
228 mLockPatternView.setAlpha(0f);
229 mDetailsTextView.setAlpha(0f);
230 mFingerprintIcon.setAlpha(0f);
233 private Object[][] getActiveViews() {
234 ArrayList<ArrayList<Object>> result = new ArrayList<>();
235 result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView)));
236 result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView)));
237 if (mCancelButton.getVisibility() == View.VISIBLE) {
238 result.add(new ArrayList<Object>(Collections.singletonList(mCancelButton)));
240 LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates();
241 for (int i = 0; i < cellStates.length; i++) {
242 ArrayList<Object> row = new ArrayList<>();
243 for (int j = 0; j < cellStates[i].length; j++) {
244 row.add(cellStates[i][j]);
248 if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
249 result.add(new ArrayList<Object>(Collections.singletonList(mFingerprintIcon)));
251 Object[][] resultArr = new Object[result.size()][cellStates[0].length];
252 for (int i = 0; i < result.size(); i++) {
253 ArrayList<Object> row = result.get(i);
254 for (int j = 0; j < row.size(); j++) {
255 resultArr[i][j] = row.get(j);
262 public void startEnterAnimation() {
263 super.startEnterAnimation();
264 mLockPatternView.setAlpha(1f);
265 mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
268 private void updateStage(Stage stage) {
271 if (mHeaderText != null) {
272 mHeaderTextView.setText(mHeaderText);
274 mHeaderTextView.setText(R.string.lockpassword_confirm_your_pattern_header);
276 if (mDetailsText != null) {
277 mDetailsTextView.setText(mDetailsText);
278 } else if (!Utils.isManagedProfile(
279 UserManager.get(getActivity()), mEffectiveUserId)) {
280 mDetailsTextView.setText(
281 R.string.lockpassword_confirm_your_pattern_generic);
283 mDetailsTextView.setText(
284 R.string.lockpassword_confirm_your_pattern_generic_profile);
286 mErrorTextView.setText("");
287 if (isProfileChallenge()) {
288 updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(
292 mLockPatternView.setEnabled(true);
293 mLockPatternView.enableInput();
294 mLockPatternView.clearPattern();
296 case NeedToUnlockWrong:
297 mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
299 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
300 mLockPatternView.setEnabled(true);
301 mLockPatternView.enableInput();
304 mLockPatternView.clearPattern();
305 // enabled = false means: disable input, and have the
306 // appearance of being disabled.
307 mLockPatternView.setEnabled(false); // appearance of being disabled
311 // Always announce the header for accessibility. This is a no-op
312 // when accessibility is disabled.
313 mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
316 private Runnable mClearPatternRunnable = new Runnable() {
318 mLockPatternView.clearPattern();
322 // clear the wrong pattern unless they have started a new one
324 private void postClearPatternRunnable() {
325 mLockPatternView.removeCallbacks(mClearPatternRunnable);
326 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
330 protected void authenticationSucceeded() {
331 mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
334 private void startDisappearAnimation(final Intent intent) {
338 mDisappearing = true;
340 if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
341 mLockPatternView.clearPattern();
342 mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
346 // Bail if there is no active activity.
347 if (getActivity() == null || getActivity().isFinishing()) {
351 getActivity().setResult(RESULT_OK, intent);
352 getActivity().finish();
353 getActivity().overridePendingTransition(
354 R.anim.confirm_credential_close_enter,
355 R.anim.confirm_credential_close_exit);
359 getActivity().setResult(RESULT_OK, intent);
360 getActivity().finish();
365 public void onFingerprintIconVisibilityChanged(boolean visible) {
366 if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) {
368 // In landscape, adjust spacing depending on fingerprint icon visibility.
369 mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
370 mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
375 * The pattern listener that responds according to a user confirming
376 * an existing lock pattern.
378 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
379 = new LockPatternView.OnPatternListener() {
381 public void onPatternStart() {
382 mLockPatternView.removeCallbacks(mClearPatternRunnable);
385 public void onPatternCleared() {
386 mLockPatternView.removeCallbacks(mClearPatternRunnable);
389 public void onPatternCellAdded(List<Cell> pattern) {
393 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
394 if (mPendingLockCheck != null || mDisappearing) {
398 mLockPatternView.setEnabled(false);
400 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
401 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
402 Intent intent = new Intent();
403 if (verifyChallenge) {
404 if (isInternalActivity()) {
405 startVerifyPattern(pattern, intent);
409 startCheckPattern(pattern, intent);
413 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
416 private boolean isInternalActivity() {
417 return getActivity() instanceof ConfirmLockPattern.InternalActivity;
420 private void startVerifyPattern(final List<LockPatternView.Cell> pattern,
421 final Intent intent) {
422 final int localEffectiveUserId = mEffectiveUserId;
423 final int localUserId = mUserId;
424 long challenge = getActivity().getIntent().getLongExtra(
425 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
426 final LockPatternChecker.OnVerifyCallback onVerifyCallback =
427 new LockPatternChecker.OnVerifyCallback() {
429 public void onVerified(byte[] token, int timeoutMs) {
430 mPendingLockCheck = null;
431 boolean matched = false;
435 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
438 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
439 localEffectiveUserId);
442 mPendingLockCheck = (localEffectiveUserId == localUserId)
443 ? LockPatternChecker.verifyPattern(
444 mLockPatternUtils, pattern, challenge, localUserId,
446 : LockPatternChecker.verifyTiedProfileChallenge(
447 mLockPatternUtils, LockPatternUtils.patternToString(pattern),
448 true, challenge, localUserId, onVerifyCallback);
451 private void startCheckPattern(final List<LockPatternView.Cell> pattern,
452 final Intent intent) {
453 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
454 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
458 final int localEffectiveUserId = mEffectiveUserId;
459 mPendingLockCheck = LockPatternChecker.checkPattern(
462 localEffectiveUserId,
463 new LockPatternChecker.OnCheckCallback() {
465 public void onChecked(boolean matched, int timeoutMs) {
466 mPendingLockCheck = null;
467 if (matched && isInternalActivity()) {
468 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
469 StorageManager.CRYPT_TYPE_PATTERN);
470 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
471 LockPatternUtils.patternToString(pattern));
473 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
474 localEffectiveUserId);
480 private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
481 int effectiveUserId, boolean newResult) {
482 mLockPatternView.setEnabled(true);
485 reportSuccessfullAttempt();
487 startDisappearAnimation(intent);
488 checkForPendingIntent();
491 long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
492 effectiveUserId, timeoutMs);
493 handleAttemptLockout(deadline);
495 updateStage(Stage.NeedToUnlockWrong);
496 postClearPatternRunnable();
499 reportFailedAttempt();
505 public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
506 int effectiveUserId, boolean newResult) {
507 onPatternChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
511 protected int getLastTryErrorMessage() {
512 return R.string.lock_profile_wipe_warning_content_pattern;
515 private void handleAttemptLockout(long elapsedRealtimeDeadline) {
516 updateStage(Stage.LockedOut);
517 long elapsedRealtime = SystemClock.elapsedRealtime();
518 mCountdownTimer = new CountDownTimer(
519 elapsedRealtimeDeadline - elapsedRealtime,
520 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
523 public void onTick(long millisUntilFinished) {
524 final int secondsCountdown = (int) (millisUntilFinished / 1000);
525 mErrorTextView.setText(getString(
526 R.string.lockpattern_too_many_failed_confirmation_attempts,
531 public void onFinish() {
532 updateStage(Stage.NeedToUnlock);
538 public void createAnimation(Object obj, long delay,
539 long duration, float translationY, final boolean appearing,
540 Interpolator interpolator,
541 final Runnable finishListener) {
542 if (obj instanceof LockPatternView.CellState) {
543 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
544 mLockPatternView.startCellStateAnimation(animatedCell,
545 1f, appearing ? 1f : 0f, /* alpha */
546 appearing ? translationY : 0f, /* startTranslation */
547 appearing ? 0f : translationY, /* endTranslation */
548 appearing ? 0f : 1f, 1f /* scale */,
549 delay, duration, interpolator, finishListener);
551 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
552 appearing, interpolator, finishListener);