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