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