1 /* 2 * Copyright (C) 2020 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.keyguard; 18 19 import static android.app.StatusBarManager.SESSION_KEYGUARD; 20 21 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC; 22 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS; 23 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY; 24 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD; 25 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM; 26 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY; 27 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER; 28 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE; 29 import static com.android.systemui.DejankUtils.whitelistIpcs; 30 31 import android.app.ActivityManager; 32 import android.app.admin.DevicePolicyManager; 33 import android.content.Intent; 34 import android.content.res.ColorStateList; 35 import android.content.res.Configuration; 36 import android.content.res.Resources; 37 import android.hardware.biometrics.BiometricOverlayConstants; 38 import android.media.AudioManager; 39 import android.metrics.LogMaker; 40 import android.os.SystemClock; 41 import android.os.UserHandle; 42 import android.telephony.TelephonyManager; 43 import android.util.Log; 44 import android.util.MathUtils; 45 import android.util.Slog; 46 import android.view.KeyEvent; 47 import android.view.MotionEvent; 48 import android.view.View; 49 import android.view.ViewTreeObserver; 50 import android.widget.FrameLayout; 51 import android.window.OnBackAnimationCallback; 52 53 import androidx.annotation.NonNull; 54 import androidx.annotation.Nullable; 55 56 import com.android.internal.annotations.VisibleForTesting; 57 import com.android.internal.logging.InstanceId; 58 import com.android.internal.logging.MetricsLogger; 59 import com.android.internal.logging.UiEventLogger; 60 import com.android.internal.logging.nano.MetricsProto; 61 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 62 import com.android.internal.widget.LockPatternUtils; 63 import com.android.keyguard.KeyguardSecurityContainer.BouncerUiEvent; 64 import com.android.keyguard.KeyguardSecurityContainer.SwipeListener; 65 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 66 import com.android.keyguard.dagger.KeyguardBouncerScope; 67 import com.android.settingslib.utils.ThreadUtils; 68 import com.android.systemui.Gefingerpoken; 69 import com.android.systemui.R; 70 import com.android.systemui.biometrics.SideFpsController; 71 import com.android.systemui.biometrics.SideFpsUiRequestSource; 72 import com.android.systemui.classifier.FalsingA11yDelegate; 73 import com.android.systemui.classifier.FalsingCollector; 74 import com.android.systemui.flags.FeatureFlags; 75 import com.android.systemui.flags.Flags; 76 import com.android.systemui.log.SessionTracker; 77 import com.android.systemui.plugins.ActivityStarter; 78 import com.android.systemui.plugins.FalsingManager; 79 import com.android.systemui.shared.system.SysUiStatsLog; 80 import com.android.systemui.statusbar.policy.ConfigurationController; 81 import com.android.systemui.statusbar.policy.KeyguardStateController; 82 import com.android.systemui.statusbar.policy.UserSwitcherController; 83 import com.android.systemui.util.ViewController; 84 import com.android.systemui.util.settings.GlobalSettings; 85 86 import java.io.File; 87 import java.util.Optional; 88 89 import javax.inject.Inject; 90 91 /** Controller for {@link KeyguardSecurityContainer} */ 92 @KeyguardBouncerScope 93 public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer> 94 implements KeyguardSecurityView { 95 96 private static final boolean DEBUG = KeyguardConstants.DEBUG; 97 private static final String TAG = "KeyguardSecurityView"; 98 99 private final AdminSecondaryLockScreenController mAdminSecondaryLockScreenController; 100 private final LockPatternUtils mLockPatternUtils; 101 private final KeyguardUpdateMonitor mUpdateMonitor; 102 private final KeyguardSecurityModel mSecurityModel; 103 private final MetricsLogger mMetricsLogger; 104 private final UiEventLogger mUiEventLogger; 105 private final KeyguardStateController mKeyguardStateController; 106 private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController; 107 private final ConfigurationController mConfigurationController; 108 private final FalsingCollector mFalsingCollector; 109 private final FalsingManager mFalsingManager; 110 private final UserSwitcherController mUserSwitcherController; 111 private final GlobalSettings mGlobalSettings; 112 private final FeatureFlags mFeatureFlags; 113 private final SessionTracker mSessionTracker; 114 private final Optional<SideFpsController> mSideFpsController; 115 private final FalsingA11yDelegate mFalsingA11yDelegate; 116 private int mTranslationY; 117 // Whether the volume keys should be handled by keyguard. If true, then 118 // they will be handled here for specific media types such as music, otherwise 119 // the audio service will bring up the volume dialog. 120 private static final boolean KEYGUARD_MANAGES_VOLUME = false; 121 122 private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key"; 123 124 private final TelephonyManager mTelephonyManager; 125 private final ViewMediatorCallback mViewMediatorCallback; 126 private final AudioManager mAudioManager; 127 private View.OnKeyListener mOnKeyListener = (v, keyCode, event) -> interceptMediaKey(event); 128 private ActivityStarter.OnDismissAction mDismissAction; 129 private Runnable mCancelAction; 130 private boolean mWillRunDismissFromKeyguard; 131 132 private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; 133 134 private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid; 135 private UserSwitcherController.UserSwitchCallback mUserSwitchCallback = 136 () -> showPrimarySecurityScreen(false); 137 138 @VisibleForTesting 139 final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() { 140 private MotionEvent mTouchDown; 141 @Override 142 public boolean onInterceptTouchEvent(MotionEvent ev) { 143 return false; 144 } 145 146 @Override 147 public boolean onTouchEvent(MotionEvent ev) { 148 // Do just a bit of our own falsing. People should only be tapping on the input, not 149 // swiping. 150 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 151 // If we're in one handed mode, the user can tap on the opposite side of the screen 152 // to move the bouncer across. In that case, inhibit the falsing (otherwise the taps 153 // to move the bouncer to each screen side can end up closing it instead). 154 if (mView.isTouchOnTheOtherSideOfSecurity(ev)) { 155 mFalsingCollector.avoidGesture(); 156 } 157 158 if (mTouchDown != null) { 159 mTouchDown.recycle(); 160 mTouchDown = null; 161 } 162 mTouchDown = MotionEvent.obtain(ev); 163 } else if (mTouchDown != null) { 164 if (ev.getActionMasked() == MotionEvent.ACTION_UP 165 || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { 166 mTouchDown.recycle(); 167 mTouchDown = null; 168 } 169 } 170 return false; 171 } 172 }; 173 174 private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() { 175 176 @Override 177 public void onUserInput() { 178 mUpdateMonitor.cancelFaceAuth(); 179 } 180 181 @Override 182 public void dismiss(boolean authenticated, int targetId, 183 SecurityMode expectedSecurityMode) { 184 dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false, 185 expectedSecurityMode); 186 } 187 188 @Override 189 public boolean dismiss(boolean authenticated, int targetId, 190 boolean bypassSecondaryLockScreen, SecurityMode expectedSecurityMode) { 191 return showNextSecurityScreenOrFinish( 192 authenticated, targetId, bypassSecondaryLockScreen, expectedSecurityMode); 193 } 194 195 @Override 196 public void userActivity() { 197 mViewMediatorCallback.userActivity(); 198 } 199 200 @Override 201 public boolean isVerifyUnlockOnly() { 202 return false; 203 } 204 205 @Override 206 public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { 207 int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT; 208 if (mView.isSidedSecurityMode()) { 209 bouncerSide = mView.isSecurityLeftAligned() 210 ? SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__LEFT 211 : SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__RIGHT; 212 } 213 214 if (success) { 215 SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, 216 SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS, 217 bouncerSide); 218 mLockPatternUtils.reportSuccessfulPasswordAttempt(userId); 219 // Force a garbage collection in an attempt to erase any lockscreen password left in 220 // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard 221 // dismiss animation janky. 222 ThreadUtils.postOnBackgroundThread(() -> { 223 try { 224 Thread.sleep(5000); 225 } catch (InterruptedException ignored) { } 226 System.gc(); 227 System.runFinalization(); 228 System.gc(); 229 }); 230 } else { 231 SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, 232 SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE, 233 bouncerSide); 234 reportFailedUnlockAttempt(userId, timeoutMs); 235 } 236 mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) 237 .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE)); 238 mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS 239 : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId()); 240 } 241 242 @Override 243 public void reset() { 244 mViewMediatorCallback.resetKeyguard(); 245 } 246 247 @Override 248 public void onCancelClicked() { 249 mViewMediatorCallback.onCancelClicked(); 250 } 251 252 /** 253 * Authentication has happened and it's time to dismiss keyguard. This function 254 * should clean up and inform KeyguardViewMediator. 255 * 256 * @param strongAuth whether the user has authenticated with strong authentication like 257 * pattern, password or PIN but not by trust agents or fingerprint 258 * @param targetUserId a user that needs to be the foreground user at the dismissal 259 * completion. 260 */ 261 @Override 262 public void finish(boolean strongAuth, int targetUserId) { 263 if (mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD) 264 && !mKeyguardStateController.canDismissLockScreen() && !strongAuth) { 265 Log.e(TAG, 266 "Tried to dismiss keyguard when lockscreen is not dismissible and user " 267 + "was not authenticated with a primary security method " 268 + "(pin/password/pattern)."); 269 return; 270 } 271 // If there's a pending runnable because the user interacted with a widget 272 // and we're leaving keyguard, then run it. 273 boolean deferKeyguardDone = false; 274 mWillRunDismissFromKeyguard = false; 275 if (mDismissAction != null) { 276 deferKeyguardDone = mDismissAction.onDismiss(); 277 mWillRunDismissFromKeyguard = mDismissAction.willRunAnimationOnKeyguard(); 278 mDismissAction = null; 279 mCancelAction = null; 280 } 281 if (mViewMediatorCallback != null) { 282 if (deferKeyguardDone) { 283 mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId); 284 } else { 285 mViewMediatorCallback.keyguardDone(strongAuth, targetUserId); 286 } 287 } 288 } 289 290 @Override 291 public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) { 292 mViewMediatorCallback.setNeedsInput(needsInput); 293 } 294 }; 295 296 297 private final SwipeListener mSwipeListener = new SwipeListener() { 298 @Override 299 public void onSwipeUp() { 300 if (!mUpdateMonitor.isFaceDetectionRunning()) { 301 boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth( 302 FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER); 303 mKeyguardSecurityCallback.userActivity(); 304 if (didFaceAuthRun) { 305 showMessage(null, null); 306 } 307 } 308 if (mUpdateMonitor.isFaceEnrolled()) { 309 mUpdateMonitor.requestActiveUnlock( 310 ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT, 311 "swipeUpOnBouncer"); 312 } 313 } 314 }; 315 private final ConfigurationController.ConfigurationListener mConfigurationListener = 316 new ConfigurationController.ConfigurationListener() { 317 @Override 318 public void onThemeChanged() { 319 reloadColors(); 320 } 321 322 @Override 323 public void onUiModeChanged() { 324 reloadColors(); 325 } 326 327 @Override 328 public void onDensityOrFontScaleChanged() { 329 KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged(); 330 } 331 }; 332 private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = 333 new KeyguardUpdateMonitorCallback() { 334 @Override 335 public void onTrustGrantedForCurrentUser( 336 boolean dismissKeyguard, 337 boolean newlyUnlocked, 338 TrustGrantFlags flags, 339 String message 340 ) { 341 if (dismissKeyguard) { 342 if (!mView.isVisibleToUser()) { 343 // The trust agent dismissed the keyguard without the user proving 344 // that they are present (by swiping up to show the bouncer). That's 345 // fine if the user proved presence via some other way to the trust 346 // agent. 347 Log.i(TAG, "TrustAgent dismissed Keyguard."); 348 } 349 mKeyguardSecurityCallback.dismiss( 350 false /* authenticated */, 351 KeyguardUpdateMonitor.getCurrentUser(), 352 /* bypassSecondaryLockScreen */ false, 353 SecurityMode.Invalid 354 ); 355 } else { 356 if (flags.isInitiatedByUser() || flags.dismissKeyguardRequested()) { 357 mViewMediatorCallback.playTrustedSound(); 358 } 359 } 360 } 361 362 @Override 363 public void onDevicePolicyManagerStateChanged() { 364 showPrimarySecurityScreen(false); 365 } 366 }; 367 368 @Inject KeyguardSecurityContainerController(KeyguardSecurityContainer view, AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory, LockPatternUtils lockPatternUtils, KeyguardUpdateMonitor keyguardUpdateMonitor, KeyguardSecurityModel keyguardSecurityModel, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, KeyguardStateController keyguardStateController, KeyguardSecurityViewFlipperController securityViewFlipperController, ConfigurationController configurationController, FalsingCollector falsingCollector, FalsingManager falsingManager, UserSwitcherController userSwitcherController, FeatureFlags featureFlags, GlobalSettings globalSettings, SessionTracker sessionTracker, Optional<SideFpsController> sideFpsController, FalsingA11yDelegate falsingA11yDelegate, TelephonyManager telephonyManager, ViewMediatorCallback viewMediatorCallback, AudioManager audioManager )369 public KeyguardSecurityContainerController(KeyguardSecurityContainer view, 370 AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory, 371 LockPatternUtils lockPatternUtils, 372 KeyguardUpdateMonitor keyguardUpdateMonitor, 373 KeyguardSecurityModel keyguardSecurityModel, 374 MetricsLogger metricsLogger, 375 UiEventLogger uiEventLogger, 376 KeyguardStateController keyguardStateController, 377 KeyguardSecurityViewFlipperController securityViewFlipperController, 378 ConfigurationController configurationController, 379 FalsingCollector falsingCollector, 380 FalsingManager falsingManager, 381 UserSwitcherController userSwitcherController, 382 FeatureFlags featureFlags, 383 GlobalSettings globalSettings, 384 SessionTracker sessionTracker, 385 Optional<SideFpsController> sideFpsController, 386 FalsingA11yDelegate falsingA11yDelegate, 387 TelephonyManager telephonyManager, 388 ViewMediatorCallback viewMediatorCallback, 389 AudioManager audioManager 390 ) { 391 super(view); 392 mLockPatternUtils = lockPatternUtils; 393 mUpdateMonitor = keyguardUpdateMonitor; 394 mSecurityModel = keyguardSecurityModel; 395 mMetricsLogger = metricsLogger; 396 mUiEventLogger = uiEventLogger; 397 mKeyguardStateController = keyguardStateController; 398 mSecurityViewFlipperController = securityViewFlipperController; 399 mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create( 400 mKeyguardSecurityCallback); 401 mConfigurationController = configurationController; 402 mLastOrientation = getResources().getConfiguration().orientation; 403 mFalsingCollector = falsingCollector; 404 mFalsingManager = falsingManager; 405 mUserSwitcherController = userSwitcherController; 406 mFeatureFlags = featureFlags; 407 mGlobalSettings = globalSettings; 408 mSessionTracker = sessionTracker; 409 mSideFpsController = sideFpsController; 410 mFalsingA11yDelegate = falsingA11yDelegate; 411 mTelephonyManager = telephonyManager; 412 mViewMediatorCallback = viewMediatorCallback; 413 mAudioManager = audioManager; 414 } 415 416 @Override onInit()417 public void onInit() { 418 mSecurityViewFlipperController.init(); 419 updateResources(); 420 configureMode(); 421 } 422 423 @Override onViewAttached()424 protected void onViewAttached() { 425 mUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); 426 mView.setSwipeListener(mSwipeListener); 427 mView.addMotionEventListener(mGlobalTouchListener); 428 mConfigurationController.addCallback(mConfigurationListener); 429 mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback); 430 mView.setViewMediatorCallback(mViewMediatorCallback); 431 // Update ViewMediator with the current input method requirements 432 mViewMediatorCallback.setNeedsInput(needsInput()); 433 mView.setOnKeyListener(mOnKeyListener); 434 showPrimarySecurityScreen(false); 435 } 436 437 @Override onViewDetached()438 protected void onViewDetached() { 439 mUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); 440 mConfigurationController.removeCallback(mConfigurationListener); 441 mView.removeMotionEventListener(mGlobalTouchListener); 442 mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback); 443 } 444 445 /** */ onPause()446 public void onPause() { 447 if (DEBUG) { 448 Log.d(TAG, String.format("screen off, instance %s at %s", 449 Integer.toHexString(hashCode()), SystemClock.uptimeMillis())); 450 } 451 showPrimarySecurityScreen(true); 452 mAdminSecondaryLockScreenController.hide(); 453 if (mCurrentSecurityMode != SecurityMode.None) { 454 getCurrentSecurityController().onPause(); 455 } 456 mView.onPause(); 457 mView.clearFocus(); 458 } 459 460 /** 461 * Shows and hides the side finger print sensor animation. 462 * 463 * @param isVisible sets whether we show or hide the side fps animation 464 */ updateSideFpsVisibility(boolean isVisible)465 public void updateSideFpsVisibility(boolean isVisible) { 466 if (!mSideFpsController.isPresent()) { 467 return; 468 } 469 470 if (isVisible) { 471 mSideFpsController.get().show( 472 SideFpsUiRequestSource.PRIMARY_BOUNCER, 473 BiometricOverlayConstants.REASON_AUTH_KEYGUARD 474 ); 475 } else { 476 mSideFpsController.get().hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); 477 } 478 } 479 480 /** 481 * Shows the primary security screen for the user. This will be either the multi-selector 482 * or the user's security method. 483 * @param turningOff true if the device is being turned off 484 */ showPrimarySecurityScreen(boolean turningOff)485 public void showPrimarySecurityScreen(boolean turningOff) { 486 if (DEBUG) Log.d(TAG, "show()"); 487 SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode( 488 KeyguardUpdateMonitor.getCurrentUser())); 489 if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); 490 showSecurityScreen(securityMode); 491 } 492 493 /** 494 * Show a string explaining why the security view needs to be solved. 495 * 496 * @param reason a flag indicating which string should be shown, see 497 * {@link KeyguardSecurityView#PROMPT_REASON_NONE}, 498 * {@link KeyguardSecurityView#PROMPT_REASON_RESTART}, 499 * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}, and 500 * {@link KeyguardSecurityView#PROMPT_REASON_PREPARE_FOR_UPDATE}. 501 */ 502 @Override showPromptReason(int reason)503 public void showPromptReason(int reason) { 504 if (mCurrentSecurityMode != SecurityMode.None) { 505 if (reason != PROMPT_REASON_NONE) { 506 Log.i(TAG, "Strong auth required, reason: " + reason); 507 } 508 getCurrentSecurityController().showPromptReason(reason); 509 } 510 } 511 showMessage(CharSequence message, ColorStateList colorState)512 public void showMessage(CharSequence message, ColorStateList colorState) { 513 if (mCurrentSecurityMode != SecurityMode.None) { 514 getCurrentSecurityController().showMessage(message, colorState); 515 } 516 } 517 518 /** 519 * Sets an action to run when keyguard finishes. 520 * 521 * @param action callback to be invoked when keyguard disappear animation completes. 522 */ setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction)523 public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) { 524 if (mCancelAction != null) { 525 mCancelAction.run(); 526 mCancelAction = null; 527 } 528 mDismissAction = action; 529 mCancelAction = cancelAction; 530 } 531 532 /** 533 * @return whether dismiss action or cancel action has been set. 534 */ hasDismissActions()535 public boolean hasDismissActions() { 536 return mDismissAction != null || mCancelAction != null; 537 } 538 539 /** 540 * @return will the dismissal run from the keyguard layout (instead of from bouncer) 541 */ willRunDismissFromKeyguard()542 public boolean willRunDismissFromKeyguard() { 543 return mWillRunDismissFromKeyguard; 544 } 545 546 /** 547 * Remove any dismiss action or cancel action that was set. 548 */ cancelDismissAction()549 public void cancelDismissAction() { 550 setOnDismissAction(null, null); 551 } 552 553 /** 554 * Potentially dismiss the current security screen, after validating that all device 555 * security has been unlocked. Otherwise show the next screen. 556 */ dismiss(boolean authenticated, int targetUserId, SecurityMode expectedSecurityMode)557 public void dismiss(boolean authenticated, int targetUserId, 558 SecurityMode expectedSecurityMode) { 559 mKeyguardSecurityCallback.dismiss(authenticated, targetUserId, expectedSecurityMode); 560 } 561 562 /** 563 * Dismisses the keyguard by going to the next screen or making it gone. 564 * @param targetUserId a user that needs to be the foreground user at the dismissal completion. 565 * @return True if the keyguard is done. 566 */ dismiss(int targetUserId)567 public boolean dismiss(int targetUserId) { 568 return mKeyguardSecurityCallback.dismiss(false, targetUserId, false, 569 getCurrentSecurityMode()); 570 } 571 getCurrentSecurityMode()572 public SecurityMode getCurrentSecurityMode() { 573 return mCurrentSecurityMode; 574 } 575 576 /** 577 * @return the top of the corresponding view. 578 */ getTop()579 public int getTop() { 580 int top = mView.getTop(); 581 // The password view has an extra top padding that should be ignored. 582 if (getCurrentSecurityMode() == SecurityMode.Password) { 583 View messageArea = mView.findViewById(R.id.keyguard_message_area); 584 top += messageArea.getTop(); 585 } 586 return top; 587 } 588 589 /** Set true if the view can be interacted with */ setInteractable(boolean isInteractable)590 public void setInteractable(boolean isInteractable) { 591 mView.setInteractable(isInteractable); 592 } 593 594 /** 595 * Dismiss keyguard due to a user unlock event. 596 */ finish(boolean strongAuth, int currentUser)597 public void finish(boolean strongAuth, int currentUser) { 598 mKeyguardSecurityCallback.finish(strongAuth, currentUser); 599 } 600 601 /** 602 * @return the text of the KeyguardMessageArea. 603 */ getTitle()604 public CharSequence getTitle() { 605 return mView.getTitle(); 606 } 607 608 /** 609 * Resets the state of the views. 610 */ reset()611 public void reset() { 612 mView.reset(); 613 mSecurityViewFlipperController.reset(); 614 } 615 616 @Override onResume(int reason)617 public void onResume(int reason) { 618 if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode())); 619 mView.requestFocus(); 620 if (mCurrentSecurityMode != SecurityMode.None) { 621 int state = SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN; 622 if (mView.isSidedSecurityMode()) { 623 state = mView.isSecurityLeftAligned() 624 ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_LEFT 625 : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_RIGHT; 626 } 627 SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, state); 628 629 getCurrentSecurityController().onResume(reason); 630 } 631 mView.onResume( 632 mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()), 633 mKeyguardStateController.isFaceAuthEnabled()); 634 } 635 636 /** 637 * Show the bouncer and start appear animations. 638 * 639 */ appear()640 public void appear() { 641 // We might still be collapsed and the view didn't have time to layout yet or still 642 // be small, let's wait on the predraw to do the animation in that case. 643 mView.getViewTreeObserver().addOnPreDrawListener( 644 new ViewTreeObserver.OnPreDrawListener() { 645 @Override 646 public boolean onPreDraw() { 647 mView.getViewTreeObserver().removeOnPreDrawListener(this); 648 startAppearAnimation(); 649 return true; 650 } 651 }); 652 mView.requestLayout(); 653 } 654 startAppearAnimation()655 public void startAppearAnimation() { 656 if (mCurrentSecurityMode != SecurityMode.None) { 657 setAlpha(1f); 658 mView.startAppearAnimation(mCurrentSecurityMode); 659 getCurrentSecurityController().startAppearAnimation(); 660 } 661 } 662 663 /** Set the alpha of the security container view */ setAlpha(float alpha)664 public void setAlpha(float alpha) { 665 mView.setAlpha(alpha); 666 } 667 startDisappearAnimation(Runnable onFinishRunnable)668 public boolean startDisappearAnimation(Runnable onFinishRunnable) { 669 boolean didRunAnimation = false; 670 671 if (mCurrentSecurityMode != SecurityMode.None) { 672 mView.startDisappearAnimation(mCurrentSecurityMode); 673 didRunAnimation = getCurrentSecurityController().startDisappearAnimation( 674 onFinishRunnable); 675 } 676 677 if (!didRunAnimation && onFinishRunnable != null) { 678 onFinishRunnable.run(); 679 } 680 681 return didRunAnimation; 682 } 683 onStartingToHide()684 public void onStartingToHide() { 685 if (mCurrentSecurityMode != SecurityMode.None) { 686 getCurrentSecurityController().onStartingToHide(); 687 } 688 } 689 690 /** Called when the bouncer changes visibility. */ onBouncerVisibilityChanged(boolean isVisible)691 public void onBouncerVisibilityChanged(boolean isVisible) { 692 if (!isVisible) { 693 mView.resetScale(); 694 } 695 } 696 697 /** 698 * Shows the next security screen if there is one. 699 * @param authenticated true if the user entered the correct authentication 700 * @param targetUserId a user that needs to be the foreground user at the finish (if called) 701 * completion. 702 * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary 703 * secondary lock screen requirement, if any. 704 * @param expectedSecurityMode SecurityMode that is invoking this request. SecurityMode.Invalid 705 * indicates that no check should be done 706 * @return true if keyguard is done 707 */ showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen, SecurityMode expectedSecurityMode)708 public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId, 709 boolean bypassSecondaryLockScreen, SecurityMode expectedSecurityMode) { 710 711 if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); 712 if (expectedSecurityMode != SecurityMode.Invalid 713 && expectedSecurityMode != getCurrentSecurityMode()) { 714 Log.w(TAG, "Attempted to invoke showNextSecurityScreenOrFinish with securityMode " 715 + expectedSecurityMode + ", but current mode is " + getCurrentSecurityMode()); 716 return false; 717 } 718 719 boolean finish = false; 720 boolean strongAuth = false; 721 int eventSubtype = -1; 722 BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN; 723 if (mUpdateMonitor.getUserHasTrust(targetUserId)) { 724 finish = true; 725 eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS; 726 uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS; 727 } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) { 728 finish = true; 729 eventSubtype = BOUNCER_DISMISS_BIOMETRIC; 730 uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC; 731 } else if (SecurityMode.None == getCurrentSecurityMode()) { 732 SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); 733 if (SecurityMode.None == securityMode) { 734 finish = true; // no security required 735 eventSubtype = BOUNCER_DISMISS_NONE_SECURITY; 736 uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY; 737 } else { 738 showSecurityScreen(securityMode); // switch to the alternate security view 739 } 740 } else if (authenticated) { 741 switch (getCurrentSecurityMode()) { 742 case Pattern: 743 case Password: 744 case PIN: 745 strongAuth = true; 746 finish = true; 747 eventSubtype = BOUNCER_DISMISS_PASSWORD; 748 uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD; 749 break; 750 751 case SimPin: 752 case SimPuk: 753 // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home 754 SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); 755 if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled( 756 KeyguardUpdateMonitor.getCurrentUser())) { 757 finish = true; 758 eventSubtype = BOUNCER_DISMISS_SIM; 759 uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; 760 } else { 761 showSecurityScreen(securityMode); 762 } 763 break; 764 765 default: 766 Log.v(TAG, "Bad security screen " + getCurrentSecurityMode() 767 + ", fail safe"); 768 showPrimarySecurityScreen(false); 769 break; 770 } 771 } 772 // Check for device admin specified additional security measures. 773 if (finish && !bypassSecondaryLockScreen) { 774 Intent secondaryLockscreenIntent = 775 mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId); 776 if (secondaryLockscreenIntent != null) { 777 mAdminSecondaryLockScreenController.show(secondaryLockscreenIntent); 778 return false; 779 } 780 } 781 if (eventSubtype != -1) { 782 mMetricsLogger.write(new LogMaker(MetricsProto.MetricsEvent.BOUNCER) 783 .setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype)); 784 } 785 if (uiEvent != BouncerUiEvent.UNKNOWN) { 786 mUiEventLogger.log(uiEvent, getSessionId()); 787 } 788 if (finish) { 789 mKeyguardSecurityCallback.finish(strongAuth, targetUserId); 790 } 791 return finish; 792 } 793 needsInput()794 public boolean needsInput() { 795 return getCurrentSecurityController().needsInput(); 796 } 797 798 /** 799 * @return the {@link OnBackAnimationCallback} to animate this view during a back gesture. 800 */ 801 @NonNull getBackCallback()802 public OnBackAnimationCallback getBackCallback() { 803 return mView.getBackCallback(); 804 } 805 806 /** 807 * @return whether we should dispatch the back key event before Ime. 808 */ dispatchBackKeyEventPreIme()809 public boolean dispatchBackKeyEventPreIme() { 810 return getCurrentSecurityMode() == SecurityMode.Password; 811 } 812 813 /** 814 * Allows the media keys to work when the keyguard is showing. 815 * The media keys should be of no interest to the actual keyguard view(s), 816 * so intercepting them here should not be of any harm. 817 * @param event The key event 818 * @return whether the event was consumed as a media key. 819 */ interceptMediaKey(KeyEvent event)820 public boolean interceptMediaKey(KeyEvent event) { 821 int keyCode = event.getKeyCode(); 822 if (event.getAction() == KeyEvent.ACTION_DOWN) { 823 switch (keyCode) { 824 case KeyEvent.KEYCODE_MEDIA_PLAY: 825 case KeyEvent.KEYCODE_MEDIA_PAUSE: 826 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 827 /* Suppress PLAY/PAUSE toggle when phone is ringing or 828 * in-call to avoid music playback */ 829 if (mTelephonyManager != null 830 && mTelephonyManager.getCallState() 831 != TelephonyManager.CALL_STATE_IDLE) { 832 return true; // suppress key event 833 } 834 return false; 835 case KeyEvent.KEYCODE_MUTE: 836 case KeyEvent.KEYCODE_HEADSETHOOK: 837 case KeyEvent.KEYCODE_MEDIA_STOP: 838 case KeyEvent.KEYCODE_MEDIA_NEXT: 839 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 840 case KeyEvent.KEYCODE_MEDIA_REWIND: 841 case KeyEvent.KEYCODE_MEDIA_RECORD: 842 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 843 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 844 handleMediaKeyEvent(event); 845 return true; 846 } 847 848 case KeyEvent.KEYCODE_VOLUME_UP: 849 case KeyEvent.KEYCODE_VOLUME_DOWN: 850 case KeyEvent.KEYCODE_VOLUME_MUTE: { 851 if (KEYGUARD_MANAGES_VOLUME) { 852 // Volume buttons should only function for music (local or remote). 853 // TODO: Actually handle MUTE. 854 mAudioManager.adjustSuggestedStreamVolume( 855 keyCode == KeyEvent.KEYCODE_VOLUME_UP 856 ? AudioManager.ADJUST_RAISE 857 : AudioManager.ADJUST_LOWER /* direction */, 858 AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */); 859 // Don't execute default volume behavior 860 return true; 861 } else { 862 return false; 863 } 864 } 865 } 866 } else if (event.getAction() == KeyEvent.ACTION_UP) { 867 switch (keyCode) { 868 case KeyEvent.KEYCODE_MUTE: 869 case KeyEvent.KEYCODE_HEADSETHOOK: 870 case KeyEvent.KEYCODE_MEDIA_PLAY: 871 case KeyEvent.KEYCODE_MEDIA_PAUSE: 872 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 873 case KeyEvent.KEYCODE_MEDIA_STOP: 874 case KeyEvent.KEYCODE_MEDIA_NEXT: 875 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 876 case KeyEvent.KEYCODE_MEDIA_REWIND: 877 case KeyEvent.KEYCODE_MEDIA_RECORD: 878 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 879 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 880 handleMediaKeyEvent(event); 881 return true; 882 } 883 } 884 } 885 return false; 886 } 887 888 handleMediaKeyEvent(KeyEvent keyEvent)889 private void handleMediaKeyEvent(KeyEvent keyEvent) { 890 mAudioManager.dispatchMediaKeyEvent(keyEvent); 891 } 892 893 /** 894 * In general, we enable unlocking the insecure keyguard with the menu key. However, there are 895 * some cases where we wish to disable it, notably when the menu button placement or technology 896 * is prone to false positives. 897 * 898 * @return true if the menu key should be enabled 899 */ shouldEnableMenuKey()900 public boolean shouldEnableMenuKey() { 901 final Resources res = mView.getResources(); 902 final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen); 903 final boolean isTestHarness = ActivityManager.isRunningInTestHarness(); 904 final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists(); 905 return !configDisabled || isTestHarness || fileOverride; 906 } 907 908 909 /** 910 * Switches to the given security view unless it's already being shown, in which case 911 * this is a no-op. 912 * 913 * @param securityMode 914 */ 915 @VisibleForTesting showSecurityScreen(SecurityMode securityMode)916 void showSecurityScreen(SecurityMode securityMode) { 917 if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")"); 918 919 if (securityMode == SecurityMode.Invalid || securityMode == mCurrentSecurityMode) { 920 return; 921 } 922 923 KeyguardInputViewController<KeyguardInputView> oldView = getCurrentSecurityController(); 924 925 // Emulate Activity life cycle 926 if (oldView != null) { 927 oldView.onPause(); 928 } 929 930 KeyguardInputViewController<KeyguardInputView> newView = changeSecurityMode(securityMode); 931 if (newView != null) { 932 newView.onResume(KeyguardSecurityView.VIEW_REVEALED); 933 mSecurityViewFlipperController.show(newView); 934 configureMode(); 935 } 936 937 mKeyguardSecurityCallback.onSecurityModeChanged( 938 securityMode, newView != null && newView.needsInput()); 939 } 940 941 /** 942 * Returns whether the given security view should be used in a "one handed" way. This can be 943 * used to change how the security view is drawn (e.g. take up less of the screen, and align to 944 * one side). 945 */ canUseOneHandedBouncer()946 private boolean canUseOneHandedBouncer() { 947 if (!(mCurrentSecurityMode == SecurityMode.Pattern 948 || mCurrentSecurityMode == SecurityMode.PIN)) { 949 return false; 950 } 951 952 return getResources().getBoolean(R.bool.can_use_one_handed_bouncer); 953 } 954 canDisplayUserSwitcher()955 private boolean canDisplayUserSwitcher() { 956 return mFeatureFlags.isEnabled(Flags.BOUNCER_USER_SWITCHER); 957 } 958 configureMode()959 private void configureMode() { 960 boolean useSimSecurity = mCurrentSecurityMode == SecurityMode.SimPin 961 || mCurrentSecurityMode == SecurityMode.SimPuk; 962 int mode = KeyguardSecurityContainer.MODE_DEFAULT; 963 if (canDisplayUserSwitcher() && !useSimSecurity) { 964 mode = KeyguardSecurityContainer.MODE_USER_SWITCHER; 965 } else if (canUseOneHandedBouncer()) { 966 mode = KeyguardSecurityContainer.MODE_ONE_HANDED; 967 } 968 969 mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController, 970 () -> showMessage(getContext().getString(R.string.keyguard_unlock_to_continue), 971 null), mFalsingA11yDelegate); 972 } 973 reportFailedUnlockAttempt(int userId, int timeoutMs)974 public void reportFailedUnlockAttempt(int userId, int timeoutMs) { 975 // +1 for this time 976 final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1; 977 978 if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); 979 980 final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager(); 981 final int failedAttemptsBeforeWipe = 982 dpm.getMaximumFailedPasswordsForWipe(null, userId); 983 984 final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 985 ? (failedAttemptsBeforeWipe - failedAttempts) 986 : Integer.MAX_VALUE; // because DPM returns 0 if no restriction 987 if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) { 988 // The user has installed a DevicePolicyManager that requests a user/profile to be wiped 989 // N attempts. Once we get below the grace period, we post this dialog every time as a 990 // clear warning until the deletion fires. 991 // Check which profile has the strictest policy for failed password attempts 992 final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId); 993 int userType = USER_TYPE_PRIMARY; 994 if (expiringUser == userId) { 995 // TODO: http://b/23522538 996 if (expiringUser != UserHandle.USER_SYSTEM) { 997 userType = USER_TYPE_SECONDARY_USER; 998 } 999 } else if (expiringUser != UserHandle.USER_NULL) { 1000 userType = USER_TYPE_WORK_PROFILE; 1001 } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY 1002 if (remainingBeforeWipe > 0) { 1003 mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType); 1004 } else { 1005 // Too many attempts. The device will be wiped shortly. 1006 Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!"); 1007 mView.showWipeDialog(failedAttempts, userType); 1008 } 1009 } 1010 mLockPatternUtils.reportFailedPasswordAttempt(userId); 1011 if (timeoutMs > 0) { 1012 mLockPatternUtils.reportPasswordLockout(timeoutMs, userId); 1013 mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils, 1014 mSecurityModel.getSecurityMode(userId)); 1015 } 1016 } 1017 getCurrentSecurityController()1018 private KeyguardInputViewController<KeyguardInputView> getCurrentSecurityController() { 1019 return mSecurityViewFlipperController 1020 .getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback); 1021 } 1022 changeSecurityMode( SecurityMode securityMode)1023 private KeyguardInputViewController<KeyguardInputView> changeSecurityMode( 1024 SecurityMode securityMode) { 1025 mCurrentSecurityMode = securityMode; 1026 return getCurrentSecurityController(); 1027 } 1028 1029 /** 1030 * Apply keyguard configuration from the currently active resources. This can be called when the 1031 * device configuration changes, to re-apply some resources that are qualified on the device 1032 * configuration. 1033 */ updateResources()1034 public void updateResources() { 1035 int gravity; 1036 1037 Resources resources = mView.getResources(); 1038 1039 if (resources.getBoolean(R.bool.can_use_one_handed_bouncer)) { 1040 gravity = resources.getInteger( 1041 R.integer.keyguard_host_view_one_handed_gravity); 1042 } else { 1043 gravity = resources.getInteger(R.integer.keyguard_host_view_gravity); 1044 } 1045 1046 mTranslationY = resources 1047 .getDimensionPixelSize(R.dimen.keyguard_host_view_translation_y); 1048 // Android SysUI uses a FrameLayout as the top-level, but Auto uses RelativeLayout. 1049 // We're just changing the gravity here though (which can't be applied to RelativeLayout), 1050 // so only attempt the update if mView is inside a FrameLayout. 1051 if (mView.getLayoutParams() instanceof FrameLayout.LayoutParams) { 1052 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mView.getLayoutParams(); 1053 if (lp.gravity != gravity) { 1054 lp.gravity = gravity; 1055 mView.setLayoutParams(lp); 1056 } 1057 } 1058 1059 int newOrientation = getResources().getConfiguration().orientation; 1060 if (newOrientation != mLastOrientation) { 1061 mLastOrientation = newOrientation; 1062 configureMode(); 1063 } 1064 } 1065 getSessionId()1066 private @Nullable InstanceId getSessionId() { 1067 return mSessionTracker.getSessionId(SESSION_KEYGUARD); 1068 } 1069 1070 /** Update keyguard position based on a tapped X coordinate. */ updateKeyguardPosition(float x)1071 public void updateKeyguardPosition(float x) { 1072 mView.updatePositionByTouchX(x); 1073 } 1074 reloadColors()1075 private void reloadColors() { 1076 reinflateViewFlipper(() -> mView.reloadColors()); 1077 } 1078 1079 /** Handles density or font scale changes. */ onDensityOrFontScaleChanged()1080 private void onDensityOrFontScaleChanged() { 1081 reinflateViewFlipper(() -> mView.onDensityOrFontScaleChanged()); 1082 } 1083 1084 /** 1085 * Reinflate the view flipper child view. 1086 */ reinflateViewFlipper( KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener)1087 public void reinflateViewFlipper( 1088 KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener) { 1089 mSecurityViewFlipperController.clearViews(); 1090 if (mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)) { 1091 mSecurityViewFlipperController.asynchronouslyInflateView(mCurrentSecurityMode, 1092 mKeyguardSecurityCallback, onViewInflatedListener); 1093 } else { 1094 mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode, 1095 mKeyguardSecurityCallback); 1096 onViewInflatedListener.onViewInflated(); 1097 } 1098 } 1099 1100 /** 1101 * Fades and translates in/out the security screen. 1102 * Fades in as expansion approaches 0. 1103 * Animation duration is between 0.33f and 0.67f of panel expansion fraction. 1104 * @param fraction amount of the screen that should show. 1105 */ setExpansion(float fraction)1106 public void setExpansion(float fraction) { 1107 float scaledFraction = BouncerPanelExpansionCalculator.showBouncerProgress(fraction); 1108 mView.setAlpha(MathUtils.constrain(1 - scaledFraction, 0f, 1f)); 1109 mView.setTranslationY(scaledFraction * mTranslationY); 1110 } 1111 } 1112