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