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