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 getIntent()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 isValidFragment(String fragmentName)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 ConfirmLockPatternFragment()103 public ConfirmLockPatternFragment() { 104 105 } 106 107 @Override onCreate(Bundle savedInstanceState)108 public void onCreate(Bundle savedInstanceState) { 109 super.onCreate(savedInstanceState); 110 } 111 112 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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 onSaveInstanceState(Bundle outState)192 public void onSaveInstanceState(Bundle outState) { 193 // deliberately not calling super since we are managing this in full 194 } 195 196 @Override onPause()197 public void onPause() { 198 super.onPause(); 199 200 if (mCountdownTimer != null) { 201 mCountdownTimer.cancel(); 202 } 203 mCredentialCheckResultTracker.setListener(null); 204 } 205 206 @Override getMetricsCategory()207 public int getMetricsCategory() { 208 return MetricsEvent.CONFIRM_LOCK_PATTERN; 209 } 210 211 @Override onResume()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 onShowError()229 protected void onShowError() { 230 } 231 232 @Override prepareEnterAnimation()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 getDefaultDetails()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 getActiveViews()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 startEnterAnimation()287 public void startEnterAnimation() { 288 super.startEnterAnimation(); 289 mLockPatternView.setAlpha(1f); 290 mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this); 291 } 292 updateStage(Stage stage)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 getDefaultHeader()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 postClearPatternRunnable()348 private void postClearPatternRunnable() { 349 mLockPatternView.removeCallbacks(mClearPatternRunnable); 350 mLockPatternView.postDelayed(mClearPatternRunnable, CLEAR_WRONG_ATTEMPT_TIMEOUT_MS); 351 } 352 353 @Override authenticationSucceeded()354 protected void authenticationSucceeded() { 355 mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId); 356 } 357 startDisappearAnimation(final Intent intent)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 onFingerprintIconVisibilityChanged(boolean visible)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 onPatternChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)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 onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)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 getLastTryErrorMessage(int userType)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 handleAttemptLockout(long elapsedRealtimeDeadline)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 createAnimation(Object obj, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)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