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