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