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