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