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