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 static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER; 20 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE; 21 import static android.app.admin.DevicePolicyResources.UNDEFINED; 22 23 import static com.android.settings.biometrics.GatekeeperPasswordProvider.containsGatekeeperPasswordHandle; 24 import static com.android.settings.biometrics.GatekeeperPasswordProvider.getGatekeeperPasswordHandle; 25 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE; 26 27 import android.annotation.SuppressLint; 28 import android.app.Activity; 29 import android.app.KeyguardManager; 30 import android.app.RemoteLockscreenValidationResult; 31 import android.app.settings.SettingsEnums; 32 import android.content.Intent; 33 import android.os.AsyncTask; 34 import android.os.Bundle; 35 import android.os.CountDownTimer; 36 import android.os.SystemClock; 37 import android.os.UserManager; 38 import android.text.TextUtils; 39 import android.util.Log; 40 import android.view.LayoutInflater; 41 import android.view.MotionEvent; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.view.animation.AnimationUtils; 45 import android.view.animation.Interpolator; 46 import android.widget.TextView; 47 48 import androidx.annotation.Nullable; 49 50 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient; 51 import com.android.internal.widget.LockPatternChecker; 52 import com.android.internal.widget.LockPatternUtils; 53 import com.android.internal.widget.LockPatternView; 54 import com.android.internal.widget.LockPatternView.Cell; 55 import com.android.internal.widget.LockscreenCredential; 56 import com.android.settings.R; 57 import com.android.settingslib.animation.AppearAnimationCreator; 58 import com.android.settingslib.animation.AppearAnimationUtils; 59 import com.android.settingslib.animation.DisappearAnimationUtils; 60 61 import java.util.ArrayList; 62 import java.util.Collections; 63 import java.util.List; 64 65 /** 66 * Launch this when you want the user to confirm their lock pattern. 67 * 68 * Sets an activity result of {@link Activity#RESULT_OK} when the user 69 * successfully confirmed their pattern. 70 */ 71 public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { 72 73 public static class InternalActivity extends ConfirmLockPattern { 74 } 75 76 private enum Stage { 77 NeedToUnlock, 78 NeedToUnlockWrong, 79 LockedOut 80 } 81 82 @Override getIntent()83 public Intent getIntent() { 84 Intent modIntent = new Intent(super.getIntent()); 85 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName()); 86 return modIntent; 87 } 88 89 @Override isValidFragment(String fragmentName)90 protected boolean isValidFragment(String fragmentName) { 91 if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true; 92 return false; 93 } 94 95 public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment 96 implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener, 97 SaveAndFinishWorker.Listener, RemoteLockscreenValidationFragment.Listener { 98 99 private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result"; 100 101 private LockPatternView mLockPatternView; 102 private AsyncTask<?, ?, ?> mPendingLockCheck; 103 private CredentialCheckResultTracker mCredentialCheckResultTracker; 104 private boolean mDisappearing = false; 105 private CountDownTimer mCountdownTimer; 106 107 private View mSudContent; 108 109 // caller-supplied text for various prompts 110 private CharSequence mHeaderText; 111 private CharSequence mDetailsText; 112 private CharSequence mCheckBoxLabel; 113 114 private AppearAnimationUtils mAppearAnimationUtils; 115 private DisappearAnimationUtils mDisappearAnimationUtils; 116 117 private boolean mIsManagedProfile; 118 119 // required constructor for fragments ConfirmLockPatternFragment()120 public ConfirmLockPatternFragment() { 121 122 } 123 124 @SuppressLint("ClickableViewAccessibility") 125 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)126 public View onCreateView(LayoutInflater inflater, ViewGroup container, 127 Bundle savedInstanceState) { 128 ConfirmLockPattern activity = (ConfirmLockPattern) getActivity(); 129 View view = inflater.inflate( 130 activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.NORMAL 131 ? R.layout.confirm_lock_pattern_normal 132 : R.layout.confirm_lock_pattern, 133 container, 134 false); 135 mGlifLayout = view.findViewById(R.id.setup_wizard_layout); 136 mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern); 137 mErrorTextView = (TextView) view.findViewById(R.id.errorText); 138 // TODO(b/243008023) Workaround for Glif layout on 2 panel choose lock settings. 139 mSudContent = mGlifLayout.findViewById( 140 com.google.android.setupdesign.R.id.sud_layout_content); 141 mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(), 142 0); 143 mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId); 144 145 // make it so unhandled touch events within the unlock screen go to the 146 // lock pattern view. 147 final LinearLayoutWithDefaultTouchRecepient topLayout 148 = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout); 149 topLayout.setDefaultTouchRecepient(mLockPatternView); 150 151 Intent intent = getActivity().getIntent(); 152 if (intent != null) { 153 mHeaderText = intent.getCharSequenceExtra( 154 ConfirmDeviceCredentialBaseFragment.HEADER_TEXT); 155 mDetailsText = intent.getCharSequenceExtra( 156 ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT); 157 mCheckBoxLabel = intent.getCharSequenceExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL); 158 } 159 if (TextUtils.isEmpty(mHeaderText) && mIsManagedProfile) { 160 mHeaderText = mDevicePolicyManager.getOrganizationNameForUser(mUserId); 161 } 162 163 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( 164 mEffectiveUserId)); 165 mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener); 166 mLockPatternView.setOnTouchListener((v, event) -> { 167 if (event.getAction() == MotionEvent.ACTION_DOWN) { 168 v.getParent().requestDisallowInterceptTouchEvent(true); 169 } 170 return false; 171 }); 172 updateStage(Stage.NeedToUnlock); 173 174 if (savedInstanceState == null) { 175 // on first launch, if no lock pattern is set, then finish with 176 // success (don't want user to get stuck confirming something that 177 // doesn't exist). 178 // Don't do this check for FRP though, because the pattern is not stored 179 // in a way that isLockPatternEnabled is aware of for that case. 180 // TODO(roosa): This block should no longer be needed since we removed the 181 // ability to disable the pattern in L. Remove this block after 182 // ensuring it's safe to do so. (Note that ConfirmLockPassword 183 // doesn't have this). 184 if (!mFrp && !mRemoteValidation && !mRepairMode 185 && !mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) { 186 getActivity().setResult(Activity.RESULT_OK); 187 getActivity().finish(); 188 } 189 } 190 mAppearAnimationUtils = new AppearAnimationUtils(getContext(), 191 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */, 192 1.3f /* delayScale */, AnimationUtils.loadInterpolator( 193 getContext(), android.R.interpolator.linear_out_slow_in)); 194 mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(), 195 125, 4f /* translationScale */, 196 0.3f /* delayScale */, AnimationUtils.loadInterpolator( 197 getContext(), android.R.interpolator.fast_out_linear_in), 198 new AppearAnimationUtils.RowTranslationScaler() { 199 @Override 200 public float getRowTranslationScale(int row, int numRows) { 201 return (float)(numRows - row) / numRows; 202 } 203 }); 204 setAccessibilityTitle(mGlifLayout.getHeaderText()); 205 206 mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager() 207 .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT); 208 if (mCredentialCheckResultTracker == null) { 209 mCredentialCheckResultTracker = new CredentialCheckResultTracker(); 210 getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker, 211 FRAGMENT_TAG_CHECK_LOCK_RESULT).commit(); 212 } 213 214 if (mRemoteValidation) { 215 // ProgressBar visibility is set to GONE until interacted with. 216 // Set progress bar to INVISIBLE, so the pattern does not get bumped down later. 217 mGlifLayout.setProgressBarShown(false); 218 // Lock pattern is generally not visible until the user has set a lockscreen for the 219 // first time. For a new user, this means that the pattern will always be hidden. 220 // Despite this prerequisite, we want to show the pattern anyway for this flow. 221 mLockPatternView.setInStealthMode(false); 222 } 223 224 return view; 225 } 226 227 @Override onViewCreated(View view, @Nullable Bundle savedInstanceState)228 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 229 super.onViewCreated(view, savedInstanceState); 230 if (mRemoteValidation) { 231 if (mCheckBox != null) { 232 mCheckBox.setText(TextUtils.isEmpty(mCheckBoxLabel) 233 ? getDefaultCheckboxLabel() 234 : mCheckBoxLabel); 235 } 236 if (mCancelButton != null && TextUtils.isEmpty(mAlternateButtonText)) { 237 mCancelButton.setText(R.string.lockpassword_forgot_pattern); 238 } 239 updateRemoteLockscreenValidationViews(); 240 } 241 242 if (mForgotButton != null) { 243 mForgotButton.setText(R.string.lockpassword_forgot_pattern); 244 } 245 } 246 247 @Override onSaveInstanceState(Bundle outState)248 public void onSaveInstanceState(Bundle outState) { 249 // deliberately not calling super since we are managing this in full 250 } 251 252 @Override onPause()253 public void onPause() { 254 super.onPause(); 255 256 if (mCountdownTimer != null) { 257 mCountdownTimer.cancel(); 258 } 259 mCredentialCheckResultTracker.setListener(null); 260 if (mRemoteLockscreenValidationFragment != null) { 261 mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null); 262 } 263 } 264 265 @Override getMetricsCategory()266 public int getMetricsCategory() { 267 return SettingsEnums.CONFIRM_LOCK_PATTERN; 268 } 269 270 @Override onResume()271 public void onResume() { 272 super.onResume(); 273 274 // if the user is currently locked out, enforce it. 275 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId); 276 if (deadline != 0) { 277 mCredentialCheckResultTracker.clearResult(); 278 handleAttemptLockout(deadline); 279 } else if (!mLockPatternView.isEnabled()) { 280 // The deadline has passed, but the timer was cancelled. Or the pending lock 281 // check was cancelled. Need to clean up. 282 updateStage(Stage.NeedToUnlock); 283 } 284 mCredentialCheckResultTracker.setListener(this); 285 if (mRemoteLockscreenValidationFragment != null) { 286 mRemoteLockscreenValidationFragment.setListener(this, mHandler); 287 if (mRemoteLockscreenValidationFragment.isRemoteValidationInProgress()) { 288 mLockPatternView.setEnabled(false); 289 } 290 } 291 } 292 293 @Override onShowError()294 protected void onShowError() { 295 } 296 297 @Override prepareEnterAnimation()298 public void prepareEnterAnimation() { 299 super.prepareEnterAnimation(); 300 mGlifLayout.getHeaderTextView().setAlpha(0f); 301 mCancelButton.setAlpha(0f); 302 if (mForgotButton != null) { 303 mForgotButton.setAlpha(0f); 304 } 305 mLockPatternView.setAlpha(0f); 306 mGlifLayout.getDescriptionTextView().setAlpha(0f); 307 } 308 getDefaultDetails()309 private String getDefaultDetails() { 310 if (mFrp) { 311 return getString(R.string.lockpassword_confirm_your_pattern_details_frp); 312 } 313 if (mRepairMode) { 314 return getString(R.string.lockpassword_confirm_repair_mode_pattern_details); 315 } 316 if (mRemoteValidation) { 317 return getString( 318 R.string.lockpassword_remote_validation_pattern_details); 319 } 320 final boolean isStrongAuthRequired = isStrongAuthRequired(); 321 return isStrongAuthRequired 322 ? getString(R.string.lockpassword_strong_auth_required_device_pattern) 323 : getString(R.string.lockpassword_confirm_your_pattern_generic); 324 } 325 getActiveViews()326 private Object[][] getActiveViews() { 327 ArrayList<ArrayList<Object>> result = new ArrayList<>(); 328 result.add(new ArrayList<>(Collections.singletonList(mGlifLayout.getHeaderTextView()))); 329 result.add(new ArrayList<>( 330 Collections.singletonList(mGlifLayout.getDescriptionTextView()))); 331 if (mCancelButton.getVisibility() == View.VISIBLE) { 332 result.add(new ArrayList<>(Collections.singletonList(mCancelButton))); 333 } 334 if (mForgotButton != null) { 335 result.add(new ArrayList<>(Collections.singletonList(mForgotButton))); 336 } 337 LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates(); 338 for (int i = 0; i < cellStates.length; i++) { 339 ArrayList<Object> row = new ArrayList<>(); 340 for (int j = 0; j < cellStates[i].length; j++) { 341 row.add(cellStates[i][j]); 342 } 343 result.add(row); 344 } 345 Object[][] resultArr = new Object[result.size()][cellStates[0].length]; 346 for (int i = 0; i < result.size(); i++) { 347 ArrayList<Object> row = result.get(i); 348 for (int j = 0; j < row.size(); j++) { 349 resultArr[i][j] = row.get(j); 350 } 351 } 352 return resultArr; 353 } 354 355 @Override startEnterAnimation()356 public void startEnterAnimation() { 357 super.startEnterAnimation(); 358 mLockPatternView.setAlpha(1f); 359 mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this); 360 } 361 updateStage(Stage stage)362 private void updateStage(Stage stage) { 363 switch (stage) { 364 case NeedToUnlock: 365 if (mHeaderText != null) { 366 mGlifLayout.setHeaderText(mHeaderText); 367 } else { 368 mGlifLayout.setHeaderText(getDefaultHeader()); 369 } 370 371 CharSequence detailsText = 372 mDetailsText == null ? getDefaultDetails() : mDetailsText; 373 374 if (mIsManagedProfile) { 375 mGlifLayout.getDescriptionTextView().setVisibility(View.GONE); 376 } else { 377 mGlifLayout.setDescriptionText(detailsText); 378 } 379 380 mErrorTextView.setText(""); 381 updateErrorMessage( 382 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); 383 384 mLockPatternView.setEnabled(true); 385 mLockPatternView.enableInput(); 386 mLockPatternView.clearPattern(); 387 break; 388 case NeedToUnlockWrong: 389 showError(R.string.lockpattern_need_to_unlock_wrong, 390 CLEAR_WRONG_ATTEMPT_TIMEOUT_MS); 391 392 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 393 mLockPatternView.setEnabled(true); 394 mLockPatternView.enableInput(); 395 break; 396 case LockedOut: 397 mLockPatternView.clearPattern(); 398 // enabled = false means: disable input, and have the 399 // appearance of being disabled. 400 mLockPatternView.setEnabled(false); // appearance of being disabled 401 break; 402 } 403 404 // Always announce the header for accessibility. This is a no-op 405 // when accessibility is disabled. 406 mGlifLayout.getHeaderTextView().announceForAccessibility(mGlifLayout.getHeaderText()); 407 } 408 getDefaultHeader()409 private String getDefaultHeader() { 410 if (mFrp) { 411 return getString(R.string.lockpassword_confirm_your_pattern_header_frp); 412 } 413 if (mRepairMode) { 414 return getString(R.string.lockpassword_confirm_repair_mode_pattern_header); 415 } 416 if (mRemoteValidation) { 417 return getString(R.string.lockpassword_remote_validation_header); 418 } 419 if (mIsManagedProfile) { 420 return mDevicePolicyManager.getResources().getString( 421 CONFIRM_WORK_PROFILE_PATTERN_HEADER, 422 () -> getString(R.string.lockpassword_confirm_your_work_pattern_header)); 423 } 424 425 return getString(R.string.lockpassword_confirm_your_pattern_header); 426 } 427 getDefaultCheckboxLabel()428 private String getDefaultCheckboxLabel() { 429 if (mRemoteValidation) { 430 return getString(R.string.lockpassword_remote_validation_set_pattern_as_screenlock); 431 } 432 throw new IllegalStateException( 433 "Trying to get default checkbox label for illegal flow"); 434 } 435 436 private Runnable mClearPatternRunnable = new Runnable() { 437 public void run() { 438 mLockPatternView.clearPattern(); 439 } 440 }; 441 442 // clear the wrong pattern unless they have started a new one 443 // already postClearPatternRunnable()444 private void postClearPatternRunnable() { 445 mLockPatternView.removeCallbacks(mClearPatternRunnable); 446 mLockPatternView.postDelayed(mClearPatternRunnable, CLEAR_WRONG_ATTEMPT_TIMEOUT_MS); 447 } 448 449 @Override authenticationSucceeded()450 protected void authenticationSucceeded() { 451 mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId); 452 } 453 startDisappearAnimation(final Intent intent)454 private void startDisappearAnimation(final Intent intent) { 455 if (mDisappearing) { 456 return; 457 } 458 mDisappearing = true; 459 460 final ConfirmLockPattern activity = (ConfirmLockPattern) getActivity(); 461 // Bail if there is no active activity. 462 if (activity == null || activity.isFinishing()) { 463 return; 464 } 465 if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) { 466 mLockPatternView.clearPattern(); 467 mDisappearAnimationUtils.startAnimation2d(getActiveViews(), 468 () -> { 469 activity.setResult(RESULT_OK, intent); 470 activity.finish(); 471 activity.overridePendingTransition( 472 R.anim.confirm_credential_close_enter, 473 R.anim.confirm_credential_close_exit); 474 }, this); 475 } else { 476 activity.setResult(RESULT_OK, intent); 477 activity.finish(); 478 } 479 } 480 481 /** 482 * The pattern listener that responds according to a user confirming 483 * an existing lock pattern. 484 */ 485 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener 486 = new LockPatternView.OnPatternListener() { 487 488 public void onPatternStart() { 489 mLockPatternView.removeCallbacks(mClearPatternRunnable); 490 } 491 492 public void onPatternCleared() { 493 mLockPatternView.removeCallbacks(mClearPatternRunnable); 494 } 495 496 public void onPatternCellAdded(List<Cell> pattern) { 497 498 } 499 500 public void onPatternDetected(List<LockPatternView.Cell> pattern) { 501 if (mPendingLockCheck != null || mDisappearing) { 502 return; 503 } 504 505 mLockPatternView.setEnabled(false); 506 507 final LockscreenCredential credential = LockscreenCredential.createPattern(pattern); 508 509 if (mRemoteValidation) { 510 validateGuess(credential); 511 updateRemoteLockscreenValidationViews(); 512 return; 513 } 514 515 // TODO(b/161956762): Sanitize this 516 Intent intent = new Intent(); 517 if (mReturnGatekeeperPassword) { 518 if (isInternalActivity()) { 519 startVerifyPattern(credential, intent, 520 LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE); 521 return; 522 } 523 } else if (mForceVerifyPath) { 524 if (isInternalActivity()) { 525 final int flags = mRequestWriteRepairModePassword 526 ? LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW : 0; 527 startVerifyPattern(credential, intent, flags); 528 return; 529 } 530 } else { 531 startCheckPattern(credential, intent); 532 return; 533 } 534 535 mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId); 536 } 537 538 private boolean isInternalActivity() { 539 return getActivity() instanceof ConfirmLockPattern.InternalActivity; 540 } 541 542 private void startVerifyPattern(final LockscreenCredential pattern, 543 final Intent intent, @LockPatternUtils.VerifyFlag int flags) { 544 final int localEffectiveUserId = mEffectiveUserId; 545 final int localUserId = mUserId; 546 final LockPatternChecker.OnVerifyCallback onVerifyCallback = 547 (response, timeoutMs) -> { 548 mPendingLockCheck = null; 549 final boolean matched = response.isMatched(); 550 if (matched && mReturnCredentials) { 551 if ((flags & LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0) { 552 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 553 response.getGatekeeperPasswordHandle()); 554 } else { 555 intent.putExtra( 556 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 557 response.getGatekeeperHAT()); 558 } 559 } 560 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 561 localEffectiveUserId); 562 }; 563 mPendingLockCheck = (localEffectiveUserId == localUserId) 564 ? LockPatternChecker.verifyCredential( 565 mLockPatternUtils, pattern, localUserId, flags, 566 onVerifyCallback) 567 : LockPatternChecker.verifyTiedProfileChallenge( 568 mLockPatternUtils, pattern, localUserId, flags, 569 onVerifyCallback); 570 } 571 572 private void startCheckPattern(final LockscreenCredential pattern, 573 final Intent intent) { 574 if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { 575 // Pattern size is less than the minimum, do not count it as an fail attempt. 576 onPatternChecked(false, intent, 0, mEffectiveUserId, false /* newResult */); 577 return; 578 } 579 580 final int localEffectiveUserId = mEffectiveUserId; 581 mPendingLockCheck = LockPatternChecker.checkCredential( 582 mLockPatternUtils, 583 pattern, 584 localEffectiveUserId, 585 new LockPatternChecker.OnCheckCallback() { 586 @Override 587 public void onChecked(boolean matched, int timeoutMs) { 588 mPendingLockCheck = null; 589 if (matched && isInternalActivity() && mReturnCredentials) { 590 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, 591 pattern); 592 } 593 mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, 594 localEffectiveUserId); 595 } 596 }); 597 } 598 }; 599 onPatternChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)600 private void onPatternChecked(boolean matched, Intent intent, int timeoutMs, 601 int effectiveUserId, boolean newResult) { 602 mLockPatternView.setEnabled(true); 603 if (matched) { 604 if (newResult) { 605 ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils, 606 mUserManager, mDevicePolicyManager, mEffectiveUserId, 607 /* isStrongAuth */ true); 608 } 609 startDisappearAnimation(intent); 610 ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity()); 611 } else { 612 if (timeoutMs > 0) { 613 refreshLockScreen(); 614 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 615 effectiveUserId, timeoutMs); 616 handleAttemptLockout(deadline); 617 } else { 618 updateStage(Stage.NeedToUnlockWrong); 619 postClearPatternRunnable(); 620 } 621 if (newResult) { 622 reportFailedAttempt(); 623 } 624 } 625 } 626 627 @Override onRemoteLockscreenValidationResult( RemoteLockscreenValidationResult result)628 public void onRemoteLockscreenValidationResult( 629 RemoteLockscreenValidationResult result) { 630 switch (result.getResultCode()) { 631 case RemoteLockscreenValidationResult.RESULT_GUESS_VALID: 632 if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment 633 .getLockscreenCredential() != null) { 634 Log.i(TAG, "Setting device screen lock to the other device's screen lock."); 635 SaveAndFinishWorker saveAndFinishWorker = new SaveAndFinishWorker(); 636 getFragmentManager().beginTransaction().add(saveAndFinishWorker, null) 637 .commit(); 638 getFragmentManager().executePendingTransactions(); 639 saveAndFinishWorker 640 .setListener(this) 641 .setRequestGatekeeperPasswordHandle(true); 642 saveAndFinishWorker.start( 643 mLockPatternUtils, 644 mRemoteLockscreenValidationFragment.getLockscreenCredential(), 645 /* currentCredential= */ null, 646 mEffectiveUserId); 647 } else { 648 mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(), 649 /* timeoutMs= */ 0, mEffectiveUserId); 650 } 651 return; 652 case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID: 653 mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(), 654 /* timeoutMs= */ 0, mEffectiveUserId); 655 break; 656 case RemoteLockscreenValidationResult.RESULT_LOCKOUT: 657 mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(), 658 (int) result.getTimeoutMillis(), mEffectiveUserId); 659 break; 660 case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS: 661 case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED: 662 onRemoteLockscreenValidationFailure(String.format( 663 "Cannot continue remote lockscreen validation. ResultCode=%d", 664 result.getResultCode())); 665 break; 666 } 667 updateRemoteLockscreenValidationViews(); 668 mRemoteLockscreenValidationFragment.clearLockscreenCredential(); 669 } 670 671 @Override onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)672 public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs, 673 int effectiveUserId, boolean newResult) { 674 onPatternChecked(matched, intent, timeoutMs, effectiveUserId, newResult); 675 } 676 677 @Override getLastTryOverrideErrorMessageId(int userType)678 protected String getLastTryOverrideErrorMessageId(int userType) { 679 if (userType == USER_TYPE_MANAGED_PROFILE) { 680 return WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE; 681 } 682 683 return UNDEFINED; 684 } 685 686 @Override getLastTryDefaultErrorMessage(int userType)687 protected int getLastTryDefaultErrorMessage(int userType) { 688 switch (userType) { 689 case USER_TYPE_PRIMARY: 690 return R.string.lock_last_pattern_attempt_before_wipe_device; 691 case USER_TYPE_MANAGED_PROFILE: 692 return R.string.lock_last_pattern_attempt_before_wipe_profile; 693 case USER_TYPE_SECONDARY: 694 return R.string.lock_last_pattern_attempt_before_wipe_user; 695 default: 696 throw new IllegalArgumentException("Unrecognized user type:" + userType); 697 } 698 } 699 handleAttemptLockout(long elapsedRealtimeDeadline)700 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 701 clearResetErrorRunnable(); 702 updateStage(Stage.LockedOut); 703 long elapsedRealtime = SystemClock.elapsedRealtime(); 704 mCountdownTimer = new CountDownTimer( 705 elapsedRealtimeDeadline - elapsedRealtime, 706 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) { 707 708 @Override 709 public void onTick(long millisUntilFinished) { 710 final int secondsCountdown = (int) (millisUntilFinished / 1000); 711 mErrorTextView.setText(getString( 712 R.string.lockpattern_too_many_failed_confirmation_attempts, 713 secondsCountdown)); 714 } 715 716 @Override 717 public void onFinish() { 718 updateStage(Stage.NeedToUnlock); 719 } 720 }.start(); 721 } 722 723 @Override createAnimation(Object obj, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)724 public void createAnimation(Object obj, long delay, 725 long duration, float translationY, final boolean appearing, 726 Interpolator interpolator, 727 final Runnable finishListener) { 728 if (obj instanceof LockPatternView.CellState) { 729 final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj; 730 mLockPatternView.startCellStateAnimation(animatedCell, 731 1f, appearing ? 1f : 0f, /* alpha */ 732 appearing ? translationY : 0f, /* startTranslation */ 733 appearing ? 0f : translationY, /* endTranslation */ 734 appearing ? 0f : 1f, 1f /* scale */, 735 delay, duration, interpolator, finishListener); 736 } else { 737 mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY, 738 appearing, interpolator, finishListener); 739 } 740 } 741 742 /** 743 * Callback for when the current device's lockscreen to the guess used for 744 * remote lockscreen validation. 745 */ 746 @Override onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)747 public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { 748 Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen."); 749 mRemoteLockscreenValidationFragment.clearLockscreenCredential(); 750 751 Intent result = new Intent(); 752 if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) { 753 result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData)); 754 } 755 mCredentialCheckResultTracker.setResult(/* matched= */ true, result, 756 /* timeoutMs= */ 0, mEffectiveUserId); 757 } 758 } 759 } 760