• 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 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
getIntent()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
isValidFragment(String fragmentName)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
ConfirmLockPatternFragment()103         public ConfirmLockPatternFragment() {
104 
105         }
106 
107         @Override
onCreate(Bundle savedInstanceState)108         public void onCreate(Bundle savedInstanceState) {
109             super.onCreate(savedInstanceState);
110         }
111 
112         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onSaveInstanceState(Bundle outState)192         public void onSaveInstanceState(Bundle outState) {
193             // deliberately not calling super since we are managing this in full
194         }
195 
196         @Override
onPause()197         public void onPause() {
198             super.onPause();
199 
200             if (mCountdownTimer != null) {
201                 mCountdownTimer.cancel();
202             }
203             mCredentialCheckResultTracker.setListener(null);
204         }
205 
206         @Override
getMetricsCategory()207         public int getMetricsCategory() {
208             return MetricsEvent.CONFIRM_LOCK_PATTERN;
209         }
210 
211         @Override
onResume()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
onShowError()229         protected void onShowError() {
230         }
231 
232         @Override
prepareEnterAnimation()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 
getDefaultDetails()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 
getActiveViews()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
startEnterAnimation()287         public void startEnterAnimation() {
288             super.startEnterAnimation();
289             mLockPatternView.setAlpha(1f);
290             mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
291         }
292 
updateStage(Stage stage)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 
getDefaultHeader()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
postClearPatternRunnable()348         private void postClearPatternRunnable() {
349             mLockPatternView.removeCallbacks(mClearPatternRunnable);
350             mLockPatternView.postDelayed(mClearPatternRunnable, CLEAR_WRONG_ATTEMPT_TIMEOUT_MS);
351         }
352 
353         @Override
authenticationSucceeded()354         protected void authenticationSucceeded() {
355             mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
356         }
357 
startDisappearAnimation(final Intent intent)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
onFingerprintIconVisibilityChanged(boolean visible)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 
onPatternChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)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
onCredentialChecked(boolean matched, Intent intent, int timeoutMs, int effectiveUserId, boolean newResult)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
getLastTryErrorMessage(int userType)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 
handleAttemptLockout(long elapsedRealtimeDeadline)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
createAnimation(Object obj, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)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