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 17 package com.android.settings.password; 18 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; 33 34 import com.android.internal.logging.nano.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.settings.R; 41 import com.android.settingslib.animation.AppearAnimationCreator; 42 import com.android.settingslib.animation.AppearAnimationUtils; 43 import com.android.settingslib.animation.DisappearAnimationUtils; 44 45 import java.util.ArrayList; 46 import java.util.Collections; 47 import java.util.List; 48 49 /** 50 * Launch this when you want the user to confirm their lock pattern. 51 * 52 * Sets an activity result of {@link Activity#RESULT_OK} when the user 53 * successfully confirmed their pattern. 54 */ 55 public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { 56 57 public static class InternalActivity extends ConfirmLockPattern { 58 } 59 60 private enum Stage { 61 NeedToUnlock, 62 NeedToUnlockWrong, 63 LockedOut 64 } 65 66 @Override 67 public Intent getIntent() { 68 Intent modIntent = new Intent(super.getIntent()); 69 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName()); 70 return modIntent; 71 } 72 73 @Override 74 protected boolean isValidFragment(String fragmentName) { 75 if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true; 76 return false; 77 } 78 79 public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment 80 implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener { 81 82 private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result"; 83 84 private LockPatternView mLockPatternView; 85 private AsyncTask<?, ?, ?> mPendingLockCheck; 86 private CredentialCheckResultTracker mCredentialCheckResultTracker; 87 private boolean mDisappearing = false; 88 private CountDownTimer mCountdownTimer; 89 90 private TextView mHeaderTextView; 91 private TextView mDetailsTextView; 92 private View mLeftSpacerLandscape; 93 private View mRightSpacerLandscape; 94 95 // caller-supplied text for various prompts 96 private CharSequence mHeaderText; 97 private CharSequence mDetailsText; 98 99 private AppearAnimationUtils mAppearAnimationUtils; 100 private DisappearAnimationUtils mDisappearAnimationUtils; 101 102 // required constructor for fragments 103 public ConfirmLockPatternFragment() { 104 105 } 106 107 @Override 108 public void onCreate(Bundle savedInstanceState) { 109 super.onCreate(savedInstanceState); 110 } 111 112 @Override 113 public View onCreateView(LayoutInflater inflater, ViewGroup container, 114 Bundle savedInstanceState) { 115 ConfirmLockPattern activity = (ConfirmLockPattern) getActivity(); 116 View view = inflater.inflate( 117 activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.INTERNAL 118 ? R.layout.confirm_lock_pattern_internal 119 : R.layout.confirm_lock_pattern, 120 container, 121 false); 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); 128 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); 134 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); 141 } 142 143 mLockPatternView.setTactileFeedbackEnabled( 144 mLockPatternUtils.isTactileFeedbackEnabled()); 145 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 146 mEffectiveUserId)); 147 mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener); 148 updateStage(Stage.NeedToUnlock); 149 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 153 // doesn't exist). 154 // Don't do this check for FRP though, because the pattern is not stored 155 // in a way that isLockPatternEnabled is aware of for that case. 156 // TODO(roosa): This block should no longer be needed since we removed the 157 // ability to disable the pattern in L. Remove this block after 158 // ensuring it's safe to do so. (Note that ConfirmLockPassword 159 // doesn't have this). 160 if (!mFrp && !mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) { 161 getActivity().setResult(Activity.RESULT_OK); 162 getActivity().finish(); 163 } 164 } 165 mAppearAnimationUtils = new AppearAnimationUtils(getContext(), 166 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */, 167 1.3f /* delayScale */, AnimationUtils.loadInterpolator( 168 getContext(), android.R.interpolator.linear_out_slow_in)); 169 mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(), 170 125, 4f /* translationScale */, 171 0.3f /* delayScale */, AnimationUtils.loadInterpolator( 172 getContext(), android.R.interpolator.fast_out_linear_in), 173 new AppearAnimationUtils.RowTranslationScaler() { 174 @Override 175 public float getRowTranslationScale(int row, int numRows) { 176 return (float)(numRows - row) / numRows; 177 } 178 }); 179 setAccessibilityTitle(mHeaderTextView.getText()); 180 181 mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager() 182 .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT); 183 if (mCredentialCheckResultTracker == null) { 184 mCredentialCheckResultTracker = new CredentialCheckResultTracker(); 185 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker, 186 FRAGMENT_TAG_CHECK_LOCK_RESULT).commit(); 187 } 188 return view; 189 } 190 191 @Override 192 public void onSaveInstanceState(Bundle outState) { 193 // deliberately not calling super since we are managing this in full 194 } 195 196 @Override 197 public void onPause() { 198 super.onPause(); 199 200 if (mCountdownTimer != null) { 201 mCountdownTimer.cancel(); 202 } 203 mCredentialCheckResultTracker.setListener(null); 204 } 205 206 @Override 207 public int getMetricsCategory() { 208 return MetricsEvent.CONFIRM_LOCK_PATTERN; 209 } 210 211 @Override 212 public void onResume() { 213 super.onResume(); 214 215 // if the user is currently locked out, enforce it. 216 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId); 217 if (deadline != 0) { 218 mCredentialCheckResultTracker.clearResult(); 219 handleAttemptLockout(deadline); 220 } else if (!mLockPatternView.isEnabled()) { 221 // The deadline has passed, but the timer was cancelled. Or the pending lock 222 // check was cancelled. Need to clean up. 223 updateStage(Stage.NeedToUnlock); 224 } 225 mCredentialCheckResultTracker.setListener(this); 226 } 227 228 @Override 229 protected void onShowError() { 230 } 231 232 @Override 233 public void prepareEnterAnimation() { 234 super.prepareEnterAnimation(); 235 mHeaderTextView.setAlpha(0f); 236 mCancelButton.setAlpha(0f); 237 mLockPatternView.setAlpha(0f); 238 mDetailsTextView.setAlpha(0f); 239 mFingerprintIcon.setAlpha(0f); 240 } 241 242 private int getDefaultDetails() { 243 if (mFrp) { 244 return R.string.lockpassword_confirm_your_pattern_details_frp; 245 } 246 final boolean isStrongAuthRequired = isStrongAuthRequired(); 247 if (UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId)) { 248 return isStrongAuthRequired 249 ? R.string.lockpassword_strong_auth_required_work_pattern 250 : R.string.lockpassword_confirm_your_pattern_generic_profile; 251 } else { 252 return isStrongAuthRequired 253 ? R.string.lockpassword_strong_auth_required_device_pattern 254 : R.string.lockpassword_confirm_your_pattern_generic; 255 } 256 } 257 258 private Object[][] getActiveViews() { 259 ArrayList<ArrayList<Object>> result = new ArrayList<>(); 260 result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView))); 261 result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView))); 262 if (mCancelButton.getVisibility() == View.VISIBLE) { 263 result.add(new ArrayList<Object>(Collections.singletonList(mCancelButton))); 264 } 265 LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates(); 266 for (int i = 0; i < cellStates.length; i++) { 267 ArrayList<Object> row = new ArrayList<>(); 268 for (int j = 0; j < cellStates[i].length; j++) { 269 row.add(cellStates[i][j]); 270 } 271 result.add(row); 272 } 273 if (mFingerprintIcon.getVisibility() == View.VISIBLE) { 274 result.add(new ArrayList<Object>(Collections.singletonList(mFingerprintIcon))); 275 } 276 Object[][] resultArr = new Object[result.size()][cellStates[0].length]; 277 for (int i = 0; i < result.size(); i++) { 278 ArrayList<Object> row = result.get(i); 279 for (int j = 0; j < row.size(); j++) { 280 resultArr[i][j] = row.get(j); 281 } 282 } 283 return resultArr; 284 } 285 286 @Override 287 public void startEnterAnimation() { 288 super.startEnterAnimation(); 289 mLockPatternView.setAlpha(1f); 290 mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this); 291 } 292 293 private void updateStage(Stage stage) { 294 switch (stage) { 295 case NeedToUnlock: 296 if (mHeaderText != null) { 297 mHeaderTextView.setText(mHeaderText); 298 } else { 299 mHeaderTextView.setText(getDefaultHeader()); 300 } 301 if (mDetailsText != null) { 302 mDetailsTextView.setText(mDetailsText); 303 } else { 304 mDetailsTextView.setText(getDefaultDetails()); 305 } 306 mErrorTextView.setText(""); 307 updateErrorMessage( 308 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); 309 310 mLockPatternView.setEnabled(true); 311 mLockPatternView.enableInput(); 312 mLockPatternView.clearPattern(); 313 break; 314 case NeedToUnlockWrong: 315 showError(R.string.lockpattern_need_to_unlock_wrong, 316 CLEAR_WRONG_ATTEMPT_TIMEOUT_MS); 317 318 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 319 mLockPatternView.setEnabled(true); 320 mLockPatternView.enableInput(); 321 break; 322 case LockedOut: 323 mLockPatternView.clearPattern(); 324 // enabled = false means: disable input, and have the 325 // appearance of being disabled. 326 mLockPatternView.setEnabled(false); // appearance of being disabled 327 break; 328 } 329 330 // Always announce the header for accessibility. This is a no-op 331 // when accessibility is disabled. 332 mHeaderTextView.announceForAccessibility(mHeaderTextView.getText()); 333 } 334 335 private int getDefaultHeader() { 336 return mFrp ? R.string.lockpassword_confirm_your_pattern_header_frp 337 : R.string.lockpassword_confirm_your_pattern_header; 338 } 339 340 private Runnable mClearPatternRunnable = new Runnable() { 341 public void run() { 342 mLockPatternView.clearPattern(); 343 } 344 }; 345 346 // clear the wrong pattern unless they have started a new one 347 // already 348 private void postClearPatternRunnable() { 349 mLockPatternView.removeCallbacks(mClearPatternRunnable); 350 mLockPatternView.postDelayed(mClearPatternRunnable, CLEAR_WRONG_ATTEMPT_TIMEOUT_MS); 351 } 352 353 @Override 354 protected void authenticationSucceeded() { 355 mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId); 356 } 357 358 private void startDisappearAnimation(final Intent intent) { 359 if (mDisappearing) { 360 return; 361 } 362 mDisappearing = true; 363 364 final ConfirmLockPattern activity = (ConfirmLockPattern) getActivity(); 365 // Bail if there is no active activity. 366 if (activity == null || activity.isFinishing()) { 367 return; 368 } 369 if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) { 370 mLockPatternView.clearPattern(); 371 mDisappearAnimationUtils.startAnimation2d(getActiveViews(), 372 () -> { 373 activity.setResult(RESULT_OK, intent); 374 activity.finish(); 375 activity.overridePendingTransition( 376 R.anim.confirm_credential_close_enter, 377 R.anim.confirm_credential_close_exit); 378 }, this); 379 } else { 380 activity.setResult(RESULT_OK, intent); 381 activity.finish(); 382 } 383 } 384 385 @Override 386 public void onFingerprintIconVisibilityChanged(boolean visible) { 387 if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) { 388 389 // In landscape, adjust spacing depending on fingerprint icon visibility. 390 mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE); 391 mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE); 392 } 393 } 394 395 /** 396 * The pattern listener that responds according to a user confirming 397 * an existing lock pattern. 398 */ 399 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener 400 = new LockPatternView.OnPatternListener() { 401 402 public void onPatternStart() { 403 mLockPatternView.removeCallbacks(mClearPatternRunnable); 404 } 405 406 public void onPatternCleared() { 407 mLockPatternView.removeCallbacks(mClearPatternRunnable); 408 } 409 410 public void onPatternCellAdded(List<Cell> pattern) { 411 412 } 413 414 public void onPatternDetected(List<LockPatternView.Cell> pattern) { 415 if (mPendingLockCheck != null || mDisappearing) { 416 return; 417 } 418 419 mLockPatternView.setEnabled(false); 420 421 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra( 422 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 423 Intent intent = new Intent(); 424 if (verifyChallenge) { 425 if (isInternalActivity()) { 426 startVerifyPattern(pattern, intent); 427 return; 428 } 429 } else { 430 startCheckPattern(pattern, intent); 431 return; 432 } 433 434 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId); 435 } 436 437 private boolean isInternalActivity() { 438 return getActivity() instanceof ConfirmLockPattern.InternalActivity; 439 } 440 441 private void startVerifyPattern(final List<LockPatternView.Cell> pattern, 442 final Intent intent) { 443 final int localEffectiveUserId = mEffectiveUserId; 444 final int localUserId = mUserId; 445 long challenge = getActivity().getIntent().getLongExtra( 446 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 447 final LockPatternChecker.OnVerifyCallback onVerifyCallback = 448 new LockPatternChecker.OnVerifyCallback() { 449 @Override 450 public void onVerified(byte[] token, int timeoutMs) { 451 mPendingLockCheck = null; 452 boolean matched = false; 453 if (token != null) { 454 matched = true; 455 if (mReturnCredentials) { 456 intent.putExtra( 457 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 458 token); 459 } 460 } 461 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 462 localEffectiveUserId); 463 } 464 }; 465 mPendingLockCheck = (localEffectiveUserId == localUserId) 466 ? LockPatternChecker.verifyPattern( 467 mLockPatternUtils, pattern, challenge, localUserId, 468 onVerifyCallback) 469 : LockPatternChecker.verifyTiedProfileChallenge( 470 mLockPatternUtils, LockPatternUtils.patternToString(pattern), 471 true, challenge, localUserId, onVerifyCallback); 472 } 473 474 private void startCheckPattern(final List<LockPatternView.Cell> pattern, 475 final Intent intent) { 476 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { 477 // Pattern size is less than the minimum, do not count it as an fail attempt. 478 onPatternChecked(false, intent, 0, mEffectiveUserId, false /* newResult */); 479 return; 480 } 481 482 final int localEffectiveUserId = mEffectiveUserId; 483 mPendingLockCheck = LockPatternChecker.checkPattern( 484 mLockPatternUtils, 485 pattern, 486 localEffectiveUserId, 487 new LockPatternChecker.OnCheckCallback() { 488 @Override 489 public void onChecked(boolean matched, int timeoutMs) { 490 mPendingLockCheck = null; 491 if (matched && isInternalActivity() && mReturnCredentials) { 492 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, 493 StorageManager.CRYPT_TYPE_PATTERN); 494 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, 495 LockPatternUtils.patternToString(pattern)); 496 } 497 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 498 localEffectiveUserId); 499 } 500 }); 501 } 502 }; 503 504 private void onPatternChecked(boolean matched, Intent intent, int timeoutMs, 505 int effectiveUserId, boolean newResult) { 506 mLockPatternView.setEnabled(true); 507 if (matched) { 508 if (newResult) { 509 reportSuccessfulAttempt(); 510 } 511 startDisappearAnimation(intent); 512 checkForPendingIntent(); 513 } else { 514 if (timeoutMs > 0) { 515 refreshLockScreen(); 516 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 517 effectiveUserId, timeoutMs); 518 handleAttemptLockout(deadline); 519 } else { 520 updateStage(Stage.NeedToUnlockWrong); 521 postClearPatternRunnable(); 522 } 523 if (newResult) { 524 reportFailedAttempt(); 525 } 526 } 527 } 528 529 @Override 530 public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs, 531 int effectiveUserId, boolean newResult) { 532 onPatternChecked(matched, intent, timeoutMs, effectiveUserId, newResult); 533 } 534 535 @Override 536 protected int getLastTryErrorMessage(int userType) { 537 switch (userType) { 538 case USER_TYPE_PRIMARY: 539 return R.string.lock_last_pattern_attempt_before_wipe_device; 540 case USER_TYPE_MANAGED_PROFILE: 541 return R.string.lock_last_pattern_attempt_before_wipe_profile; 542 case USER_TYPE_SECONDARY: 543 return R.string.lock_last_pattern_attempt_before_wipe_user; 544 default: 545 throw new IllegalArgumentException("Unrecognized user type:" + userType); 546 } 547 } 548 549 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 550 updateStage(Stage.LockedOut); 551 long elapsedRealtime = SystemClock.elapsedRealtime(); 552 mCountdownTimer = new CountDownTimer( 553 elapsedRealtimeDeadline - elapsedRealtime, 554 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) { 555 556 @Override 557 public void onTick(long millisUntilFinished) { 558 final int secondsCountdown = (int) (millisUntilFinished / 1000); 559 mErrorTextView.setText(getString( 560 R.string.lockpattern_too_many_failed_confirmation_attempts, 561 secondsCountdown)); 562 } 563 564 @Override 565 public void onFinish() { 566 updateStage(Stage.NeedToUnlock); 567 } 568 }.start(); 569 } 570 571 @Override 572 public void createAnimation(Object obj, long delay, 573 long duration, float translationY, final boolean appearing, 574 Interpolator interpolator, 575 final Runnable finishListener) { 576 if (obj instanceof LockPatternView.CellState) { 577 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj; 578 mLockPatternView.startCellStateAnimation(animatedCell, 579 1f, appearing ? 1f : 0f, /* alpha */ 580 appearing ? translationY : 0f, /* startTranslation */ 581 appearing ? 0f : translationY, /* endTranslation */ 582 appearing ? 0f : 1f, 1f /* scale */, 583 delay, duration, interpolator, finishListener); 584 } else { 585 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY, 586 appearing, interpolator, finishListener); 587 } 588 } 589 } 590 } 591