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.content.IntentSender;
22 import android.os.AsyncTask;
23 import android.os.Bundle;
24 import android.os.CountDownTimer;
25 import android.os.SystemClock;
26 import android.os.UserManager;
27 import android.os.storage.StorageManager;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.animation.AnimationUtils;
32 import android.view.animation.Interpolator;
33 import android.widget.TextView;
35 import com.android.internal.logging.MetricsLogger;
36 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
37 import com.android.internal.widget.LockPatternChecker;
38 import com.android.internal.widget.LockPatternUtils;
39 import com.android.internal.widget.LockPatternView;
40 import com.android.internal.widget.LockPatternView.Cell;
41 import com.android.settingslib.animation.AppearAnimationCreator;
42 import com.android.settingslib.animation.AppearAnimationUtils;
43 import com.android.settingslib.animation.DisappearAnimationUtils;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
50 * Launch this when you want the user to confirm their lock pattern.
52 * Sets an activity result of {@link Activity#RESULT_OK} when the user
53 * successfully confirmed their pattern.
55 public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
57 public static class InternalActivity extends ConfirmLockPattern {
67 public Intent getIntent() {
68 Intent modIntent = new Intent(super.getIntent());
69 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
74 protected boolean isValidFragment(String fragmentName) {
75 if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true;
79 public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
80 implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener {
82 // how long we wait to clear a wrong pattern
83 private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
85 private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
87 private LockPatternView mLockPatternView;
88 private LockPatternUtils mLockPatternUtils;
89 private AsyncTask<?, ?, ?> mPendingLockCheck;
90 private CredentialCheckResultTracker mCredentialCheckResultTracker;
91 private boolean mDisappearing = false;
92 private CountDownTimer mCountdownTimer;
94 private TextView mHeaderTextView;
95 private TextView mDetailsTextView;
96 private TextView mErrorTextView;
97 private View mLeftSpacerLandscape;
98 private View mRightSpacerLandscape;
100 // caller-supplied text for various prompts
101 private CharSequence mHeaderText;
102 private CharSequence mDetailsText;
104 private AppearAnimationUtils mAppearAnimationUtils;
105 private DisappearAnimationUtils mDisappearAnimationUtils;
107 // required constructor for fragments
108 public ConfirmLockPatternFragment() {
113 public void onCreate(Bundle savedInstanceState) {
114 super.onCreate(savedInstanceState);
115 mLockPatternUtils = new LockPatternUtils(getActivity());
119 public View onCreateView(LayoutInflater inflater, ViewGroup container,
120 Bundle savedInstanceState) {
121 View view = inflater.inflate(R.layout.confirm_lock_pattern, null);
122 mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
123 mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
124 mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
125 mErrorTextView = (TextView) view.findViewById(R.id.errorText);
126 mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer);
127 mRightSpacerLandscape = view.findViewById(R.id.rightSpacer);
129 // make it so unhandled touch events within the unlock screen go to the
130 // lock pattern view.
131 final LinearLayoutWithDefaultTouchRecepient topLayout
132 = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
133 topLayout.setDefaultTouchRecepient(mLockPatternView);
135 Intent intent = getActivity().getIntent();
136 if (intent != null) {
137 mHeaderText = intent.getCharSequenceExtra(
138 ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
139 mDetailsText = intent.getCharSequenceExtra(
140 ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
143 mLockPatternView.setTactileFeedbackEnabled(
144 mLockPatternUtils.isTactileFeedbackEnabled());
145 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
147 mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
148 updateStage(Stage.NeedToUnlock);
150 if (savedInstanceState == null) {
151 // on first launch, if no lock pattern is set, then finish with
152 // success (don't want user to get stuck confirming something that
154 if (!mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
155 getActivity().setResult(Activity.RESULT_OK);
156 getActivity().finish();
159 mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
160 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */,
161 1.3f /* delayScale */, AnimationUtils.loadInterpolator(
162 getContext(), android.R.interpolator.linear_out_slow_in));
163 mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
164 125, 4f /* translationScale */,
165 0.3f /* delayScale */, AnimationUtils.loadInterpolator(
166 getContext(), android.R.interpolator.fast_out_linear_in),
167 new AppearAnimationUtils.RowTranslationScaler() {
169 public float getRowTranslationScale(int row, int numRows) {
170 return (float)(numRows - row) / numRows;
173 setAccessibilityTitle(mHeaderTextView.getText());
175 mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
176 .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
177 if (mCredentialCheckResultTracker == null) {
178 mCredentialCheckResultTracker = new CredentialCheckResultTracker();
179 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
180 FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
187 public void onSaveInstanceState(Bundle outState) {
188 // deliberately not calling super since we are managing this in full
192 public void onPause() {
195 if (mCountdownTimer != null) {
196 mCountdownTimer.cancel();
198 mCredentialCheckResultTracker.setListener(null);
202 protected int getMetricsCategory() {
203 return MetricsLogger.CONFIRM_LOCK_PATTERN;
207 public void onResume() {
210 // if the user is currently locked out, enforce it.
211 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
213 mCredentialCheckResultTracker.clearResult();
214 handleAttemptLockout(deadline);
215 } else if (!mLockPatternView.isEnabled()) {
216 // The deadline has passed, but the timer was cancelled. Or the pending lock
217 // check was cancelled. Need to clean up.
218 updateStage(Stage.NeedToUnlock);
220 mCredentialCheckResultTracker.setListener(this);
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("");
288 mLockPatternView.setEnabled(true);
289 mLockPatternView.enableInput();
290 mLockPatternView.clearPattern();
292 case NeedToUnlockWrong:
293 mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
295 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
296 mLockPatternView.setEnabled(true);
297 mLockPatternView.enableInput();
300 mLockPatternView.clearPattern();
301 // enabled = false means: disable input, and have the
302 // appearance of being disabled.
303 mLockPatternView.setEnabled(false); // appearance of being disabled
307 // Always announce the header for accessibility. This is a no-op
308 // when accessibility is disabled.
309 mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
312 private Runnable mClearPatternRunnable = new Runnable() {
314 mLockPatternView.clearPattern();
318 // clear the wrong pattern unless they have started a new one
320 private void postClearPatternRunnable() {
321 mLockPatternView.removeCallbacks(mClearPatternRunnable);
322 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
326 protected void authenticationSucceeded() {
327 mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
330 private void startDisappearAnimation(final Intent intent) {
334 mDisappearing = true;
336 if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
337 mLockPatternView.clearPattern();
338 mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
342 // Bail if there is no active activity.
343 if (getActivity() == null || getActivity().isFinishing()) {
347 getActivity().setResult(RESULT_OK, intent);
348 getActivity().finish();
349 getActivity().overridePendingTransition(
350 R.anim.confirm_credential_close_enter,
351 R.anim.confirm_credential_close_exit);
355 getActivity().setResult(RESULT_OK, intent);
356 getActivity().finish();
361 public void onFingerprintIconVisibilityChanged(boolean visible) {
362 if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) {
364 // In landscape, adjust spacing depending on fingerprint icon visibility.
365 mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
366 mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
371 * The pattern listener that responds according to a user confirming
372 * an existing lock pattern.
374 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
375 = new LockPatternView.OnPatternListener() {
377 public void onPatternStart() {
378 mLockPatternView.removeCallbacks(mClearPatternRunnable);
381 public void onPatternCleared() {
382 mLockPatternView.removeCallbacks(mClearPatternRunnable);
385 public void onPatternCellAdded(List<Cell> pattern) {
389 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
390 if (mPendingLockCheck != null || mDisappearing) {
394 mLockPatternView.setEnabled(false);
396 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
397 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
398 Intent intent = new Intent();
399 if (verifyChallenge) {
400 if (isInternalActivity()) {
401 startVerifyPattern(pattern, intent);
405 startCheckPattern(pattern, intent);
409 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
412 private boolean isInternalActivity() {
413 return getActivity() instanceof ConfirmLockPattern.InternalActivity;
416 private void startVerifyPattern(final List<LockPatternView.Cell> pattern,
417 final Intent intent) {
418 final int localEffectiveUserId = mEffectiveUserId;
419 long challenge = getActivity().getIntent().getLongExtra(
420 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
421 mPendingLockCheck = LockPatternChecker.verifyPattern(
425 localEffectiveUserId,
426 new LockPatternChecker.OnVerifyCallback() {
428 public void onVerified(byte[] token, int timeoutMs) {
429 mPendingLockCheck = null;
430 boolean matched = false;
434 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
437 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
438 localEffectiveUserId);
443 private void startCheckPattern(final List<LockPatternView.Cell> pattern,
444 final Intent intent) {
445 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
446 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
450 final int localEffectiveUserId = mEffectiveUserId;
451 mPendingLockCheck = LockPatternChecker.checkPattern(
454 localEffectiveUserId,
455 new LockPatternChecker.OnCheckCallback() {
457 public void onChecked(boolean matched, int timeoutMs) {
458 mPendingLockCheck = null;
459 if (matched && isInternalActivity()) {
460 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
461 StorageManager.CRYPT_TYPE_PATTERN);
462 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
463 LockPatternUtils.patternToString(pattern));
465 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
466 localEffectiveUserId);
472 private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
473 int effectiveUserId) {
474 mLockPatternView.setEnabled(true);
476 startDisappearAnimation(intent);
477 checkForPendingIntent();
480 long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
481 effectiveUserId, timeoutMs);
482 handleAttemptLockout(deadline);
484 updateStage(Stage.NeedToUnlockWrong);
485 postClearPatternRunnable();
491 public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
492 int effectiveUserId) {
493 onPatternChecked(matched, intent, timeoutMs, effectiveUserId);
496 private void handleAttemptLockout(long elapsedRealtimeDeadline) {
497 updateStage(Stage.LockedOut);
498 long elapsedRealtime = SystemClock.elapsedRealtime();
499 mCountdownTimer = new CountDownTimer(
500 elapsedRealtimeDeadline - elapsedRealtime,
501 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
504 public void onTick(long millisUntilFinished) {
505 final int secondsCountdown = (int) (millisUntilFinished / 1000);
506 mErrorTextView.setText(getString(
507 R.string.lockpattern_too_many_failed_confirmation_attempts,
512 public void onFinish() {
513 updateStage(Stage.NeedToUnlock);
519 public void createAnimation(Object obj, long delay,
520 long duration, float translationY, final boolean appearing,
521 Interpolator interpolator,
522 final Runnable finishListener) {
523 if (obj instanceof LockPatternView.CellState) {
524 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
525 mLockPatternView.startCellStateAnimation(animatedCell,
526 1f, appearing ? 1f : 0f, /* alpha */
527 appearing ? translationY : 0f, /* startTranslation */
528 appearing ? 0f : translationY, /* endTranslation */
529 appearing ? 0f : 1f, 1f /* scale */,
530 delay, duration, interpolator, finishListener);
532 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
533 appearing, interpolator, finishListener);