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