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