ConfirmLockPattern.java revision 7bdffd85ebae7b5b14eef07059cb501451476c7d
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 17package com.android.settings; 18 19import com.android.internal.logging.MetricsLogger; 20import com.android.internal.widget.LockPatternUtils; 21import com.android.internal.widget.LockPatternView; 22import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient; 23import com.android.internal.widget.LockPatternChecker; 24import com.android.internal.widget.LockPatternView.Cell; 25import com.android.settingslib.animation.AppearAnimationCreator; 26import com.android.settingslib.animation.AppearAnimationUtils; 27import com.android.settingslib.animation.DisappearAnimationUtils; 28 29import android.app.Activity; 30import android.content.Intent; 31import android.os.CountDownTimer; 32import android.os.SystemClock; 33import android.os.AsyncTask; 34import android.os.Bundle; 35import android.os.UserHandle; 36import android.os.storage.StorageManager; 37import android.view.animation.AnimationUtils; 38import android.view.animation.Interpolator; 39import android.widget.TextView; 40import android.view.LayoutInflater; 41import android.view.View; 42import android.view.ViewGroup; 43 44import java.util.ArrayList; 45import java.util.Collections; 46import java.util.List; 47 48/** 49 * Launch this when you want the user to confirm their lock pattern. 50 * 51 * Sets an activity result of {@link Activity#RESULT_OK} when the user 52 * successfully confirmed their pattern. 53 */ 54public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { 55 56 public static class InternalActivity extends ConfirmLockPattern { 57 } 58 59 private enum Stage { 60 NeedToUnlock, 61 NeedToUnlockWrong, 62 LockedOut 63 } 64 65 @Override 66 public Intent getIntent() { 67 Intent modIntent = new Intent(super.getIntent()); 68 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName()); 69 return modIntent; 70 } 71 72 @Override 73 protected boolean isValidFragment(String fragmentName) { 74 if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true; 75 return false; 76 } 77 78 public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment 79 implements AppearAnimationCreator<Object> { 80 81 // how long we wait to clear a wrong pattern 82 private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000; 83 84 private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts"; 85 86 private LockPatternView mLockPatternView; 87 private LockPatternUtils mLockPatternUtils; 88 private AsyncTask<?, ?, ?> mPendingLockCheck; 89 private int mNumWrongConfirmAttempts; 90 private CountDownTimer mCountdownTimer; 91 92 private TextView mHeaderTextView; 93 private TextView mDetailsTextView; 94 private TextView mErrorTextView; 95 private View mLeftSpacerLandscape; 96 private View mRightSpacerLandscape; 97 98 // caller-supplied text for various prompts 99 private CharSequence mHeaderText; 100 private CharSequence mDetailsText; 101 102 private AppearAnimationUtils mAppearAnimationUtils; 103 private DisappearAnimationUtils mDisappearAnimationUtils; 104 105 private int mEffectiveUserId; 106 107 // required constructor for fragments 108 public ConfirmLockPatternFragment() { 109 110 } 111 112 @Override 113 public void onCreate(Bundle savedInstanceState) { 114 super.onCreate(savedInstanceState); 115 mLockPatternUtils = new LockPatternUtils(getActivity()); 116 mEffectiveUserId = Utils.getEffectiveUserId(getActivity()); 117 } 118 119 @Override 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 mDetailsTextView = (TextView) view.findViewById(R.id.detailsText); 126 mErrorTextView = (TextView) view.findViewById(R.id.errorText); 127 mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer); 128 mRightSpacerLandscape = view.findViewById(R.id.rightSpacer); 129 130 // make it so unhandled touch events within the unlock screen go to the 131 // lock pattern view. 132 final LinearLayoutWithDefaultTouchRecepient topLayout 133 = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout); 134 topLayout.setDefaultTouchRecepient(mLockPatternView); 135 136 Intent intent = getActivity().getIntent(); 137 if (intent != null) { 138 mHeaderText = intent.getCharSequenceExtra( 139 ConfirmDeviceCredentialBaseFragment.HEADER_TEXT); 140 mDetailsText = intent.getCharSequenceExtra( 141 ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT); 142 } 143 144 mLockPatternView.setTactileFeedbackEnabled( 145 mLockPatternUtils.isTactileFeedbackEnabled()); 146 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 147 UserHandle.myUserId())); 148 mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener); 149 updateStage(Stage.NeedToUnlock); 150 151 if (savedInstanceState != null) { 152 mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS); 153 } else { 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 156 // doesn't exist). 157 if (!mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) { 158 getActivity().setResult(Activity.RESULT_OK); 159 getActivity().finish(); 160 } 161 } 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() { 171 @Override 172 public float getRowTranslationScale(int row, int numRows) { 173 return (float)(numRows - row) / numRows; 174 } 175 }); 176 setAccessibilityTitle(mHeaderTextView.getText()); 177 return view; 178 } 179 180 @Override 181 public void onSaveInstanceState(Bundle outState) { 182 // deliberately not calling super since we are managing this in full 183 outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts); 184 } 185 186 @Override 187 public void onPause() { 188 super.onPause(); 189 190 if (mCountdownTimer != null) { 191 mCountdownTimer.cancel(); 192 } 193 if (mPendingLockCheck != null) { 194 mPendingLockCheck.cancel(false); 195 mPendingLockCheck = null; 196 } 197 } 198 199 @Override 200 protected int getMetricsCategory() { 201 return MetricsLogger.CONFIRM_LOCK_PATTERN; 202 } 203 204 @Override 205 public void onResume() { 206 super.onResume(); 207 208 // if the user is currently locked out, enforce it. 209 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId); 210 if (deadline != 0) { 211 handleAttemptLockout(deadline); 212 } else if (!mLockPatternView.isEnabled()) { 213 // The deadline has passed, but the timer was cancelled. Or the pending lock 214 // check was cancelled. Need to clean up. 215 mNumWrongConfirmAttempts = 0; 216 updateStage(Stage.NeedToUnlock); 217 } 218 } 219 220 @Override 221 public void prepareEnterAnimation() { 222 super.prepareEnterAnimation(); 223 mHeaderTextView.setAlpha(0f); 224 mCancelButton.setAlpha(0f); 225 mLockPatternView.setAlpha(0f); 226 mDetailsTextView.setAlpha(0f); 227 mFingerprintIcon.setAlpha(0f); 228 } 229 230 private Object[][] getActiveViews() { 231 ArrayList<ArrayList<Object>> result = new ArrayList<>(); 232 result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView))); 233 result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView))); 234 if (mCancelButton.getVisibility() == View.VISIBLE) { 235 result.add(new ArrayList<Object>(Collections.singletonList(mCancelButton))); 236 } 237 LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates(); 238 for (int i = 0; i < cellStates.length; i++) { 239 ArrayList<Object> row = new ArrayList<>(); 240 for (int j = 0; j < cellStates[i].length; j++) { 241 row.add(cellStates[i][j]); 242 } 243 result.add(row); 244 } 245 if (mFingerprintIcon.getVisibility() == View.VISIBLE) { 246 result.add(new ArrayList<Object>(Collections.singletonList(mFingerprintIcon))); 247 } 248 Object[][] resultArr = new Object[result.size()][cellStates[0].length]; 249 for (int i = 0; i < result.size(); i++) { 250 ArrayList<Object> row = result.get(i); 251 for (int j = 0; j < row.size(); j++) { 252 resultArr[i][j] = row.get(j); 253 } 254 } 255 return resultArr; 256 } 257 258 @Override 259 public void startEnterAnimation() { 260 super.startEnterAnimation(); 261 mLockPatternView.setAlpha(1f); 262 mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this); 263 } 264 265 private void updateStage(Stage stage) { 266 switch (stage) { 267 case NeedToUnlock: 268 if (mHeaderText != null) { 269 mHeaderTextView.setText(mHeaderText); 270 } else { 271 mHeaderTextView.setText(R.string.lockpassword_confirm_your_pattern_header); 272 } 273 if (mDetailsText != null) { 274 mDetailsTextView.setText(mDetailsText); 275 } else { 276 mDetailsTextView.setText( 277 R.string.lockpassword_confirm_your_pattern_generic); 278 } 279 mErrorTextView.setText(""); 280 281 mLockPatternView.setEnabled(true); 282 mLockPatternView.enableInput(); 283 mLockPatternView.clearPattern(); 284 break; 285 case NeedToUnlockWrong: 286 mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong); 287 288 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 289 mLockPatternView.setEnabled(true); 290 mLockPatternView.enableInput(); 291 break; 292 case LockedOut: 293 mLockPatternView.clearPattern(); 294 // enabled = false means: disable input, and have the 295 // appearance of being disabled. 296 mLockPatternView.setEnabled(false); // appearance of being disabled 297 break; 298 } 299 300 // Always announce the header for accessibility. This is a no-op 301 // when accessibility is disabled. 302 mHeaderTextView.announceForAccessibility(mHeaderTextView.getText()); 303 } 304 305 private Runnable mClearPatternRunnable = new Runnable() { 306 public void run() { 307 mLockPatternView.clearPattern(); 308 } 309 }; 310 311 // clear the wrong pattern unless they have started a new one 312 // already 313 private void postClearPatternRunnable() { 314 mLockPatternView.removeCallbacks(mClearPatternRunnable); 315 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS); 316 } 317 318 @Override 319 protected void authenticationSucceeded() { 320 startDisappearAnimation(new Intent()); 321 } 322 323 private void startDisappearAnimation(final Intent intent) { 324 if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) { 325 mLockPatternView.clearPattern(); 326 mDisappearAnimationUtils.startAnimation2d(getActiveViews(), 327 new Runnable() { 328 @Override 329 public void run() { 330 getActivity().setResult(RESULT_OK, intent); 331 getActivity().finish(); 332 getActivity().overridePendingTransition( 333 R.anim.confirm_credential_close_enter, 334 R.anim.confirm_credential_close_exit); 335 } 336 }, this); 337 } else { 338 getActivity().setResult(RESULT_OK, intent); 339 getActivity().finish(); 340 } 341 } 342 343 @Override 344 public void onFingerprintIconVisibilityChanged(boolean visible) { 345 if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) { 346 347 // In landscape, adjust spacing depending on fingerprint icon visibility. 348 mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE); 349 mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE); 350 } 351 } 352 353 /** 354 * The pattern listener that responds according to a user confirming 355 * an existing lock pattern. 356 */ 357 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener 358 = new LockPatternView.OnPatternListener() { 359 360 public void onPatternStart() { 361 mLockPatternView.removeCallbacks(mClearPatternRunnable); 362 } 363 364 public void onPatternCleared() { 365 mLockPatternView.removeCallbacks(mClearPatternRunnable); 366 } 367 368 public void onPatternCellAdded(List<Cell> pattern) { 369 370 } 371 372 public void onPatternDetected(List<LockPatternView.Cell> pattern) { 373 mLockPatternView.setEnabled(false); 374 if (mPendingLockCheck != null) { 375 mPendingLockCheck.cancel(false); 376 } 377 378 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra( 379 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 380 Intent intent = new Intent(); 381 if (verifyChallenge) { 382 if (isInternalActivity()) { 383 startVerifyPattern(pattern, intent); 384 return; 385 } 386 } else { 387 startCheckPattern(pattern, intent); 388 return; 389 } 390 391 onPatternChecked(pattern, false, intent, 0, mEffectiveUserId); 392 } 393 394 private boolean isInternalActivity() { 395 return getActivity() instanceof ConfirmLockPattern.InternalActivity; 396 } 397 398 private void startVerifyPattern(final List<LockPatternView.Cell> pattern, 399 final Intent intent) { 400 final int localEffectiveUserId = mEffectiveUserId; 401 long challenge = getActivity().getIntent().getLongExtra( 402 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 403 mPendingLockCheck = LockPatternChecker.verifyPattern( 404 mLockPatternUtils, 405 pattern, 406 challenge, 407 localEffectiveUserId, 408 new LockPatternChecker.OnVerifyCallback() { 409 @Override 410 public void onVerified(byte[] token, int timeoutMs) { 411 mPendingLockCheck = null; 412 boolean matched = false; 413 if (token != null) { 414 matched = true; 415 intent.putExtra( 416 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 417 token); 418 } 419 onPatternChecked(pattern, 420 matched, intent, timeoutMs, localEffectiveUserId); 421 } 422 }); 423 } 424 425 private void startCheckPattern(final List<LockPatternView.Cell> pattern, 426 final Intent intent) { 427 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { 428 onPatternChecked(pattern, false, intent, 0, mEffectiveUserId); 429 return; 430 } 431 432 final int localEffectiveUserId = mEffectiveUserId; 433 mPendingLockCheck = LockPatternChecker.checkPattern( 434 mLockPatternUtils, 435 pattern, 436 localEffectiveUserId, 437 new LockPatternChecker.OnCheckCallback() { 438 @Override 439 public void onChecked(boolean matched, int timeoutMs) { 440 mPendingLockCheck = null; 441 if (matched && isInternalActivity()) { 442 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, 443 StorageManager.CRYPT_TYPE_PATTERN); 444 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, 445 LockPatternUtils.patternToString(pattern)); 446 } 447 onPatternChecked(pattern, matched, intent, timeoutMs, 448 localEffectiveUserId); 449 } 450 }); 451 } 452 453 private void onPatternChecked(List<LockPatternView.Cell> pattern, 454 boolean matched, Intent intent, int timeoutMs, int effectiveUserId) { 455 mLockPatternView.setEnabled(true); 456 if (matched) { 457 startDisappearAnimation(intent); 458 } else { 459 if (timeoutMs > 0) { 460 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 461 effectiveUserId, timeoutMs); 462 handleAttemptLockout(deadline); 463 } else { 464 updateStage(Stage.NeedToUnlockWrong); 465 postClearPatternRunnable(); 466 } 467 } 468 } 469 }; 470 471 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 472 updateStage(Stage.LockedOut); 473 long elapsedRealtime = SystemClock.elapsedRealtime(); 474 mCountdownTimer = new CountDownTimer( 475 elapsedRealtimeDeadline - elapsedRealtime, 476 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) { 477 478 @Override 479 public void onTick(long millisUntilFinished) { 480 final int secondsCountdown = (int) (millisUntilFinished / 1000); 481 mErrorTextView.setText(getString( 482 R.string.lockpattern_too_many_failed_confirmation_attempts, 483 secondsCountdown)); 484 } 485 486 @Override 487 public void onFinish() { 488 mNumWrongConfirmAttempts = 0; 489 updateStage(Stage.NeedToUnlock); 490 } 491 }.start(); 492 } 493 494 @Override 495 public void createAnimation(Object obj, long delay, 496 long duration, float translationY, final boolean appearing, 497 Interpolator interpolator, 498 final Runnable finishListener) { 499 if (obj instanceof LockPatternView.CellState) { 500 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj; 501 mLockPatternView.startCellStateAnimation(animatedCell, 502 1f, appearing ? 1f : 0f, /* alpha */ 503 appearing ? translationY : 0f, /* startTranslation */ 504 appearing ? 0f : translationY, /* endTranslation */ 505 appearing ? 0f : 1f, 1f /* scale */, 506 delay, duration, interpolator, finishListener); 507 } else { 508 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY, 509 appearing, interpolator, finishListener); 510 } 511 } 512 } 513} 514