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