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