• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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