• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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