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