• 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.view.WindowInsets.Type.ime;
19 import static android.view.WindowInsets.Type.systemBars;
20 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
21 
22 import static java.lang.Integer.max;
23 
24 import android.animation.Animator;
25 import android.animation.AnimatorListenerAdapter;
26 import android.app.Activity;
27 import android.app.AlertDialog;
28 import android.content.Context;
29 import android.graphics.Rect;
30 import android.provider.Settings;
31 import android.util.AttributeSet;
32 import android.util.MathUtils;
33 import android.util.TypedValue;
34 import android.view.Gravity;
35 import android.view.MotionEvent;
36 import android.view.VelocityTracker;
37 import android.view.View;
38 import android.view.ViewConfiguration;
39 import android.view.ViewPropertyAnimator;
40 import android.view.WindowInsets;
41 import android.view.WindowInsetsAnimation;
42 import android.view.WindowManager;
43 import android.widget.FrameLayout;
44 
45 import androidx.annotation.Nullable;
46 import androidx.annotation.VisibleForTesting;
47 import androidx.dynamicanimation.animation.DynamicAnimation;
48 import androidx.dynamicanimation.animation.SpringAnimation;
49 
50 import com.android.internal.jank.InteractionJankMonitor;
51 import com.android.internal.logging.UiEvent;
52 import com.android.internal.logging.UiEventLogger;
53 import com.android.internal.widget.LockPatternUtils;
54 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
55 import com.android.systemui.Gefingerpoken;
56 import com.android.systemui.R;
57 import com.android.systemui.animation.Interpolators;
58 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
59 
60 import java.util.ArrayList;
61 import java.util.List;
62 
63 public class KeyguardSecurityContainer extends FrameLayout {
64     static final int USER_TYPE_PRIMARY = 1;
65     static final int USER_TYPE_WORK_PROFILE = 2;
66     static final int USER_TYPE_SECONDARY_USER = 3;
67 
68     // Bouncer is dismissed due to no security.
69     static final int BOUNCER_DISMISS_NONE_SECURITY = 0;
70     // Bouncer is dismissed due to pin, password or pattern entered.
71     static final int BOUNCER_DISMISS_PASSWORD = 1;
72     // Bouncer is dismissed due to biometric (face, fingerprint or iris) authenticated.
73     static final int BOUNCER_DISMISS_BIOMETRIC = 2;
74     // Bouncer is dismissed due to extended access granted.
75     static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3;
76     // Bouncer is dismissed due to sim card unlock code entered.
77     static final int BOUNCER_DISMISS_SIM = 4;
78 
79     // Make the view move slower than the finger, as if the spring were applying force.
80     private static final float TOUCH_Y_MULTIPLIER = 0.25f;
81     // How much you need to drag the bouncer to trigger an auth retry (in dps.)
82     private static final float MIN_DRAG_SIZE = 10;
83     // How much to scale the default slop by, to avoid accidental drags.
84     private static final float SLOP_SCALE = 4f;
85 
86     private static final long IME_DISAPPEAR_DURATION_MS = 125;
87 
88     @VisibleForTesting
89     KeyguardSecurityViewFlipper mSecurityViewFlipper;
90     private AlertDialog mAlertDialog;
91     private boolean mSwipeUpToRetry;
92 
93     private final ViewConfiguration mViewConfiguration;
94     private final SpringAnimation mSpringAnimation;
95     private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
96     private final List<Gefingerpoken> mMotionEventListeners = new ArrayList<>();
97 
98     private float mLastTouchY = -1;
99     private int mActivePointerId = -1;
100     private boolean mIsDragging;
101     private float mStartTouchY = -1;
102     private boolean mDisappearAnimRunning;
103     private SwipeListener mSwipeListener;
104 
105     private boolean mIsSecurityViewLeftAligned = true;
106     private boolean mOneHandedMode = false;
107     private SecurityMode mSecurityMode = SecurityMode.Invalid;
108     private ViewPropertyAnimator mRunningOneHandedAnimator;
109 
110     private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
111             new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
112 
113                 private final Rect mInitialBounds = new Rect();
114                 private final Rect mFinalBounds = new Rect();
115 
116                 @Override
117                 public void onPrepare(WindowInsetsAnimation animation) {
118                     mSecurityViewFlipper.getBoundsOnScreen(mInitialBounds);
119                 }
120 
121                 @Override
122                 public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation,
123                         WindowInsetsAnimation.Bounds bounds) {
124                     if (!mDisappearAnimRunning) {
125                         beginJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
126                     } else {
127                         beginJankInstrument(
128                                 InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
129                     }
130                     mSecurityViewFlipper.getBoundsOnScreen(mFinalBounds);
131                     return bounds;
132                 }
133 
134                 @Override
135                 public WindowInsets onProgress(WindowInsets windowInsets,
136                         List<WindowInsetsAnimation> list) {
137                     float start = mDisappearAnimRunning
138                             ? -(mFinalBounds.bottom - mInitialBounds.bottom)
139                             : mInitialBounds.bottom - mFinalBounds.bottom;
140                     float end = mDisappearAnimRunning
141                             ? -((mFinalBounds.bottom - mInitialBounds.bottom) * 0.75f)
142                             : 0f;
143                     int translationY = 0;
144                     float interpolatedFraction = 1f;
145                     for (WindowInsetsAnimation animation : list) {
146                         if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
147                             continue;
148                         }
149                         interpolatedFraction = animation.getInterpolatedFraction();
150 
151                         final int paddingBottom = (int) MathUtils.lerp(
152                                 start, end,
153                                 interpolatedFraction);
154                         translationY += paddingBottom;
155                     }
156                     mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction,
157                             !mDisappearAnimRunning);
158 
159                     return windowInsets;
160                 }
161 
162                 @Override
163                 public void onEnd(WindowInsetsAnimation animation) {
164                     if (!mDisappearAnimRunning) {
165                         endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
166                         mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f,
167                                 true /* appearingAnim */);
168                     } else {
169                         endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
170                     }
171                 }
172             };
173 
174     // Used to notify the container when something interesting happens.
175     public interface SecurityCallback {
dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen)176         boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen);
177 
userActivity()178         void userActivity();
179 
onSecurityModeChanged(SecurityMode securityMode, boolean needsInput)180         void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
181 
182         /**
183          * @param strongAuth   wheher the user has authenticated with strong authentication like
184          *                     pattern, password or PIN but not by trust agents or fingerprint
185          * @param targetUserId a user that needs to be the foreground user at the finish completion.
186          */
finish(boolean strongAuth, int targetUserId)187         void finish(boolean strongAuth, int targetUserId);
188 
reset()189         void reset();
190 
onCancelClicked()191         void onCancelClicked();
192     }
193 
194     public interface SwipeListener {
onSwipeUp()195         void onSwipeUp();
196     }
197 
198     @VisibleForTesting
199     public enum BouncerUiEvent implements UiEventLogger.UiEventEnum {
200         @UiEvent(doc = "Default UiEvent used for variable initialization.")
201         UNKNOWN(0),
202 
203         @UiEvent(doc = "Bouncer is dismissed using extended security access.")
204         BOUNCER_DISMISS_EXTENDED_ACCESS(413),
205 
206         @UiEvent(doc = "Bouncer is dismissed using biometric.")
207         BOUNCER_DISMISS_BIOMETRIC(414),
208 
209         @UiEvent(doc = "Bouncer is dismissed without security access.")
210         BOUNCER_DISMISS_NONE_SECURITY(415),
211 
212         @UiEvent(doc = "Bouncer is dismissed using password security.")
213         BOUNCER_DISMISS_PASSWORD(416),
214 
215         @UiEvent(doc = "Bouncer is dismissed using sim security access.")
216         BOUNCER_DISMISS_SIM(417),
217 
218         @UiEvent(doc = "Bouncer is successfully unlocked using password.")
219         BOUNCER_PASSWORD_SUCCESS(418),
220 
221         @UiEvent(doc = "An attempt to unlock bouncer using password has failed.")
222         BOUNCER_PASSWORD_FAILURE(419);
223 
224         private final int mId;
225 
BouncerUiEvent(int id)226         BouncerUiEvent(int id) {
227             mId = id;
228         }
229 
230         @Override
getId()231         public int getId() {
232             return mId;
233         }
234     }
235 
KeyguardSecurityContainer(Context context, AttributeSet attrs)236     public KeyguardSecurityContainer(Context context, AttributeSet attrs) {
237         this(context, attrs, 0);
238     }
239 
KeyguardSecurityContainer(Context context)240     public KeyguardSecurityContainer(Context context) {
241         this(context, null, 0);
242     }
243 
KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle)244     public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
245         super(context, attrs, defStyle);
246         mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
247         mViewConfiguration = ViewConfiguration.get(context);
248     }
249 
onResume(SecurityMode securityMode, boolean faceAuthEnabled)250     void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
251         mSecurityMode = securityMode;
252         mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
253         updateBiometricRetry(securityMode, faceAuthEnabled);
254 
255         updateLayoutForSecurityMode(securityMode);
256     }
257 
updateLayoutForSecurityMode(SecurityMode securityMode)258     void updateLayoutForSecurityMode(SecurityMode securityMode) {
259         mSecurityMode = securityMode;
260         mOneHandedMode = canUseOneHandedBouncer();
261 
262         if (mOneHandedMode) {
263             mIsSecurityViewLeftAligned = isOneHandedKeyguardLeftAligned(mContext);
264         }
265 
266         updateSecurityViewGravity();
267         updateSecurityViewLocation(false);
268     }
269 
270     /** Update keyguard position based on a tapped X coordinate. */
updateKeyguardPosition(float x)271     public void updateKeyguardPosition(float x) {
272         if (mOneHandedMode) {
273             moveBouncerForXCoordinate(x, /* animate= */false);
274         }
275     }
276 
277     /** Return whether the one-handed keyguard should be enabled. */
canUseOneHandedBouncer()278     private boolean canUseOneHandedBouncer() {
279         // Is it enabled?
280         if (!getResources().getBoolean(
281                 com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) {
282             return false;
283         }
284 
285         if (!KeyguardSecurityModel.isSecurityViewOneHanded(mSecurityMode)) {
286             return false;
287         }
288 
289         return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
290     }
291 
292     /** Read whether the one-handed keyguard should be on the left/right from settings. */
isOneHandedKeyguardLeftAligned(Context context)293     private boolean isOneHandedKeyguardLeftAligned(Context context) {
294         try {
295             return Settings.Global.getInt(context.getContentResolver(),
296                     Settings.Global.ONE_HANDED_KEYGUARD_SIDE)
297                     == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
298         } catch (Settings.SettingNotFoundException ex) {
299             return true;
300         }
301     }
302 
updateSecurityViewGravity()303     private void updateSecurityViewGravity() {
304         View securityView = findKeyguardSecurityView();
305 
306         if (securityView == null) {
307             return;
308         }
309 
310         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) securityView.getLayoutParams();
311 
312         if (mOneHandedMode) {
313             lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
314         } else {
315             lp.gravity = Gravity.CENTER_HORIZONTAL;
316         }
317 
318         securityView.setLayoutParams(lp);
319     }
320 
321     /**
322      * Moves the inner security view to the correct location (in one handed mode) with animation.
323      * This is triggered when the user taps on the side of the screen that is not currently occupied
324      * by the security view .
325      */
updateSecurityViewLocation(boolean animate)326     private void updateSecurityViewLocation(boolean animate) {
327         View securityView = findKeyguardSecurityView();
328 
329         if (securityView == null) {
330             return;
331         }
332 
333         if (!mOneHandedMode) {
334             securityView.setTranslationX(0);
335             return;
336         }
337 
338         if (mRunningOneHandedAnimator != null) {
339             mRunningOneHandedAnimator.cancel();
340             mRunningOneHandedAnimator = null;
341         }
342 
343         int targetTranslation = mIsSecurityViewLeftAligned ? 0 : (int) (getMeasuredWidth() / 2f);
344 
345         if (animate) {
346             mRunningOneHandedAnimator = securityView.animate().translationX(targetTranslation);
347             mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
348             mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() {
349                 @Override
350                 public void onAnimationEnd(Animator animation) {
351                     mRunningOneHandedAnimator = null;
352                 }
353             });
354 
355             mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
356             mRunningOneHandedAnimator.start();
357         } else {
358             securityView.setTranslationX(targetTranslation);
359         }
360     }
361 
362     @Nullable
findKeyguardSecurityView()363     private KeyguardSecurityViewFlipper findKeyguardSecurityView() {
364         for (int i = 0; i < getChildCount(); i++) {
365             View child = getChildAt(i);
366 
367             if (isKeyguardSecurityView(child)) {
368                 return (KeyguardSecurityViewFlipper) child;
369             }
370         }
371 
372         return null;
373     }
374 
isKeyguardSecurityView(View view)375     private boolean isKeyguardSecurityView(View view) {
376         return view instanceof KeyguardSecurityViewFlipper;
377     }
378 
onPause()379     public void onPause() {
380         if (mAlertDialog != null) {
381             mAlertDialog.dismiss();
382             mAlertDialog = null;
383         }
384         mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
385     }
386 
387     @Override
shouldDelayChildPressedState()388     public boolean shouldDelayChildPressedState() {
389         return true;
390     }
391 
392     @Override
onInterceptTouchEvent(MotionEvent event)393     public boolean onInterceptTouchEvent(MotionEvent event) {
394         boolean result =  mMotionEventListeners.stream().anyMatch(
395                 listener -> listener.onInterceptTouchEvent(event))
396                 || super.onInterceptTouchEvent(event);
397 
398         switch (event.getActionMasked()) {
399             case MotionEvent.ACTION_DOWN:
400                 int pointerIndex = event.getActionIndex();
401                 mStartTouchY = event.getY(pointerIndex);
402                 mActivePointerId = event.getPointerId(pointerIndex);
403                 mVelocityTracker.clear();
404                 break;
405             case MotionEvent.ACTION_MOVE:
406                 if (mIsDragging) {
407                     return true;
408                 }
409                 if (!mSwipeUpToRetry) {
410                     return false;
411                 }
412                 // Avoid dragging the pattern view
413                 if (mSecurityViewFlipper.getSecurityView().disallowInterceptTouch(event)) {
414                     return false;
415                 }
416                 int index = event.findPointerIndex(mActivePointerId);
417                 float touchSlop = mViewConfiguration.getScaledTouchSlop() * SLOP_SCALE;
418                 if (index != -1 && mStartTouchY - event.getY(index) > touchSlop) {
419                     mIsDragging = true;
420                     return true;
421                 }
422                 break;
423             case MotionEvent.ACTION_CANCEL:
424             case MotionEvent.ACTION_UP:
425                 mIsDragging = false;
426                 break;
427         }
428         return result;
429     }
430 
431     @Override
onTouchEvent(MotionEvent event)432     public boolean onTouchEvent(MotionEvent event) {
433         final int action = event.getActionMasked();
434 
435         boolean result =  mMotionEventListeners.stream()
436                 .anyMatch(listener -> listener.onTouchEvent(event))
437                 || super.onTouchEvent(event);
438 
439         switch (action) {
440             case MotionEvent.ACTION_MOVE:
441                 mVelocityTracker.addMovement(event);
442                 int pointerIndex = event.findPointerIndex(mActivePointerId);
443                 float y = event.getY(pointerIndex);
444                 if (mLastTouchY != -1) {
445                     float dy = y - mLastTouchY;
446                     setTranslationY(getTranslationY() + dy * TOUCH_Y_MULTIPLIER);
447                 }
448                 mLastTouchY = y;
449                 break;
450             case MotionEvent.ACTION_UP:
451             case MotionEvent.ACTION_CANCEL:
452                 mActivePointerId = -1;
453                 mLastTouchY = -1;
454                 mIsDragging = false;
455                 startSpringAnimation(mVelocityTracker.getYVelocity());
456                 break;
457             case MotionEvent.ACTION_POINTER_UP:
458                 int index = event.getActionIndex();
459                 int pointerId = event.getPointerId(index);
460                 if (pointerId == mActivePointerId) {
461                     // This was our active pointer going up. Choose a new
462                     // active pointer and adjust accordingly.
463                     final int newPointerIndex = index == 0 ? 1 : 0;
464                     mLastTouchY = event.getY(newPointerIndex);
465                     mActivePointerId = event.getPointerId(newPointerIndex);
466                 }
467                 break;
468         }
469         if (action == MotionEvent.ACTION_UP) {
470             if (-getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
471                     MIN_DRAG_SIZE, getResources().getDisplayMetrics())) {
472                 if (mSwipeListener != null) {
473                     mSwipeListener.onSwipeUp();
474                 }
475             } else {
476                 if (!mIsDragging) {
477                     handleTap(event);
478                 }
479             }
480         }
481         return true;
482     }
483 
addMotionEventListener(Gefingerpoken listener)484     void addMotionEventListener(Gefingerpoken listener) {
485         mMotionEventListeners.add(listener);
486     }
487 
removeMotionEventListener(Gefingerpoken listener)488     void removeMotionEventListener(Gefingerpoken listener) {
489         mMotionEventListeners.remove(listener);
490     }
491 
handleTap(MotionEvent event)492     private void handleTap(MotionEvent event) {
493         // If we're using a fullscreen security mode, skip
494         if (!mOneHandedMode) {
495             return;
496         }
497 
498         moveBouncerForXCoordinate(event.getX(), /* animate= */true);
499     }
500 
moveBouncerForXCoordinate(float x, boolean animate)501     private void moveBouncerForXCoordinate(float x, boolean animate) {
502         // Did the tap hit the "other" side of the bouncer?
503         if ((mIsSecurityViewLeftAligned && (x > getWidth() / 2f))
504                 || (!mIsSecurityViewLeftAligned && (x < getWidth() / 2f))) {
505             mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned;
506 
507             Settings.Global.putInt(
508                     mContext.getContentResolver(),
509                     Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
510                     mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
511                             : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
512 
513             updateSecurityViewLocation(animate);
514         }
515     }
516 
setSwipeListener(SwipeListener swipeListener)517     void setSwipeListener(SwipeListener swipeListener) {
518         mSwipeListener = swipeListener;
519     }
520 
startSpringAnimation(float startVelocity)521     private void startSpringAnimation(float startVelocity) {
522         mSpringAnimation
523                 .setStartVelocity(startVelocity)
524                 .animateToFinalPosition(0);
525     }
526 
startDisappearAnimation(SecurityMode securitySelection)527     public void startDisappearAnimation(SecurityMode securitySelection) {
528         mDisappearAnimRunning = true;
529     }
530 
beginJankInstrument(int cuj)531     private void beginJankInstrument(int cuj) {
532         KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView();
533         if (securityView == null) return;
534         InteractionJankMonitor.getInstance().begin(securityView, cuj);
535     }
536 
endJankInstrument(int cuj)537     private void endJankInstrument(int cuj) {
538         InteractionJankMonitor.getInstance().end(cuj);
539     }
540 
cancelJankInstrument(int cuj)541     private void cancelJankInstrument(int cuj) {
542         InteractionJankMonitor.getInstance().cancel(cuj);
543     }
544 
545     /**
546      * Enables/disables swipe up to retry on the bouncer.
547      */
updateBiometricRetry(SecurityMode securityMode, boolean faceAuthEnabled)548     private void updateBiometricRetry(SecurityMode securityMode, boolean faceAuthEnabled) {
549         mSwipeUpToRetry = faceAuthEnabled
550                 && securityMode != SecurityMode.SimPin
551                 && securityMode != SecurityMode.SimPuk
552                 && securityMode != SecurityMode.None;
553     }
554 
getTitle()555     public CharSequence getTitle() {
556         return mSecurityViewFlipper.getTitle();
557     }
558 
559 
560     @Override
onFinishInflate()561     public void onFinishInflate() {
562         super.onFinishInflate();
563         mSecurityViewFlipper = findViewById(R.id.view_flipper);
564     }
565 
566     @Override
onApplyWindowInsets(WindowInsets insets)567     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
568 
569         // Consume bottom insets because we're setting the padding locally (for IME and navbar.)
570         int bottomInset = insets.getInsetsIgnoringVisibility(systemBars()).bottom;
571         int imeInset = insets.getInsets(ime()).bottom;
572         int inset = max(bottomInset, imeInset);
573         setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), inset);
574         return insets.inset(0, 0, 0, inset);
575     }
576 
showDialog(String title, String message)577     private void showDialog(String title, String message) {
578         if (mAlertDialog != null) {
579             mAlertDialog.dismiss();
580         }
581 
582         mAlertDialog = new AlertDialog.Builder(mContext)
583                 .setTitle(title)
584                 .setMessage(message)
585                 .setCancelable(false)
586                 .setNeutralButton(R.string.ok, null)
587                 .create();
588         if (!(mContext instanceof Activity)) {
589             mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
590         }
591         mAlertDialog.show();
592     }
593 
showTimeoutDialog(int userId, int timeoutMs, LockPatternUtils lockPatternUtils, SecurityMode securityMode)594     void showTimeoutDialog(int userId, int timeoutMs, LockPatternUtils lockPatternUtils,
595             SecurityMode securityMode) {
596         int timeoutInSeconds = timeoutMs / 1000;
597         int messageId = 0;
598 
599         switch (securityMode) {
600             case Pattern:
601                 messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message;
602                 break;
603             case PIN:
604                 messageId = R.string.kg_too_many_failed_pin_attempts_dialog_message;
605                 break;
606             case Password:
607                 messageId = R.string.kg_too_many_failed_password_attempts_dialog_message;
608                 break;
609             // These don't have timeout dialogs.
610             case Invalid:
611             case None:
612             case SimPin:
613             case SimPuk:
614                 break;
615         }
616 
617         if (messageId != 0) {
618             final String message = mContext.getString(messageId,
619                     lockPatternUtils.getCurrentFailedPasswordAttempts(userId),
620                     timeoutInSeconds);
621             showDialog(null, message);
622         }
623     }
624 
625     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)626     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
627         int maxHeight = 0;
628         int maxWidth = 0;
629         int childState = 0;
630 
631         int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
632                 MeasureSpec.getSize(widthMeasureSpec) / 2,
633                 MeasureSpec.getMode(widthMeasureSpec));
634 
635         for (int i = 0; i < getChildCount(); i++) {
636             final View view = getChildAt(i);
637             if (view.getVisibility() != GONE) {
638                 if (mOneHandedMode && isKeyguardSecurityView(view)) {
639                     measureChildWithMargins(view, halfWidthMeasureSpec, 0,
640                             heightMeasureSpec, 0);
641                 } else {
642                     measureChildWithMargins(view, widthMeasureSpec, 0,
643                             heightMeasureSpec, 0);
644                 }
645                 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
646                 maxWidth = Math.max(maxWidth,
647                         view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
648                 maxHeight = Math.max(maxHeight,
649                         view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
650                 childState = combineMeasuredStates(childState, view.getMeasuredState());
651             }
652         }
653 
654         maxWidth += getPaddingLeft() + getPaddingRight();
655         maxHeight += getPaddingTop() + getPaddingBottom();
656 
657         // Check against our minimum height and width
658         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
659         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
660 
661         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
662                 resolveSizeAndState(maxHeight, heightMeasureSpec,
663                         childState << MEASURED_HEIGHT_STATE_SHIFT));
664     }
665 
666     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)667     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
668         super.onLayout(changed, left, top, right, bottom);
669 
670         // After a layout pass, we need to re-place the inner bouncer, as our bounds may have
671         // changed.
672         updateSecurityViewLocation(/* animate= */false);
673     }
674 
showAlmostAtWipeDialog(int attempts, int remaining, int userType)675     void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
676         String message = null;
677         switch (userType) {
678             case USER_TYPE_PRIMARY:
679                 message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe,
680                         attempts, remaining);
681                 break;
682             case USER_TYPE_SECONDARY_USER:
683                 message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_user,
684                         attempts, remaining);
685                 break;
686             case USER_TYPE_WORK_PROFILE:
687                 message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_profile,
688                         attempts, remaining);
689                 break;
690         }
691         showDialog(null, message);
692     }
693 
showWipeDialog(int attempts, int userType)694     void showWipeDialog(int attempts, int userType) {
695         String message = null;
696         switch (userType) {
697             case USER_TYPE_PRIMARY:
698                 message = mContext.getString(R.string.kg_failed_attempts_now_wiping,
699                         attempts);
700                 break;
701             case USER_TYPE_SECONDARY_USER:
702                 message = mContext.getString(R.string.kg_failed_attempts_now_erasing_user,
703                         attempts);
704                 break;
705             case USER_TYPE_WORK_PROFILE:
706                 message = mContext.getString(R.string.kg_failed_attempts_now_erasing_profile,
707                         attempts);
708                 break;
709         }
710         showDialog(null, message);
711     }
712 
reset()713     public void reset() {
714         mDisappearAnimRunning = false;
715     }
716 }
717 
718