1 /* 2 * Copyright (C) 2014 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 package com.android.keyguard; 17 18 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE; 19 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE; 20 import static android.view.WindowInsets.Type.ime; 21 import static android.view.WindowInsets.Type.systemBars; 22 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; 23 24 import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM; 25 import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD; 26 import static androidx.constraintlayout.widget.ConstraintSet.END; 27 import static androidx.constraintlayout.widget.ConstraintSet.LEFT; 28 import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT; 29 import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; 30 import static androidx.constraintlayout.widget.ConstraintSet.RIGHT; 31 import static androidx.constraintlayout.widget.ConstraintSet.START; 32 import static androidx.constraintlayout.widget.ConstraintSet.TOP; 33 import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT; 34 35 import static com.android.systemui.Flags.bouncerUiRevamp2; 36 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; 37 38 import static java.lang.Integer.max; 39 40 import android.animation.Animator; 41 import android.animation.AnimatorListenerAdapter; 42 import android.animation.AnimatorSet; 43 import android.animation.ObjectAnimator; 44 import android.animation.ValueAnimator; 45 import android.app.Activity; 46 import android.app.AlertDialog; 47 import android.app.admin.DevicePolicyManager; 48 import android.content.Context; 49 import android.content.res.Configuration; 50 import android.content.res.Resources; 51 import android.graphics.Bitmap; 52 import android.graphics.BlendMode; 53 import android.graphics.Canvas; 54 import android.graphics.Color; 55 import android.graphics.Rect; 56 import android.graphics.Typeface; 57 import android.graphics.drawable.BitmapDrawable; 58 import android.graphics.drawable.Drawable; 59 import android.graphics.drawable.Icon; 60 import android.graphics.drawable.LayerDrawable; 61 import android.os.UserManager; 62 import android.provider.Settings; 63 import android.transition.TransitionManager; 64 import android.util.AttributeSet; 65 import android.util.Log; 66 import android.util.MathUtils; 67 import android.util.TypedValue; 68 import android.view.GestureDetector; 69 import android.view.LayoutInflater; 70 import android.view.MotionEvent; 71 import android.view.VelocityTracker; 72 import android.view.View; 73 import android.view.ViewConfiguration; 74 import android.view.ViewGroup; 75 import android.view.WindowInsets; 76 import android.view.WindowInsetsAnimation; 77 import android.view.WindowManager; 78 import android.widget.FrameLayout; 79 import android.widget.ImageView; 80 import android.widget.TextView; 81 import android.window.BackEvent; 82 import android.window.OnBackAnimationCallback; 83 84 import androidx.annotation.IntDef; 85 import androidx.annotation.NonNull; 86 import androidx.annotation.VisibleForTesting; 87 import androidx.constraintlayout.widget.ConstraintLayout; 88 import androidx.constraintlayout.widget.ConstraintSet; 89 import androidx.dynamicanimation.animation.DynamicAnimation; 90 import androidx.dynamicanimation.animation.SpringAnimation; 91 92 import com.android.app.animation.Interpolators; 93 import com.android.internal.jank.InteractionJankMonitor; 94 import com.android.internal.logging.UiEvent; 95 import com.android.internal.logging.UiEventLogger; 96 import com.android.internal.util.UserIcons; 97 import com.android.internal.widget.LockPatternUtils; 98 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 99 import com.android.settingslib.Utils; 100 import com.android.settingslib.drawable.CircleFramedDrawable; 101 import com.android.systemui.FontStyles; 102 import com.android.systemui.Gefingerpoken; 103 import com.android.systemui.classifier.FalsingA11yDelegate; 104 import com.android.systemui.plugins.FalsingManager; 105 import com.android.systemui.res.R; 106 import com.android.systemui.shade.TouchLogger; 107 import com.android.systemui.shared.system.SysUiStatsLog; 108 import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; 109 import com.android.systemui.statusbar.policy.UserSwitcherController; 110 import com.android.systemui.user.data.source.UserRecord; 111 import com.android.systemui.util.settings.GlobalSettings; 112 113 import java.util.ArrayList; 114 import java.util.List; 115 import java.util.concurrent.Executor; 116 import java.util.function.Consumer; 117 118 /** Determines how the bouncer is displayed to the user. */ 119 public class KeyguardSecurityContainer extends ConstraintLayout { 120 static final int USER_TYPE_PRIMARY = 1; 121 static final int USER_TYPE_WORK_PROFILE = 2; 122 static final int USER_TYPE_SECONDARY_USER = 3; 123 private boolean mTransparentModeEnabled = false; 124 125 @IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER}) 126 public @interface Mode {} 127 static final int MODE_UNINITIALIZED = -1; 128 static final int MODE_DEFAULT = 0; 129 static final int MODE_ONE_HANDED = 1; 130 static final int MODE_USER_SWITCHER = 2; 131 132 // Bouncer is dismissed due to no security. 133 static final int BOUNCER_DISMISS_NONE_SECURITY = 0; 134 // Bouncer is dismissed due to pin, password or pattern entered. 135 static final int BOUNCER_DISMISS_PASSWORD = 1; 136 // Bouncer is dismissed due to biometric (face, fingerprint or iris) authenticated. 137 static final int BOUNCER_DISMISS_BIOMETRIC = 2; 138 // Bouncer is dismissed due to extended access granted. 139 static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3; 140 // Bouncer is dismissed due to sim card unlock code entered. 141 static final int BOUNCER_DISMISS_SIM = 4; 142 // Bouncer dismissed after being allowed to dismiss by forceDismissiblekeyguard 143 static final int BOUNCER_DISMISSIBLE_KEYGUARD = 5; 144 private static final String TAG = "KeyguardSecurityView"; 145 146 // Make the view move slower than the finger, as if the spring were applying force. 147 private static final float TOUCH_Y_MULTIPLIER = 0.25f; 148 // How much you need to drag the bouncer to trigger an auth retry (in dps.) 149 private static final float MIN_DRAG_SIZE = 10; 150 // How much to scale the default slop by, to avoid accidental drags. 151 private static final float SLOP_SCALE = 4f; 152 @VisibleForTesting 153 // How much the view scales down to during back gestures. 154 static final float MIN_BACK_SCALE = 0.9f; 155 @VisibleForTesting 156 KeyguardSecurityViewFlipper mSecurityViewFlipper; 157 private GlobalSettings mGlobalSettings; 158 private FalsingManager mFalsingManager; 159 private UserSwitcherController mUserSwitcherController; 160 private FalsingA11yDelegate mFalsingA11yDelegate; 161 private AlertDialog mAlertDialog; 162 private boolean mSwipeUpToRetry; 163 164 private final ViewConfiguration mViewConfiguration; 165 private final SpringAnimation mSpringAnimation; 166 private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); 167 private final List<Gefingerpoken> mMotionEventListeners = new ArrayList<>(); 168 private final GestureDetector mDoubleTapDetector; 169 170 private float mLastTouchY = -1; 171 private int mActivePointerId = -1; 172 private boolean mIsDragging; 173 private float mStartTouchY = -1; 174 private boolean mDisappearAnimRunning; 175 private boolean mIsAppearAnimationDelayed; 176 private SwipeListener mSwipeListener; 177 private ViewMode mViewMode = new DefaultViewMode(); 178 private boolean mIsInteractable; 179 protected ViewMediatorCallback mViewMediatorCallback; 180 private Executor mBgExecutor; 181 /* 182 * Using MODE_UNINITIALIZED to mean the view mode is set to DefaultViewMode, but init() has not 183 * yet been called on it. This will happen when the ViewController is initialized. 184 */ 185 private @Mode int mCurrentMode = MODE_UNINITIALIZED; 186 private int mWidth = -1; 187 188 /** 189 * This callback is used to animate KeyguardSecurityContainer and its child views based on 190 * the interaction with the ime. After 191 * {@link WindowInsetsAnimation.Callback#onPrepare(WindowInsetsAnimation)}, 192 * {@link #onApplyWindowInsets} is called where we 193 * set the bottom padding to be the height of the keyboard. We use this padding to determine 194 * the delta of vertical distance for y-translation animations. 195 * Note that bottom padding is not set when the disappear animation is started because 196 * we are deferring the y translation logic to the animator in 197 * {@link KeyguardPasswordView#startDisappearAnimation(Runnable)} 198 */ 199 private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = 200 new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { 201 202 private final Rect mInitialBounds = new Rect(); 203 private final Rect mFinalBounds = new Rect(); 204 205 @Override 206 public void onPrepare(WindowInsetsAnimation animation) { 207 mSecurityViewFlipper.getBoundsOnScreen(mInitialBounds); 208 } 209 210 @Override 211 public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation, 212 WindowInsetsAnimation.Bounds bounds) { 213 if (!mDisappearAnimRunning) { 214 beginJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR); 215 } else { 216 beginJankInstrument( 217 InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR); 218 } 219 mSecurityViewFlipper.getBoundsOnScreen(mFinalBounds); 220 return bounds; 221 } 222 223 @Override 224 public WindowInsets onProgress(WindowInsets windowInsets, 225 List<WindowInsetsAnimation> list) { 226 float start = mDisappearAnimRunning 227 ? -(mFinalBounds.bottom - mInitialBounds.bottom) 228 : mInitialBounds.bottom - mFinalBounds.bottom; 229 float end = mDisappearAnimRunning 230 ? -((mFinalBounds.bottom - mInitialBounds.bottom) * 0.75f) 231 : 0f; 232 int translationY = 0; 233 float interpolatedFraction = 1f; 234 for (WindowInsetsAnimation animation : list) { 235 if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) { 236 continue; 237 } 238 interpolatedFraction = animation.getInterpolatedFraction(); 239 final int paddingBottom = (int) MathUtils.lerp( 240 start, end, 241 interpolatedFraction); 242 translationY += paddingBottom; 243 } 244 245 float alpha = mDisappearAnimRunning 246 ? 1 - interpolatedFraction 247 : Math.max(interpolatedFraction, getAlpha()); 248 updateChildren(translationY, alpha); 249 250 return windowInsets; 251 } 252 253 @Override 254 public void onEnd(WindowInsetsAnimation animation) { 255 if (!mDisappearAnimRunning) { 256 endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR); 257 } else { 258 endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR); 259 setAlpha(0f); 260 } 261 updateChildren(0 /* translationY */, 1f /* alpha */); 262 } 263 }; 264 265 private final OnBackAnimationCallback mBackCallback = new OnBackAnimationCallback() { 266 @Override 267 public void onBackCancelled() { 268 // TODO(b/259608500): Remove once back API auto animates progress to 0 on cancel. 269 resetScale(); 270 } 271 272 @Override 273 public void onBackInvoked() { } 274 275 @Override 276 public void onBackProgressed(BackEvent event) { 277 float progress = event.getProgress(); 278 // TODO(b/263819310): Update the interpolator to match spec. 279 float scale = MIN_BACK_SCALE + (1 - MIN_BACK_SCALE) * (1 - progress); 280 setScale(scale); 281 } 282 }; 283 /** 284 * @return the {@link OnBackAnimationCallback} to animate this view during a back gesture. 285 */ 286 @NonNull getBackCallback()287 OnBackAnimationCallback getBackCallback() { 288 return mBackCallback; 289 } 290 291 public interface SwipeListener { onSwipeUp()292 void onSwipeUp(); 293 /** */ onSwipeDown()294 void onSwipeDown(); 295 } 296 297 @VisibleForTesting 298 public enum BouncerUiEvent implements UiEventLogger.UiEventEnum { 299 @UiEvent(doc = "Default UiEvent used for variable initialization.") 300 UNKNOWN(0), 301 302 @UiEvent(doc = "Bouncer is dismissed using extended security access.") 303 BOUNCER_DISMISS_EXTENDED_ACCESS(413), 304 305 @UiEvent(doc = "Bouncer is dismissed using biometric.") 306 BOUNCER_DISMISS_BIOMETRIC(414), 307 308 @UiEvent(doc = "Bouncer is dismissed without security access.") 309 BOUNCER_DISMISS_NONE_SECURITY(415), 310 311 @UiEvent(doc = "Bouncer is dismissed using password security.") 312 BOUNCER_DISMISS_PASSWORD(416), 313 314 @UiEvent(doc = "Bouncer is dismissed using sim security access.") 315 BOUNCER_DISMISS_SIM(417), 316 317 @UiEvent(doc = "Bouncer is successfully unlocked using password.") 318 BOUNCER_PASSWORD_SUCCESS(418), 319 320 @UiEvent(doc = "An attempt to unlock bouncer using password has failed.") 321 BOUNCER_PASSWORD_FAILURE(419); 322 323 private final int mId; 324 BouncerUiEvent(int id)325 BouncerUiEvent(int id) { 326 mId = id; 327 } 328 329 @Override getId()330 public int getId() { 331 return mId; 332 } 333 } 334 KeyguardSecurityContainer(Context context, AttributeSet attrs)335 public KeyguardSecurityContainer(Context context, AttributeSet attrs) { 336 this(context, attrs, 0); 337 } 338 KeyguardSecurityContainer(Context context)339 public KeyguardSecurityContainer(Context context) { 340 this(context, null, 0); 341 } 342 KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle)343 public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) { 344 super(context, attrs, defStyle); 345 mSpringAnimation = new SpringAnimation(this, DynamicAnimation.TRANSLATION_Y); 346 mViewConfiguration = ViewConfiguration.get(context); 347 mDoubleTapDetector = new GestureDetector(context, new DoubleTapListener()); 348 349 // Add additional top padding. 350 setPadding(getPaddingLeft(), getPaddingTop() + getResources().getDimensionPixelSize( 351 R.dimen.keyguard_security_container_padding_top), getPaddingRight(), 352 getPaddingBottom()); 353 reloadBackgroundColor(); 354 } 355 onResume(SecurityMode securityMode, boolean faceAuthEnabled)356 void onResume(SecurityMode securityMode, boolean faceAuthEnabled) { 357 mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); 358 updateBiometricRetry(securityMode, faceAuthEnabled); 359 } 360 setBackgroundExecutor(Executor bgExecutor)361 void setBackgroundExecutor(Executor bgExecutor) { 362 mBgExecutor = bgExecutor; 363 } 364 initMode(@ode int mode, GlobalSettings globalSettings, FalsingManager falsingManager, UserSwitcherController userSwitcherController, UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback, FalsingA11yDelegate falsingA11yDelegate)365 void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager, 366 UserSwitcherController userSwitcherController, 367 UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback, 368 FalsingA11yDelegate falsingA11yDelegate) { 369 if (mCurrentMode == mode) return; 370 Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to " 371 + modeToString(mode)); 372 mCurrentMode = mode; 373 mViewMode.onDestroy(); 374 375 switch (mode) { 376 case MODE_ONE_HANDED: 377 mViewMode = new OneHandedViewMode(); 378 break; 379 case MODE_USER_SWITCHER: 380 mViewMode = new UserSwitcherViewMode(userSwitcherCallback, mBgExecutor); 381 break; 382 default: 383 mViewMode = new DefaultViewMode(); 384 } 385 mGlobalSettings = globalSettings; 386 mFalsingManager = falsingManager; 387 mFalsingA11yDelegate = falsingA11yDelegate; 388 mUserSwitcherController = userSwitcherController; 389 setupViewMode(); 390 } 391 modeToString(@ode int mode)392 private String modeToString(@Mode int mode) { 393 switch (mode) { 394 case MODE_UNINITIALIZED: 395 return "Uninitialized"; 396 case MODE_DEFAULT: 397 return "Default"; 398 case MODE_ONE_HANDED: 399 return "OneHanded"; 400 case MODE_USER_SWITCHER: 401 return "UserSwitcher"; 402 default: 403 throw new IllegalArgumentException("mode: " + mode + " not supported"); 404 } 405 } 406 setupViewMode()407 private void setupViewMode() { 408 if (mSecurityViewFlipper == null || mGlobalSettings == null 409 || mFalsingManager == null || mUserSwitcherController == null) { 410 return; 411 } 412 413 mViewMode.init(this, mGlobalSettings, mSecurityViewFlipper, mFalsingManager, 414 mUserSwitcherController, mFalsingA11yDelegate); 415 } 416 getMode()417 @Mode int getMode() { 418 return mCurrentMode; 419 } 420 421 /** 422 * The position of the container can be adjusted based upon a touch at location x. This has 423 * been used in one-handed mode to make sure the bouncer appears on the side of the display 424 * that the user last interacted with. 425 */ updatePositionByTouchX(float x)426 void updatePositionByTouchX(float x) { 427 mViewMode.updatePositionByTouchX(x); 428 } 429 isSidedSecurityMode()430 public boolean isSidedSecurityMode() { 431 return mViewMode instanceof SidedSecurityMode; 432 } 433 434 /** Returns whether the inner SecurityViewFlipper is left-aligned when in sided mode. */ isSecurityLeftAligned()435 public boolean isSecurityLeftAligned() { 436 return mViewMode instanceof SidedSecurityMode 437 && ((SidedSecurityMode) mViewMode).isLeftAligned(); 438 } 439 440 /** 441 * Returns whether the touch happened on the other side of security (like bouncer) when in 442 * sided mode. 443 */ isTouchOnTheOtherSideOfSecurity(MotionEvent ev)444 public boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev) { 445 return mViewMode instanceof SidedSecurityMode 446 && ((SidedSecurityMode) mViewMode).isTouchOnTheOtherSideOfSecurity(ev); 447 } 448 onPause()449 public void onPause() { 450 if (mAlertDialog != null) { 451 mAlertDialog.dismiss(); 452 mAlertDialog = null; 453 } 454 mSecurityViewFlipper.setWindowInsetsAnimationCallback(null); 455 mViewMode.reset(); 456 } 457 458 /** Set true if the view can be interacted with */ setInteractable(boolean isInteractable)459 public void setInteractable(boolean isInteractable) { 460 mIsInteractable = isInteractable; 461 } 462 463 @Override shouldDelayChildPressedState()464 public boolean shouldDelayChildPressedState() { 465 return true; 466 } 467 468 @Override onInterceptTouchEvent(MotionEvent event)469 public boolean onInterceptTouchEvent(MotionEvent event) { 470 if (!mIsInteractable) { 471 return true; 472 } 473 474 boolean result = mMotionEventListeners.stream().anyMatch( 475 listener -> listener.onInterceptTouchEvent(event)) 476 || super.onInterceptTouchEvent(event); 477 478 switch (event.getActionMasked()) { 479 case MotionEvent.ACTION_DOWN: 480 int pointerIndex = event.getActionIndex(); 481 mStartTouchY = event.getY(pointerIndex); 482 mActivePointerId = event.getPointerId(pointerIndex); 483 mVelocityTracker.clear(); 484 break; 485 case MotionEvent.ACTION_MOVE: 486 if (mIsDragging) { 487 return true; 488 } 489 if (!mSwipeUpToRetry) { 490 return false; 491 } 492 // Avoid dragging the pattern view 493 if (mSecurityViewFlipper.getSecurityView() != null 494 && mSecurityViewFlipper.getSecurityView().disallowInterceptTouch(event)) { 495 return false; 496 } 497 int index = event.findPointerIndex(mActivePointerId); 498 float touchSlop = mViewConfiguration.getScaledTouchSlop() * SLOP_SCALE; 499 if (index != -1 && mStartTouchY - event.getY(index) > touchSlop) { 500 mIsDragging = true; 501 return true; 502 } 503 break; 504 case MotionEvent.ACTION_CANCEL: 505 case MotionEvent.ACTION_UP: 506 mIsDragging = false; 507 break; 508 } 509 return result; 510 } 511 512 @Override onTouchEvent(MotionEvent event)513 public boolean onTouchEvent(MotionEvent event) { 514 final int action = event.getActionMasked(); 515 516 boolean result = mMotionEventListeners.stream() 517 .anyMatch(listener -> listener.onTouchEvent(event)) 518 || super.onTouchEvent(event); 519 520 // double tap detector should be called after listeners handle touches as listeners are 521 // helping with ignoring falsing. Otherwise falsing will be activated for some double taps 522 mDoubleTapDetector.onTouchEvent(event); 523 524 switch (action) { 525 case MotionEvent.ACTION_MOVE: 526 mVelocityTracker.addMovement(event); 527 int pointerIndex = event.findPointerIndex(mActivePointerId); 528 if (pointerIndex != -1) { 529 float y = event.getY(pointerIndex); 530 if (mLastTouchY != -1) { 531 float dy = y - mLastTouchY; 532 setTranslationY(getTranslationY() + dy * TOUCH_Y_MULTIPLIER); 533 } 534 mLastTouchY = y; 535 } 536 break; 537 case MotionEvent.ACTION_UP: 538 case MotionEvent.ACTION_CANCEL: 539 mActivePointerId = -1; 540 mLastTouchY = -1; 541 mIsDragging = false; 542 startSpringAnimation(mVelocityTracker.getYVelocity()); 543 break; 544 case MotionEvent.ACTION_POINTER_UP: 545 int index = event.getActionIndex(); 546 int pointerId = event.getPointerId(index); 547 if (pointerId == mActivePointerId) { 548 // This was our active pointer going up. Choose a new 549 // active pointer and adjust accordingly. 550 final int newPointerIndex = index == 0 ? 1 : 0; 551 mLastTouchY = event.getY(newPointerIndex); 552 mActivePointerId = event.getPointerId(newPointerIndex); 553 } 554 break; 555 } 556 if (action == MotionEvent.ACTION_UP) { 557 if (-getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 558 MIN_DRAG_SIZE, getResources().getDisplayMetrics())) { 559 if (mSwipeListener != null) { 560 mSwipeListener.onSwipeUp(); 561 } 562 } else if (getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 563 MIN_DRAG_SIZE, getResources().getDisplayMetrics())) { 564 if (mSwipeListener != null) { 565 mSwipeListener.onSwipeDown(); 566 } 567 } 568 } 569 return true; 570 } 571 572 private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener { 573 @Override onDoubleTap(MotionEvent e)574 public boolean onDoubleTap(MotionEvent e) { 575 return handleDoubleTap(e); 576 } 577 } 578 handleDoubleTap(MotionEvent e)579 @VisibleForTesting boolean handleDoubleTap(MotionEvent e) { 580 if (!mIsDragging) { 581 mViewMode.handleDoubleTap(e); 582 return true; 583 } 584 return false; 585 } 586 isAppearAnimationDelayed()587 boolean isAppearAnimationDelayed() { 588 return mIsAppearAnimationDelayed; 589 } 590 addMotionEventListener(Gefingerpoken listener)591 void addMotionEventListener(Gefingerpoken listener) { 592 mMotionEventListeners.add(listener); 593 } 594 removeMotionEventListener(Gefingerpoken listener)595 void removeMotionEventListener(Gefingerpoken listener) { 596 mMotionEventListeners.remove(listener); 597 } 598 setSwipeListener(SwipeListener swipeListener)599 void setSwipeListener(SwipeListener swipeListener) { 600 mSwipeListener = swipeListener; 601 } 602 startSpringAnimation(float startVelocity)603 private void startSpringAnimation(float startVelocity) { 604 mSpringAnimation 605 .setStartVelocity(startVelocity) 606 .animateToFinalPosition(0); 607 } 608 609 /** 610 * Runs after a successful authentication only 611 */ startDisappearAnimation(SecurityMode securitySelection)612 public void startDisappearAnimation(SecurityMode securitySelection) { 613 mDisappearAnimRunning = true; 614 if (securitySelection == SecurityMode.Password 615 && mSecurityViewFlipper.getSecurityView() instanceof KeyguardPasswordView) { 616 ((KeyguardPasswordView) mSecurityViewFlipper.getSecurityView()) 617 .setDisappearAnimationListener(this::setTranslationY); 618 } else { 619 mViewMode.startDisappearAnimation(securitySelection); 620 } 621 } 622 623 /** 624 * This will run when the bouncer shows in all cases except when the user drags the bouncer up. 625 */ startAppearAnimation(SecurityMode securityMode)626 public void startAppearAnimation(SecurityMode securityMode) { 627 setTranslationY(0f); 628 updateChildren(0 /* translationY */, 1f /* alpha */); 629 mViewMode.startAppearAnimation(securityMode); 630 } 631 632 /** 633 * Set view translationY and alpha as we delay bouncer animation. 634 */ setupForDelayedAppear()635 public void setupForDelayedAppear() { 636 setTranslationY(0f); 637 setAlpha(0f); 638 setIsAppearAnimationDelayed(true); 639 } 640 setIsAppearAnimationDelayed(boolean isDelayed)641 public void setIsAppearAnimationDelayed(boolean isDelayed) { 642 mIsAppearAnimationDelayed = isDelayed; 643 } 644 beginJankInstrument(int cuj)645 private void beginJankInstrument(int cuj) { 646 KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView(); 647 if (securityView == null) return; 648 InteractionJankMonitor.getInstance().begin(securityView, cuj); 649 } 650 endJankInstrument(int cuj)651 private void endJankInstrument(int cuj) { 652 InteractionJankMonitor.getInstance().end(cuj); 653 } 654 cancelJankInstrument(int cuj)655 private void cancelJankInstrument(int cuj) { 656 InteractionJankMonitor.getInstance().cancel(cuj); 657 } 658 659 /** 660 * Enables/disables swipe up to retry on the bouncer. 661 */ updateBiometricRetry(SecurityMode securityMode, boolean faceAuthEnabled)662 private void updateBiometricRetry(SecurityMode securityMode, boolean faceAuthEnabled) { 663 mSwipeUpToRetry = faceAuthEnabled 664 && securityMode != SecurityMode.SimPin 665 && securityMode != SecurityMode.SimPuk 666 && securityMode != SecurityMode.None; 667 } 668 getTitle()669 public CharSequence getTitle() { 670 return mSecurityViewFlipper.getTitle(); 671 } 672 673 674 @Override onFinishInflate()675 public void onFinishInflate() { 676 super.onFinishInflate(); 677 updateSecurityViewFlipper(); 678 } 679 updateSecurityViewFlipper()680 protected void updateSecurityViewFlipper() { 681 mSecurityViewFlipper = findViewById(R.id.view_flipper); 682 setupViewMode(); 683 } 684 685 @Override onApplyWindowInsets(WindowInsets insets)686 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 687 688 // Consume bottom insets because we're setting the padding locally (for IME and navbar.) 689 int bottomInset = insets.getInsetsIgnoringVisibility(systemBars()).bottom; 690 int imeInset = insets.getInsets(ime()).bottom; 691 int inset = max(bottomInset, imeInset); 692 int paddingBottom = max(inset, getContext().getResources() 693 .getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin)); 694 // If security mode is password, we rely on the animation value of defined in 695 // KeyguardPasswordView to determine the y translation animation. 696 // This means that we will prevent the WindowInsetsAnimationCallback from setting any y 697 // translation values by preventing the setting of the padding here. 698 if (!mDisappearAnimRunning) { 699 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); 700 } 701 return insets.inset(0, 0, 0, inset); 702 } 703 704 @Override dispatchTouchEvent(MotionEvent ev)705 public boolean dispatchTouchEvent(MotionEvent ev) { 706 return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev)); 707 } 708 709 @Override dispatchDraw(Canvas canvas)710 protected void dispatchDraw(Canvas canvas) { 711 super.dispatchDraw(canvas); 712 if (mViewMediatorCallback != null) { 713 mViewMediatorCallback.keyguardDoneDrawing(); 714 } 715 } 716 setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback)717 public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) { 718 mViewMediatorCallback = viewMediatorCallback; 719 } 720 showDialog(String title, String message)721 private void showDialog(String title, String message) { 722 if (mAlertDialog != null) { 723 mAlertDialog.dismiss(); 724 } 725 726 mAlertDialog = new AlertDialog.Builder(mContext) 727 .setTitle(title) 728 .setMessage(message) 729 .setCancelable(false) 730 .setNeutralButton(R.string.ok, null) 731 .create(); 732 if (!(mContext instanceof Activity)) { 733 mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 734 } 735 mAlertDialog.show(); 736 } 737 showTimeoutDialog(int userId, int timeoutMs, LockPatternUtils lockPatternUtils, SecurityMode securityMode)738 void showTimeoutDialog(int userId, int timeoutMs, LockPatternUtils lockPatternUtils, 739 SecurityMode securityMode) { 740 int timeoutInSeconds = timeoutMs / 1000; 741 int messageId = 0; 742 743 switch (securityMode) { 744 case Pattern: 745 messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message; 746 break; 747 case PIN: 748 messageId = R.string.kg_too_many_failed_pin_attempts_dialog_message; 749 break; 750 case Password: 751 messageId = R.string.kg_too_many_failed_password_attempts_dialog_message; 752 break; 753 // These don't have timeout dialogs. 754 case Invalid: 755 case None: 756 case SimPin: 757 case SimPuk: 758 break; 759 } 760 761 if (messageId != 0) { 762 final String message = mContext.getString(messageId, 763 lockPatternUtils.getCurrentFailedPasswordAttempts(userId), 764 timeoutInSeconds); 765 showDialog(null, message); 766 } 767 } 768 769 @Override onLayout(boolean changed, int left, int top, int right, int bottom)770 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 771 super.onLayout(changed, left, top, right, bottom); 772 int width = right - left; 773 if (changed && mWidth != width) { 774 mWidth = width; 775 mViewMode.updateSecurityViewLocation(); 776 } 777 } 778 779 @Override onConfigurationChanged(Configuration config)780 protected void onConfigurationChanged(Configuration config) { 781 super.onConfigurationChanged(config); 782 mViewMode.updateSecurityViewLocation(); 783 } 784 showAlmostAtWipeDialog(int attempts, int remaining, int userType)785 void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { 786 String message = null; 787 switch (userType) { 788 case USER_TYPE_PRIMARY: 789 message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe, 790 attempts, remaining); 791 break; 792 case USER_TYPE_SECONDARY_USER: 793 message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_user, 794 attempts, remaining); 795 break; 796 case USER_TYPE_WORK_PROFILE: 797 message = mContext.getSystemService(DevicePolicyManager.class).getResources() 798 .getString(KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE, 799 () -> mContext.getString( 800 R.string.kg_failed_attempts_almost_at_erase_profile, 801 attempts, remaining), 802 attempts, remaining); 803 break; 804 } 805 showDialog(null, message); 806 } 807 showWipeDialog(int attempts, int userType)808 void showWipeDialog(int attempts, int userType) { 809 String message = null; 810 switch (userType) { 811 case USER_TYPE_PRIMARY: 812 message = mContext.getString(R.string.kg_failed_attempts_now_wiping, 813 attempts); 814 break; 815 case USER_TYPE_SECONDARY_USER: 816 message = mContext.getString(R.string.kg_failed_attempts_now_erasing_user, 817 attempts); 818 break; 819 case USER_TYPE_WORK_PROFILE: 820 message = mContext.getSystemService(DevicePolicyManager.class).getResources() 821 .getString(KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE, 822 () -> mContext.getString( 823 R.string.kg_failed_attempts_now_erasing_profile, attempts), 824 attempts); 825 break; 826 } 827 showDialog(null, message); 828 } 829 reset()830 public void reset() { 831 mViewMode.reset(); 832 mDisappearAnimRunning = false; 833 mIsAppearAnimationDelayed = false; 834 } 835 836 /** 837 * Make the bouncer background transparent 838 */ enableTransparentMode()839 public void enableTransparentMode() { 840 mTransparentModeEnabled = true; 841 reloadBackgroundColor(); 842 } 843 844 /** 845 * Make the bouncer background opaque 846 */ disableTransparentMode()847 public void disableTransparentMode() { 848 mTransparentModeEnabled = false; 849 reloadBackgroundColor(); 850 } 851 reloadBackgroundColor()852 private void reloadBackgroundColor() { 853 if (mTransparentModeEnabled) { 854 setBackgroundColor(Color.TRANSPARENT); 855 } else { 856 setBackgroundColor( 857 getContext().getColor(com.android.internal.R.color.materialColorSurfaceDim)); 858 } 859 invalidate(); 860 } 861 reloadColors()862 void reloadColors() { 863 mViewMode.reloadColors(); 864 reloadBackgroundColor(); 865 } 866 867 /** Handles density or font scale changes. */ onDensityOrFontScaleChanged()868 void onDensityOrFontScaleChanged() { 869 mViewMode.onDensityOrFontScaleChanged(); 870 } 871 resetScale()872 void resetScale() { 873 setScale(1); 874 } 875 setScale(float scale)876 private void setScale(float scale) { 877 setScaleX(scale); 878 setScaleY(scale); 879 } 880 updateChildren(int translationY, float alpha)881 private void updateChildren(int translationY, float alpha) { 882 for (int i = 0; i < getChildCount(); ++i) { 883 View child = getChildAt(i); 884 child.setTranslationY(translationY); 885 child.setAlpha(alpha); 886 } 887 } 888 889 /** 890 * Enscapsulates the differences between bouncer modes for the container. 891 */ 892 interface ViewMode { init(@onNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, @NonNull UserSwitcherController userSwitcherController, @NonNull FalsingA11yDelegate falsingA11yDelegate)893 default void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, 894 @NonNull KeyguardSecurityViewFlipper viewFlipper, 895 @NonNull FalsingManager falsingManager, 896 @NonNull UserSwitcherController userSwitcherController, 897 @NonNull FalsingA11yDelegate falsingA11yDelegate) {}; 898 899 /** Reinitialize the location */ updateSecurityViewLocation()900 default void updateSecurityViewLocation() {}; 901 902 /** Alter the ViewFlipper position, based upon a touch outside of it */ updatePositionByTouchX(float x)903 default void updatePositionByTouchX(float x) {}; 904 905 /** A double tap on the container, outside of the ViewFlipper */ handleDoubleTap(MotionEvent event)906 default void handleDoubleTap(MotionEvent event) {}; 907 908 /** Called when the view needs to reset or hides */ reset()909 default void reset() {}; 910 911 /** Refresh colors */ reloadColors()912 default void reloadColors() {}; 913 914 /** Handles density or font scale changes. */ onDensityOrFontScaleChanged()915 default void onDensityOrFontScaleChanged() {} 916 917 /** On a successful auth, optionally handle how the view disappears */ startDisappearAnimation(SecurityMode securityMode)918 default void startDisappearAnimation(SecurityMode securityMode) {}; 919 920 /** On notif tap, this animation will run */ startAppearAnimation(SecurityMode securityMode)921 default void startAppearAnimation(SecurityMode securityMode) {}; 922 923 /** Called when we are setting a new ViewMode */ onDestroy()924 default void onDestroy() {}; 925 } 926 927 /** 928 * Base class for modes which support having on left/right side of the screen, used for large 929 * screen devices 930 */ 931 abstract static class SidedSecurityMode implements ViewMode { 932 private KeyguardSecurityViewFlipper mViewFlipper; 933 private ConstraintLayout mView; 934 private GlobalSettings mGlobalSettings; 935 private int mDefaultSideSetting; 936 init(ConstraintLayout v, KeyguardSecurityViewFlipper viewFlipper, GlobalSettings globalSettings, boolean leftAlignedByDefault)937 public void init(ConstraintLayout v, KeyguardSecurityViewFlipper viewFlipper, 938 GlobalSettings globalSettings, boolean leftAlignedByDefault) { 939 mView = v; 940 mViewFlipper = viewFlipper; 941 mGlobalSettings = globalSettings; 942 mDefaultSideSetting = 943 leftAlignedByDefault ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT 944 : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT; 945 } 946 947 /** 948 * Determine if a double tap on this view is on the other side. If so, will animate 949 * positions and record the preference to always show on this side. 950 */ 951 @Override handleDoubleTap(MotionEvent event)952 public void handleDoubleTap(MotionEvent event) { 953 boolean currentlyLeftAligned = isLeftAligned(); 954 // Did the tap hit the "other" side of the bouncer? 955 if (isTouchOnTheOtherSideOfSecurity(event, currentlyLeftAligned)) { 956 boolean willBeLeftAligned = !currentlyLeftAligned; 957 updateSideSetting(willBeLeftAligned); 958 959 int keyguardState = willBeLeftAligned 960 ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT 961 : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT; 962 SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState); 963 964 updateSecurityViewLocation(willBeLeftAligned, /* animate= */ true); 965 } 966 } 967 isTouchOnTheOtherSideOfSecurity(MotionEvent ev, boolean leftAligned)968 private boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev, boolean leftAligned) { 969 float x = ev.getX(); 970 return (leftAligned && (x > mView.getWidth() / 2f)) 971 || (!leftAligned && (x < mView.getWidth() / 2f)); 972 } 973 isTouchOnTheOtherSideOfSecurity(MotionEvent ev)974 public boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev) { 975 return isTouchOnTheOtherSideOfSecurity(ev, isLeftAligned()); 976 } 977 updateSecurityViewLocation(boolean leftAlign, boolean animate)978 protected abstract void updateSecurityViewLocation(boolean leftAlign, boolean animate); 979 isLeftAligned()980 boolean isLeftAligned() { 981 return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE, 982 mDefaultSideSetting) 983 == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; 984 } 985 updateSideSetting(boolean leftAligned)986 protected void updateSideSetting(boolean leftAligned) { 987 mGlobalSettings.putInt( 988 Settings.Global.ONE_HANDED_KEYGUARD_SIDE, 989 leftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT 990 : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); 991 } 992 } 993 994 /** 995 * Default bouncer is centered within the space 996 */ 997 static class DefaultViewMode implements ViewMode { 998 private ConstraintLayout mView; 999 private KeyguardSecurityViewFlipper mViewFlipper; 1000 1001 @Override init(@onNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, @NonNull UserSwitcherController userSwitcherController, @NonNull FalsingA11yDelegate falsingA11yDelegate)1002 public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, 1003 @NonNull KeyguardSecurityViewFlipper viewFlipper, 1004 @NonNull FalsingManager falsingManager, 1005 @NonNull UserSwitcherController userSwitcherController, 1006 @NonNull FalsingA11yDelegate falsingA11yDelegate) { 1007 mView = v; 1008 mViewFlipper = viewFlipper; 1009 1010 // Reset ViewGroup to default positions 1011 updateSecurityViewGroup(); 1012 } 1013 updateSecurityViewGroup()1014 private void updateSecurityViewGroup() { 1015 ConstraintSet constraintSet = new ConstraintSet(); 1016 constraintSet.connect(mViewFlipper.getId(), START, PARENT_ID, START); 1017 constraintSet.connect(mViewFlipper.getId(), END, PARENT_ID, END); 1018 constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM); 1019 constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP); 1020 constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT); 1021 constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT); 1022 constraintSet.applyTo(mView); 1023 } 1024 1025 @Override onDestroy()1026 public void onDestroy() { 1027 if (mView == null) return; 1028 ConstraintSet constraintSet = new ConstraintSet(); 1029 constraintSet.clone(mView); 1030 constraintSet.clear(mViewFlipper.getId()); 1031 constraintSet.applyTo(mView); 1032 } 1033 } 1034 1035 /** 1036 * User switcher mode will display both the current user icon as well as 1037 * a user switcher, in both portrait and landscape modes. 1038 */ 1039 static class UserSwitcherViewMode extends SidedSecurityMode { 1040 private ConstraintLayout mView; 1041 private ViewGroup mUserSwitcherViewGroup; 1042 private KeyguardSecurityViewFlipper mViewFlipper; 1043 private TextView mUserSwitcher; 1044 private FalsingManager mFalsingManager; 1045 private UserSwitcherController mUserSwitcherController; 1046 private KeyguardUserSwitcherPopupMenu mPopup; 1047 private Executor mBgExecutor; 1048 private Resources mResources; 1049 private UserSwitcherController.UserSwitchCallback mUserSwitchCallback = 1050 this::setupUserSwitcher; 1051 1052 private UserSwitcherCallback mUserSwitcherCallback; 1053 private FalsingA11yDelegate mFalsingA11yDelegate; 1054 UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback, Executor bgExecutor)1055 UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback, Executor bgExecutor) { 1056 mUserSwitcherCallback = userSwitcherCallback; 1057 mBgExecutor = bgExecutor; 1058 } 1059 1060 @Override init(@onNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, @NonNull UserSwitcherController userSwitcherController, @NonNull FalsingA11yDelegate falsingA11yDelegate)1061 public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, 1062 @NonNull KeyguardSecurityViewFlipper viewFlipper, 1063 @NonNull FalsingManager falsingManager, 1064 @NonNull UserSwitcherController userSwitcherController, 1065 @NonNull FalsingA11yDelegate falsingA11yDelegate) { 1066 init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */false); 1067 mView = v; 1068 mViewFlipper = viewFlipper; 1069 mFalsingManager = falsingManager; 1070 mUserSwitcherController = userSwitcherController; 1071 mResources = v.getContext().getResources(); 1072 mFalsingA11yDelegate = falsingA11yDelegate; 1073 1074 if (mUserSwitcherViewGroup == null) { 1075 inflateUserSwitcher(); 1076 setupUserSwitcher(); 1077 mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback); 1078 } 1079 updateSecurityViewLocation(); 1080 } 1081 1082 @Override reset()1083 public void reset() { 1084 if (mPopup != null) { 1085 mPopup.dismiss(); 1086 mPopup = null; 1087 } 1088 setupUserSwitcher(); 1089 } 1090 1091 @Override reloadColors()1092 public void reloadColors() { 1093 TextView header = (TextView) mView.findViewById(R.id.user_switcher_header); 1094 if (header != null) { 1095 header.setTextColor(Utils.getColorAttrDefaultColor(mView.getContext(), 1096 android.R.attr.textColorPrimary)); 1097 header.setBackground(mView.getContext().getDrawable( 1098 R.drawable.bouncer_user_switcher_header_bg)); 1099 Drawable keyDownDrawable = 1100 ((LayerDrawable) header.getBackground().mutate()).findDrawableByLayerId( 1101 R.id.user_switcher_key_down); 1102 keyDownDrawable.setTintList(Utils.getColorAttr(mView.getContext(), 1103 android.R.attr.textColorPrimary)); 1104 } 1105 } 1106 1107 @Override onDensityOrFontScaleChanged()1108 public void onDensityOrFontScaleChanged() { 1109 mView.removeView(mUserSwitcherViewGroup); 1110 mView.removeView(mUserSwitcher); 1111 inflateUserSwitcher(); 1112 } 1113 1114 @Override onDestroy()1115 public void onDestroy() { 1116 mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback); 1117 1118 ConstraintSet constraintSet = new ConstraintSet(); 1119 constraintSet.clone(mView); 1120 constraintSet.clear(mUserSwitcherViewGroup.getId()); 1121 constraintSet.clear(mViewFlipper.getId()); 1122 constraintSet.applyTo(mView); 1123 1124 mView.removeView(mUserSwitcherViewGroup); 1125 mView.removeView(mUserSwitcher); 1126 mUserSwitcher = null; 1127 mUserSwitcherViewGroup = null; 1128 } 1129 findLargeUserIcon(int userId, Consumer<Drawable> consumer)1130 private void findLargeUserIcon(int userId, Consumer<Drawable> consumer) { 1131 mBgExecutor.execute(() -> { 1132 Drawable icon; 1133 Bitmap userIcon = UserManager.get(mView.getContext()).getUserIcon(userId); 1134 if (userIcon != null) { 1135 int iconSize = mResources.getDimensionPixelSize( 1136 R.dimen.bouncer_user_switcher_icon_size); 1137 icon = CircleFramedDrawable.getInstance( 1138 mView.getContext(), 1139 Icon.scaleDownIfNecessary(userIcon, iconSize, iconSize) 1140 ); 1141 } else { 1142 icon = UserIcons.getDefaultUserIcon(mResources, userId, false); 1143 } 1144 consumer.accept(icon); 1145 }); 1146 } 1147 1148 @Override startAppearAnimation(SecurityMode securityMode)1149 public void startAppearAnimation(SecurityMode securityMode) { 1150 // IME insets animations handle alpha and translation 1151 if (securityMode == SecurityMode.Password) { 1152 return; 1153 } 1154 1155 if (mUserSwitcherViewGroup == null) { 1156 return; 1157 } 1158 1159 mUserSwitcherViewGroup.setAlpha(0f); 1160 ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); 1161 int yTrans = mView.getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry); 1162 animator.setInterpolator(Interpolators.STANDARD_DECELERATE); 1163 animator.setDuration(650); 1164 animator.addListener(new AnimatorListenerAdapter() { 1165 @Override 1166 public void onAnimationEnd(Animator animation) { 1167 if (mUserSwitcherViewGroup != null) { 1168 mUserSwitcherViewGroup.setAlpha(1f); 1169 mUserSwitcherViewGroup.setTranslationY(0f); 1170 } 1171 } 1172 }); 1173 animator.addUpdateListener(animation -> { 1174 if (mUserSwitcherViewGroup != null) { 1175 float value = (float) animation.getAnimatedValue(); 1176 mUserSwitcherViewGroup.setAlpha(value); 1177 mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value); 1178 } 1179 }); 1180 animator.start(); 1181 } 1182 1183 @Override startDisappearAnimation(SecurityMode securityMode)1184 public void startDisappearAnimation(SecurityMode securityMode) { 1185 // IME insets animations handle alpha and translation 1186 if (securityMode == SecurityMode.Password) { 1187 return; 1188 } 1189 1190 int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation); 1191 1192 AnimatorSet anims = new AnimatorSet(); 1193 ObjectAnimator yAnim = ObjectAnimator.ofFloat(mViewFlipper, View.TRANSLATION_Y, 1194 yTranslation); 1195 ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA, 1196 0f); 1197 1198 anims.setInterpolator(Interpolators.STANDARD_ACCELERATE); 1199 anims.playTogether(alphaAnim, yAnim); 1200 anims.start(); 1201 } 1202 setupUserSwitcher()1203 private void setupUserSwitcher() { 1204 final UserRecord currentUser = mUserSwitcherController.getCurrentUserRecord(); 1205 if (currentUser == null) { 1206 Log.e(TAG, "Current user in user switcher is null."); 1207 return; 1208 } 1209 if (mUserSwitcher == null) { 1210 Log.w(TAG, "User switcher is not inflated, cannot setupUserSwitcher"); 1211 return; 1212 } 1213 final String currentUserName = mUserSwitcherController.getCurrentUserName(); 1214 findLargeUserIcon(currentUser.info.id, 1215 (Drawable userIcon) -> { 1216 mView.post(() -> { 1217 ImageView view = (ImageView) mView.findViewById(R.id.user_icon); 1218 if (view != null) { 1219 view.setImageDrawable(userIcon); 1220 } 1221 }); 1222 }); 1223 mUserSwitcher.setText(currentUserName); 1224 1225 KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor); 1226 anchor.setAccessibilityDelegate(mFalsingA11yDelegate); 1227 1228 BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) { 1229 @Override 1230 public View getView(int position, View convertView, ViewGroup parent) { 1231 UserRecord item = getItem(position); 1232 FrameLayout view = (FrameLayout) convertView; 1233 if (view == null) { 1234 view = (FrameLayout) LayoutInflater.from(parent.getContext()).inflate( 1235 R.layout.keyguard_bouncer_user_switcher_item, 1236 parent, 1237 false); 1238 } 1239 TextView textView = (TextView) view.getChildAt(0); 1240 textView.setText(getName(parent.getContext(), item)); 1241 Drawable icon = null; 1242 if (item.picture != null) { 1243 icon = new BitmapDrawable(item.picture); 1244 } else { 1245 icon = getDrawable(item, view.getContext()); 1246 } 1247 int iconSize = view.getResources().getDimensionPixelSize( 1248 R.dimen.bouncer_user_switcher_item_icon_size); 1249 int iconPadding = view.getResources().getDimensionPixelSize( 1250 R.dimen.bouncer_user_switcher_item_icon_padding); 1251 icon.setBounds(0, 0, iconSize, iconSize); 1252 textView.setCompoundDrawablePadding(iconPadding); 1253 textView.setCompoundDrawablesRelative(icon, null, null, null); 1254 1255 if (item == currentUser) { 1256 textView.setBackground(view.getContext().getDrawable( 1257 R.drawable.bouncer_user_switcher_item_selected_bg)); 1258 } else { 1259 textView.setBackground(null); 1260 } 1261 textView.setSelected(item == currentUser); 1262 view.setEnabled(item.isSwitchToEnabled); 1263 UserSwitcherController.setSelectableAlpha(view); 1264 return view; 1265 } 1266 1267 private Drawable getDrawable(UserRecord item, Context context) { 1268 Drawable drawable; 1269 if (item.isCurrent && item.isGuest) { 1270 drawable = context.getDrawable(R.drawable.ic_avatar_guest_user); 1271 } else { 1272 drawable = getIconDrawable(context, item); 1273 } 1274 1275 int iconColor; 1276 if (item.isSwitchToEnabled) { 1277 iconColor = Utils.getColorAttrDefaultColor(context, 1278 com.android.internal.R.attr.colorAccentPrimaryVariant); 1279 } else { 1280 iconColor = context.getResources().getColor( 1281 R.color.kg_user_switcher_restricted_avatar_icon_color, 1282 context.getTheme()); 1283 } 1284 drawable.setTint(iconColor); 1285 1286 Drawable bg = context.getDrawable( 1287 com.android.settingslib.R.drawable.user_avatar_bg); 1288 bg.setTintBlendMode(BlendMode.DST); 1289 bg.setTint(Utils.getColorAttrDefaultColor(context, 1290 com.android.internal.R.attr.colorSurfaceVariant)); 1291 drawable = new LayerDrawable(new Drawable[]{bg, drawable}); 1292 return drawable; 1293 } 1294 }; 1295 1296 anchor.setOnClickListener((v) -> { 1297 if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; 1298 mPopup = new KeyguardUserSwitcherPopupMenu(mView.getContext(), mFalsingManager); 1299 mPopup.setAnchorView(anchor); 1300 mPopup.setAdapter(adapter); 1301 mPopup.setOnItemClickListener((parent, view, pos, id) -> { 1302 if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; 1303 if (!view.isEnabled()) return; 1304 if (mPopup == null) return; 1305 // Subtract one for the header 1306 UserRecord user = adapter.getItem(pos - 1); 1307 if (user.isManageUsers || user.isAddSupervisedUser) { 1308 mUserSwitcherCallback.showUnlockToContinueMessage(); 1309 } 1310 if (!user.isCurrent) { 1311 adapter.onUserListItemClicked(user); 1312 } 1313 mPopup.dismiss(); 1314 mPopup = null; 1315 }); 1316 mPopup.show(); 1317 }); 1318 } 1319 1320 @Override updateSecurityViewLocation()1321 public void updateSecurityViewLocation() { 1322 updateSecurityViewLocation(isLeftAligned(), /* animate= */false); 1323 } 1324 updateSecurityViewLocation(boolean leftAlign, boolean animate)1325 public void updateSecurityViewLocation(boolean leftAlign, boolean animate) { 1326 if (animate) { 1327 TransitionManager.beginDelayedTransition(mView, 1328 new KeyguardSecurityViewTransition()); 1329 } 1330 int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans); 1331 int viewFlipperBottomMargin = mResources.getDimensionPixelSize( 1332 R.dimen.bouncer_user_switcher_view_mode_view_flipper_bottom_margin); 1333 int userSwitcherBottomMargin = mResources.getDimensionPixelSize( 1334 R.dimen.bouncer_user_switcher_view_mode_user_switcher_bottom_margin); 1335 if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 1336 ConstraintSet constraintSet = new ConstraintSet(); 1337 constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans); 1338 constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, mViewFlipper.getId(), 1339 TOP, userSwitcherBottomMargin); 1340 constraintSet.connect(mViewFlipper.getId(), TOP, mUserSwitcherViewGroup.getId(), 1341 BOTTOM); 1342 constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM, 1343 viewFlipperBottomMargin); 1344 constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID); 1345 constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID); 1346 constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD); 1347 constraintSet.setVerticalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD); 1348 constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(), WRAP_CONTENT); 1349 constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(), WRAP_CONTENT); 1350 constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT); 1351 constraintSet.applyTo(mView); 1352 } else { 1353 int startElement = 1354 leftAlign ? mViewFlipper.getId() : mUserSwitcherViewGroup.getId(); 1355 int endElement = 1356 leftAlign ? mUserSwitcherViewGroup.getId() : mViewFlipper.getId(); 1357 1358 ConstraintSet constraintSet = new ConstraintSet(); 1359 constraintSet.connect(startElement, START, PARENT_ID, START); 1360 constraintSet.connect(startElement, END, endElement, START); 1361 constraintSet.connect(endElement, START, startElement, END); 1362 constraintSet.connect(endElement, END, PARENT_ID, END); 1363 constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP); 1364 constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM); 1365 constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP); 1366 constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM); 1367 constraintSet.setHorizontalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD); 1368 constraintSet.setHorizontalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD); 1369 constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(), 1370 MATCH_CONSTRAINT); 1371 constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(), 1372 MATCH_CONSTRAINT); 1373 constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT); 1374 constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT); 1375 constraintSet.applyTo(mView); 1376 } 1377 } 1378 inflateUserSwitcher()1379 private void inflateUserSwitcher() { 1380 LayoutInflater.from(mView.getContext()).inflate( 1381 R.layout.keyguard_bouncer_user_switcher, 1382 mView, 1383 true); 1384 mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher); 1385 mUserSwitcher = mView.findViewById(R.id.user_switcher_header); 1386 if (bouncerUiRevamp2()) { 1387 mUserSwitcher.setTypeface( 1388 Typeface.create(FontStyles.GSF_LABEL_MEDIUM, Typeface.NORMAL)); 1389 } 1390 } 1391 1392 interface UserSwitcherCallback { showUnlockToContinueMessage()1393 void showUnlockToContinueMessage(); 1394 } 1395 } 1396 1397 /** 1398 * Logic to enabled one-handed bouncer mode. Supports animating the bouncer 1399 * between alternate sides of the display. 1400 */ 1401 static class OneHandedViewMode extends SidedSecurityMode { 1402 private ConstraintLayout mView; 1403 private KeyguardSecurityViewFlipper mViewFlipper; 1404 1405 @Override init(@onNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, @NonNull UserSwitcherController userSwitcherController, @NonNull FalsingA11yDelegate falsingA11yDelegate)1406 public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, 1407 @NonNull KeyguardSecurityViewFlipper viewFlipper, 1408 @NonNull FalsingManager falsingManager, 1409 @NonNull UserSwitcherController userSwitcherController, 1410 @NonNull FalsingA11yDelegate falsingA11yDelegate) { 1411 init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */true); 1412 mView = v; 1413 mViewFlipper = viewFlipper; 1414 1415 updateSecurityViewLocation(isLeftAligned(), /* animate= */false); 1416 } 1417 1418 /** 1419 * Moves the bouncer to align with a tap (most likely in the shade), so the bouncer 1420 * appears on the same side as a touch. 1421 */ 1422 @Override updatePositionByTouchX(float x)1423 public void updatePositionByTouchX(float x) { 1424 boolean isTouchOnLeft = x <= mView.getWidth() / 2f; 1425 updateSideSetting(isTouchOnLeft); 1426 updateSecurityViewLocation(isTouchOnLeft, /* animate= */false); 1427 } 1428 1429 @Override updateSecurityViewLocation()1430 public void updateSecurityViewLocation() { 1431 updateSecurityViewLocation(isLeftAligned(), /* animate= */false); 1432 } 1433 updateSecurityViewLocation(boolean leftAlign, boolean animate)1434 protected void updateSecurityViewLocation(boolean leftAlign, boolean animate) { 1435 if (animate) { 1436 TransitionManager.beginDelayedTransition(mView, 1437 new KeyguardSecurityViewTransition()); 1438 } 1439 ConstraintSet constraintSet = new ConstraintSet(); 1440 if (leftAlign) { 1441 constraintSet.connect(mViewFlipper.getId(), LEFT, PARENT_ID, LEFT); 1442 } else { 1443 constraintSet.connect(mViewFlipper.getId(), RIGHT, PARENT_ID, RIGHT); 1444 } 1445 constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP); 1446 constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM); 1447 constraintSet.constrainPercentWidth(mViewFlipper.getId(), 0.5f); 1448 constraintSet.applyTo(mView); 1449 } 1450 1451 @Override onDestroy()1452 public void onDestroy() { 1453 ConstraintSet constraintSet = new ConstraintSet(); 1454 constraintSet.clone(mView); 1455 constraintSet.clear(mViewFlipper.getId()); 1456 constraintSet.applyTo(mView); 1457 } 1458 } 1459 } 1460