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.app.settings.SettingsEnums; 21 import android.content.Intent; 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; 34 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.sud_layout_description); 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 SettingsEnums.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 } 235 getDefaultDetails()236 private int getDefaultDetails() { 237 if (mFrp) { 238 return R.string.lockpassword_confirm_your_pattern_details_frp; 239 } 240 final boolean isStrongAuthRequired = isStrongAuthRequired(); 241 if (UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId)) { 242 return isStrongAuthRequired 243 ? R.string.lockpassword_strong_auth_required_work_pattern 244 : R.string.lockpassword_confirm_your_pattern_generic_profile; 245 } else { 246 return isStrongAuthRequired 247 ? R.string.lockpassword_strong_auth_required_device_pattern 248 : R.string.lockpassword_confirm_your_pattern_generic; 249 } 250 } 251 getActiveViews()252 private Object[][] getActiveViews() { 253 ArrayList<ArrayList<Object>> result = new ArrayList<>(); 254 result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView))); 255 result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView))); 256 if (mCancelButton.getVisibility() == View.VISIBLE) { 257 result.add(new ArrayList<Object>(Collections.singletonList(mCancelButton))); 258 } 259 LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates(); 260 for (int i = 0; i < cellStates.length; i++) { 261 ArrayList<Object> row = new ArrayList<>(); 262 for (int j = 0; j < cellStates[i].length; j++) { 263 row.add(cellStates[i][j]); 264 } 265 result.add(row); 266 } 267 Object[][] resultArr = new Object[result.size()][cellStates[0].length]; 268 for (int i = 0; i < result.size(); i++) { 269 ArrayList<Object> row = result.get(i); 270 for (int j = 0; j < row.size(); j++) { 271 resultArr[i][j] = row.get(j); 272 } 273 } 274 return resultArr; 275 } 276 277 @Override startEnterAnimation()278 public void startEnterAnimation() { 279 super.startEnterAnimation(); 280 mLockPatternView.setAlpha(1f); 281 mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this); 282 } 283 updateStage(Stage stage)284 private void updateStage(Stage stage) { 285 switch (stage) { 286 case NeedToUnlock: 287 if (mHeaderText != null) { 288 mHeaderTextView.setText(mHeaderText); 289 } else { 290 mHeaderTextView.setText(getDefaultHeader()); 291 } 292 if (mDetailsText != null) { 293 mDetailsTextView.setText(mDetailsText); 294 } else { 295 mDetailsTextView.setText(getDefaultDetails()); 296 } 297 mErrorTextView.setText(""); 298 updateErrorMessage( 299 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); 300 301 mLockPatternView.setEnabled(true); 302 mLockPatternView.enableInput(); 303 mLockPatternView.clearPattern(); 304 break; 305 case NeedToUnlockWrong: 306 showError(R.string.lockpattern_need_to_unlock_wrong, 307 CLEAR_WRONG_ATTEMPT_TIMEOUT_MS); 308 309 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 310 mLockPatternView.setEnabled(true); 311 mLockPatternView.enableInput(); 312 break; 313 case LockedOut: 314 mLockPatternView.clearPattern(); 315 // enabled = false means: disable input, and have the 316 // appearance of being disabled. 317 mLockPatternView.setEnabled(false); // appearance of being disabled 318 break; 319 } 320 321 // Always announce the header for accessibility. This is a no-op 322 // when accessibility is disabled. 323 mHeaderTextView.announceForAccessibility(mHeaderTextView.getText()); 324 } 325 getDefaultHeader()326 private int getDefaultHeader() { 327 return mFrp ? R.string.lockpassword_confirm_your_pattern_header_frp 328 : R.string.lockpassword_confirm_your_pattern_header; 329 } 330 331 private Runnable mClearPatternRunnable = new Runnable() { 332 public void run() { 333 mLockPatternView.clearPattern(); 334 } 335 }; 336 337 // clear the wrong pattern unless they have started a new one 338 // already postClearPatternRunnable()339 private void postClearPatternRunnable() { 340 mLockPatternView.removeCallbacks(mClearPatternRunnable); 341 mLockPatternView.postDelayed(mClearPatternRunnable, CLEAR_WRONG_ATTEMPT_TIMEOUT_MS); 342 } 343 344 @Override authenticationSucceeded()345 protected void authenticationSucceeded() { 346 mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId); 347 } 348 startDisappearAnimation(final Intent intent)349 private void startDisappearAnimation(final Intent intent) { 350 if (mDisappearing) { 351 return; 352 } 353 mDisappearing = true; 354 355 final ConfirmLockPattern activity = (ConfirmLockPattern) getActivity(); 356 // Bail if there is no active activity. 357 if (activity == null || activity.isFinishing()) { 358 return; 359 } 360 if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) { 361 mLockPatternView.clearPattern(); 362 mDisappearAnimationUtils.startAnimation2d(getActiveViews(), 363 () -> { 364 activity.setResult(RESULT_OK, intent); 365 activity.finish(); 366 activity.overridePendingTransition( 367 R.anim.confirm_credential_close_enter, 368 R.anim.confirm_credential_close_exit); 369 }, this); 370 } else { 371 activity.setResult(RESULT_OK, intent); 372 activity.finish(); 373 } 374 } 375 376 /** 377 * The pattern listener that responds according to a user confirming 378 * an existing lock pattern. 379 */ 380 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener 381 = new LockPatternView.OnPatternListener() { 382 383 public void onPatternStart() { 384 mLockPatternView.removeCallbacks(mClearPatternRunnable); 385 } 386 387 public void onPatternCleared() { 388 mLockPatternView.removeCallbacks(mClearPatternRunnable); 389 } 390 391 public void onPatternCellAdded(List<Cell> pattern) { 392 393 } 394 395 public void onPatternDetected(List<LockPatternView.Cell> pattern) { 396 if (mPendingLockCheck != null || mDisappearing) { 397 return; 398 } 399 400 mLockPatternView.setEnabled(false); 401 402 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra( 403 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 404 Intent intent = new Intent(); 405 if (verifyChallenge) { 406 if (isInternalActivity()) { 407 startVerifyPattern(pattern, intent); 408 return; 409 } 410 } else { 411 startCheckPattern(pattern, intent); 412 return; 413 } 414 415 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId); 416 } 417 418 private boolean isInternalActivity() { 419 return getActivity() instanceof ConfirmLockPattern.InternalActivity; 420 } 421 422 private void startVerifyPattern(final List<LockPatternView.Cell> pattern, 423 final Intent intent) { 424 final int localEffectiveUserId = mEffectiveUserId; 425 final int localUserId = mUserId; 426 long challenge = getActivity().getIntent().getLongExtra( 427 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 428 final LockPatternChecker.OnVerifyCallback onVerifyCallback = 429 new LockPatternChecker.OnVerifyCallback() { 430 @Override 431 public void onVerified(byte[] token, int timeoutMs) { 432 mPendingLockCheck = null; 433 boolean matched = false; 434 if (token != null) { 435 matched = true; 436 if (mReturnCredentials) { 437 intent.putExtra( 438 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 439 token); 440 } 441 } 442 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 443 localEffectiveUserId); 444 } 445 }; 446 mPendingLockCheck = (localEffectiveUserId == localUserId) 447 ? LockPatternChecker.verifyPattern( 448 mLockPatternUtils, pattern, challenge, localUserId, 449 onVerifyCallback) 450 : LockPatternChecker.verifyTiedProfileChallenge( 451 mLockPatternUtils, LockPatternUtils.patternToByteArray(pattern), 452 true, challenge, localUserId, onVerifyCallback); 453 } 454 455 private void startCheckPattern(final List<LockPatternView.Cell> pattern, 456 final Intent intent) { 457 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { 458 // Pattern size is less than the minimum, do not count it as an fail attempt. 459 onPatternChecked(false, intent, 0, mEffectiveUserId, false /* newResult */); 460 return; 461 } 462 463 final int localEffectiveUserId = mEffectiveUserId; 464 mPendingLockCheck = LockPatternChecker.checkPattern( 465 mLockPatternUtils, 466 pattern, 467 localEffectiveUserId, 468 new LockPatternChecker.OnCheckCallback() { 469 @Override 470 public void onChecked(boolean matched, int timeoutMs) { 471 mPendingLockCheck = null; 472 if (matched && isInternalActivity() && mReturnCredentials) { 473 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, 474 StorageManager.CRYPT_TYPE_PATTERN); 475 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, 476 LockPatternUtils.patternToByteArray(pattern)); 477 } 478 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 479 localEffectiveUserId); 480 } 481 }); 482 } 483 }; 484 onPatternChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)485 private void onPatternChecked(boolean matched, Intent intent, int timeoutMs, 486 int effectiveUserId, boolean newResult) { 487 mLockPatternView.setEnabled(true); 488 if (matched) { 489 if (newResult) { 490 ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils, 491 mUserManager, mEffectiveUserId); 492 } 493 mBiometricManager.onConfirmDeviceCredentialSuccess(); 494 startDisappearAnimation(intent); 495 ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity()); 496 } else { 497 if (timeoutMs > 0) { 498 refreshLockScreen(); 499 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 500 effectiveUserId, timeoutMs); 501 handleAttemptLockout(deadline); 502 } else { 503 updateStage(Stage.NeedToUnlockWrong); 504 postClearPatternRunnable(); 505 } 506 if (newResult) { 507 reportFailedAttempt(); 508 } 509 } 510 } 511 512 @Override onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)513 public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs, 514 int effectiveUserId, boolean newResult) { 515 onPatternChecked(matched, intent, timeoutMs, effectiveUserId, newResult); 516 } 517 518 @Override getLastTryErrorMessage(int userType)519 protected int getLastTryErrorMessage(int userType) { 520 switch (userType) { 521 case USER_TYPE_PRIMARY: 522 return R.string.lock_last_pattern_attempt_before_wipe_device; 523 case USER_TYPE_MANAGED_PROFILE: 524 return R.string.lock_last_pattern_attempt_before_wipe_profile; 525 case USER_TYPE_SECONDARY: 526 return R.string.lock_last_pattern_attempt_before_wipe_user; 527 default: 528 throw new IllegalArgumentException("Unrecognized user type:" + userType); 529 } 530 } 531 handleAttemptLockout(long elapsedRealtimeDeadline)532 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 533 updateStage(Stage.LockedOut); 534 long elapsedRealtime = SystemClock.elapsedRealtime(); 535 mCountdownTimer = new CountDownTimer( 536 elapsedRealtimeDeadline - elapsedRealtime, 537 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) { 538 539 @Override 540 public void onTick(long millisUntilFinished) { 541 final int secondsCountdown = (int) (millisUntilFinished / 1000); 542 mErrorTextView.setText(getString( 543 R.string.lockpattern_too_many_failed_confirmation_attempts, 544 secondsCountdown)); 545 } 546 547 @Override 548 public void onFinish() { 549 updateStage(Stage.NeedToUnlock); 550 } 551 }.start(); 552 } 553 554 @Override createAnimation(Object obj, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)555 public void createAnimation(Object obj, long delay, 556 long duration, float translationY, final boolean appearing, 557 Interpolator interpolator, 558 final Runnable finishListener) { 559 if (obj instanceof LockPatternView.CellState) { 560 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj; 561 mLockPatternView.startCellStateAnimation(animatedCell, 562 1f, appearing ? 1f : 0f, /* alpha */ 563 appearing ? translationY : 0f, /* startTranslation */ 564 appearing ? 0f : translationY, /* endTranslation */ 565 appearing ? 0f : 1f, 1f /* scale */, 566 delay, duration, interpolator, finishListener); 567 } else { 568 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY, 569 appearing, interpolator, finishListener); 570 } 571 } 572 } 573 } 574