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