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 android.app.Activity; 19 import android.app.AlertDialog; 20 import android.app.admin.DevicePolicyManager; 21 import android.content.Context; 22 import android.os.UserHandle; 23 import android.os.UserManager; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.util.Slog; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.WindowManager; 30 import android.widget.FrameLayout; 31 32 import com.android.internal.widget.LockPatternUtils; 33 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 34 35 public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView { 36 private static final boolean DEBUG = KeyguardConstants.DEBUG; 37 private static final String TAG = "KeyguardSecurityView"; 38 39 private static final int USER_TYPE_PRIMARY = 1; 40 private static final int USER_TYPE_WORK_PROFILE = 2; 41 private static final int USER_TYPE_SECONDARY_USER = 3; 42 43 private KeyguardSecurityModel mSecurityModel; 44 private boolean mEnableFallback; // TODO: This should get the value from KeyguardPatternView 45 private LockPatternUtils mLockPatternUtils; 46 47 private KeyguardSecurityViewFlipper mSecurityViewFlipper; 48 private boolean mIsVerifyUnlockOnly; 49 private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid; 50 private boolean mIsBouncing; 51 private SecurityCallback mSecurityCallback; 52 53 private final KeyguardUpdateMonitor mUpdateMonitor; 54 55 // Used to notify the container when something interesting happens. 56 public interface SecurityCallback { dismiss(boolean authenticated)57 public boolean dismiss(boolean authenticated); userActivity()58 public void userActivity(); onSecurityModeChanged(SecurityMode securityMode, boolean needsInput)59 public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); finish()60 public void finish(); 61 } 62 KeyguardSecurityContainer(Context context, AttributeSet attrs)63 public KeyguardSecurityContainer(Context context, AttributeSet attrs) { 64 this(context, attrs, 0); 65 } 66 KeyguardSecurityContainer(Context context)67 public KeyguardSecurityContainer(Context context) { 68 this(context, null, 0); 69 } 70 KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle)71 public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) { 72 super(context, attrs, defStyle); 73 mSecurityModel = new KeyguardSecurityModel(context); 74 mLockPatternUtils = new LockPatternUtils(context); 75 mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 76 } 77 setSecurityCallback(SecurityCallback callback)78 public void setSecurityCallback(SecurityCallback callback) { 79 mSecurityCallback = callback; 80 } 81 82 @Override onResume(int reason)83 public void onResume(int reason) { 84 if (mCurrentSecuritySelection != SecurityMode.None) { 85 getSecurityView(mCurrentSecuritySelection).onResume(reason); 86 } 87 } 88 89 @Override onPause()90 public void onPause() { 91 if (mCurrentSecuritySelection != SecurityMode.None) { 92 getSecurityView(mCurrentSecuritySelection).onPause(); 93 } 94 } 95 startAppearAnimation()96 public void startAppearAnimation() { 97 if (mCurrentSecuritySelection != SecurityMode.None) { 98 getSecurityView(mCurrentSecuritySelection).startAppearAnimation(); 99 } 100 } 101 startDisappearAnimation(Runnable onFinishRunnable)102 public boolean startDisappearAnimation(Runnable onFinishRunnable) { 103 if (mCurrentSecuritySelection != SecurityMode.None) { 104 return getSecurityView(mCurrentSecuritySelection).startDisappearAnimation( 105 onFinishRunnable); 106 } 107 return false; 108 } 109 updateSecurityViews(boolean isBouncing)110 void updateSecurityViews(boolean isBouncing) { 111 int children = mSecurityViewFlipper.getChildCount(); 112 for (int i = 0; i < children; i++) { 113 updateSecurityView(mSecurityViewFlipper.getChildAt(i), isBouncing); 114 } 115 } 116 announceCurrentSecurityMethod()117 public void announceCurrentSecurityMethod() { 118 View v = (View) getSecurityView(mCurrentSecuritySelection); 119 if (v != null) { 120 v.announceForAccessibility(v.getContentDescription()); 121 } 122 } 123 getCurrentSecurityModeContentDescription()124 public CharSequence getCurrentSecurityModeContentDescription() { 125 View v = (View) getSecurityView(mCurrentSecuritySelection); 126 if (v != null) { 127 return v.getContentDescription(); 128 } 129 return ""; 130 } 131 getSecurityView(SecurityMode securityMode)132 private KeyguardSecurityView getSecurityView(SecurityMode securityMode) { 133 final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); 134 KeyguardSecurityView view = null; 135 final int children = mSecurityViewFlipper.getChildCount(); 136 for (int child = 0; child < children; child++) { 137 if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) { 138 view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child)); 139 break; 140 } 141 } 142 int layoutId = getLayoutIdFor(securityMode); 143 if (view == null && layoutId != 0) { 144 final LayoutInflater inflater = LayoutInflater.from(mContext); 145 if (DEBUG) Log.v(TAG, "inflating id = " + layoutId); 146 View v = inflater.inflate(layoutId, mSecurityViewFlipper, false); 147 mSecurityViewFlipper.addView(v); 148 updateSecurityView(v, mIsBouncing); 149 view = (KeyguardSecurityView)v; 150 } 151 152 return view; 153 } 154 updateSecurityView(View view, boolean isBouncing)155 private void updateSecurityView(View view, boolean isBouncing) { 156 mIsBouncing = isBouncing; 157 if (view instanceof KeyguardSecurityView) { 158 KeyguardSecurityView ksv = (KeyguardSecurityView) view; 159 ksv.setKeyguardCallback(mCallback); 160 ksv.setLockPatternUtils(mLockPatternUtils); 161 if (isBouncing) { 162 ksv.showBouncer(0); 163 } else { 164 ksv.hideBouncer(0); 165 } 166 } else { 167 Log.w(TAG, "View " + view + " is not a KeyguardSecurityView"); 168 } 169 } 170 onFinishInflate()171 protected void onFinishInflate() { 172 mSecurityViewFlipper = (KeyguardSecurityViewFlipper) findViewById(R.id.view_flipper); 173 mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils); 174 } 175 setLockPatternUtils(LockPatternUtils utils)176 public void setLockPatternUtils(LockPatternUtils utils) { 177 mLockPatternUtils = utils; 178 mSecurityModel.setLockPatternUtils(utils); 179 mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils); 180 } 181 showDialog(String title, String message)182 private void showDialog(String title, String message) { 183 final AlertDialog dialog = new AlertDialog.Builder(mContext) 184 .setTitle(title) 185 .setMessage(message) 186 .setNeutralButton(R.string.ok, null) 187 .create(); 188 if (!(mContext instanceof Activity)) { 189 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 190 } 191 dialog.show(); 192 } 193 showTimeoutDialog()194 private void showTimeoutDialog() { 195 int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; 196 int messageId = 0; 197 198 switch (mSecurityModel.getSecurityMode()) { 199 case Pattern: 200 messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message; 201 break; 202 case PIN: 203 messageId = R.string.kg_too_many_failed_pin_attempts_dialog_message; 204 break; 205 case Password: 206 messageId = R.string.kg_too_many_failed_password_attempts_dialog_message; 207 break; 208 // These don't have timeout dialogs. 209 case Account: 210 case Biometric: 211 case Invalid: 212 case None: 213 case SimPin: 214 case SimPuk: 215 break; 216 } 217 218 if (messageId != 0) { 219 final String message = mContext.getString(messageId, 220 KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(), 221 timeoutInSeconds); 222 showDialog(null, message); 223 } 224 } 225 showAlmostAtWipeDialog(int attempts, int remaining, int userType)226 private void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { 227 String message = null; 228 switch (userType) { 229 case USER_TYPE_PRIMARY: 230 message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe, 231 attempts, remaining); 232 break; 233 case USER_TYPE_SECONDARY_USER: 234 message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_user, 235 attempts, remaining); 236 break; 237 case USER_TYPE_WORK_PROFILE: 238 message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_profile, 239 attempts, remaining); 240 break; 241 } 242 showDialog(null, message); 243 } 244 showWipeDialog(int attempts, int userType)245 private void showWipeDialog(int attempts, int userType) { 246 String message = null; 247 switch (userType) { 248 case USER_TYPE_PRIMARY: 249 message = mContext.getString(R.string.kg_failed_attempts_now_wiping, 250 attempts); 251 break; 252 case USER_TYPE_SECONDARY_USER: 253 message = mContext.getString(R.string.kg_failed_attempts_now_erasing_user, 254 attempts); 255 break; 256 case USER_TYPE_WORK_PROFILE: 257 message = mContext.getString(R.string.kg_failed_attempts_now_erasing_profile, 258 attempts); 259 break; 260 } 261 showDialog(null, message); 262 } 263 showAlmostAtAccountLoginDialog()264 private void showAlmostAtAccountLoginDialog() { 265 final int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; 266 final int count = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET 267 - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT; 268 String message = mContext.getString(R.string.kg_failed_attempts_almost_at_login, 269 count, LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, timeoutInSeconds); 270 showDialog(null, message); 271 } 272 reportFailedUnlockAttempt()273 private void reportFailedUnlockAttempt() { 274 final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); 275 final int failedAttempts = monitor.getFailedUnlockAttempts() + 1; // +1 for this time 276 277 if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); 278 279 SecurityMode mode = mSecurityModel.getSecurityMode(); 280 final boolean usingPattern = mode == KeyguardSecurityModel.SecurityMode.Pattern; 281 final int currentUser = mLockPatternUtils.getCurrentUser(); 282 final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager(); 283 final int failedAttemptsBeforeWipe = 284 dpm.getMaximumFailedPasswordsForWipe(null, currentUser); 285 286 final int failedAttemptWarning = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET 287 - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT; 288 289 final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ? 290 (failedAttemptsBeforeWipe - failedAttempts) 291 : Integer.MAX_VALUE; // because DPM returns 0 if no restriction 292 boolean showTimeout = false; 293 if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) { 294 // The user has installed a DevicePolicyManager that requests a user/profile to be wiped 295 // N attempts. Once we get below the grace period, we post this dialog every time as a 296 // clear warning until the deletion fires. 297 // Check which profile has the strictest policy for failed password attempts 298 final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(currentUser); 299 int userType = USER_TYPE_PRIMARY; 300 if (expiringUser == currentUser) { 301 if (expiringUser != UserHandle.USER_OWNER) { 302 userType = USER_TYPE_SECONDARY_USER; 303 } 304 } else if (expiringUser != UserHandle.USER_NULL) { 305 userType = USER_TYPE_WORK_PROFILE; 306 } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY 307 if (remainingBeforeWipe > 0) { 308 showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType); 309 } else { 310 // Too many attempts. The device will be wiped shortly. 311 Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!"); 312 showWipeDialog(failedAttempts, userType); 313 } 314 } else { 315 showTimeout = 316 (failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) == 0; 317 if (usingPattern && mEnableFallback) { 318 if (failedAttempts == failedAttemptWarning) { 319 showAlmostAtAccountLoginDialog(); 320 showTimeout = false; // don't show both dialogs 321 } else if (failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) { 322 mLockPatternUtils.setPermanentlyLocked(true); 323 showSecurityScreen(SecurityMode.Account); 324 // don't show timeout dialog because we show account unlock screen next 325 showTimeout = false; 326 } 327 } 328 } 329 monitor.reportFailedUnlockAttempt(); 330 mLockPatternUtils.reportFailedPasswordAttempt(); 331 if (showTimeout) { 332 showTimeoutDialog(); 333 } 334 } 335 336 /** 337 * Shows the primary security screen for the user. This will be either the multi-selector 338 * or the user's security method. 339 * @param turningOff true if the device is being turned off 340 */ showPrimarySecurityScreen(boolean turningOff)341 void showPrimarySecurityScreen(boolean turningOff) { 342 SecurityMode securityMode = mSecurityModel.getSecurityMode(); 343 if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); 344 if (!turningOff && 345 KeyguardUpdateMonitor.getInstance(mContext).isAlternateUnlockEnabled()) { 346 // If we're not turning off, then allow biometric alternate. 347 // We'll reload it when the device comes back on. 348 securityMode = mSecurityModel.getAlternateFor(securityMode); 349 } 350 showSecurityScreen(securityMode); 351 } 352 353 /** 354 * Shows the backup security screen for the current security mode. This could be used for 355 * password recovery screens but is currently only used for pattern unlock to show the 356 * account unlock screen and biometric unlock to show the user's normal unlock. 357 */ showBackupSecurityScreen()358 private void showBackupSecurityScreen() { 359 if (DEBUG) Log.d(TAG, "showBackupSecurity()"); 360 SecurityMode backup = mSecurityModel.getBackupSecurityMode(mCurrentSecuritySelection); 361 showSecurityScreen(backup); 362 } 363 364 /** 365 * Shows the next security screen if there is one. 366 * @param authenticated true if the user entered the correct authentication 367 * @param authenticated 368 * @return true if keyguard is done 369 */ showNextSecurityScreenOrFinish(boolean authenticated)370 boolean showNextSecurityScreenOrFinish(boolean authenticated) { 371 if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); 372 boolean finish = false; 373 if (mUpdateMonitor.getUserHasTrust(mLockPatternUtils.getCurrentUser())) { 374 finish = true; 375 } else if (SecurityMode.None == mCurrentSecuritySelection) { 376 SecurityMode securityMode = mSecurityModel.getSecurityMode(); 377 // Allow an alternate, such as biometric unlock 378 securityMode = mSecurityModel.getAlternateFor(securityMode); 379 if (SecurityMode.None == securityMode) { 380 finish = true; // no security required 381 } else { 382 showSecurityScreen(securityMode); // switch to the alternate security view 383 } 384 } else if (authenticated) { 385 switch (mCurrentSecuritySelection) { 386 case Pattern: 387 case Password: 388 case PIN: 389 case Account: 390 case Biometric: 391 finish = true; 392 break; 393 394 case SimPin: 395 case SimPuk: 396 // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home 397 SecurityMode securityMode = mSecurityModel.getSecurityMode(); 398 if (securityMode != SecurityMode.None) { 399 showSecurityScreen(securityMode); 400 } else { 401 finish = true; 402 } 403 break; 404 405 default: 406 Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe"); 407 showPrimarySecurityScreen(false); 408 break; 409 } 410 } 411 if (finish) { 412 mSecurityCallback.finish(); 413 } 414 return finish; 415 } 416 417 /** 418 * Switches to the given security view unless it's already being shown, in which case 419 * this is a no-op. 420 * 421 * @param securityMode 422 */ showSecurityScreen(SecurityMode securityMode)423 private void showSecurityScreen(SecurityMode securityMode) { 424 if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")"); 425 426 if (securityMode == mCurrentSecuritySelection) return; 427 428 KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection); 429 KeyguardSecurityView newView = getSecurityView(securityMode); 430 431 // Emulate Activity life cycle 432 if (oldView != null) { 433 oldView.onPause(); 434 oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view 435 } 436 if (securityMode != SecurityMode.None) { 437 newView.onResume(KeyguardSecurityView.VIEW_REVEALED); 438 newView.setKeyguardCallback(mCallback); 439 } 440 441 // Find and show this child. 442 final int childCount = mSecurityViewFlipper.getChildCount(); 443 444 final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); 445 for (int i = 0; i < childCount; i++) { 446 if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) { 447 mSecurityViewFlipper.setDisplayedChild(i); 448 break; 449 } 450 } 451 452 mCurrentSecuritySelection = securityMode; 453 mSecurityCallback.onSecurityModeChanged(securityMode, 454 securityMode != SecurityMode.None && newView.needsInput()); 455 } 456 getFlipper()457 private KeyguardSecurityViewFlipper getFlipper() { 458 for (int i = 0; i < getChildCount(); i++) { 459 View child = getChildAt(i); 460 if (child instanceof KeyguardSecurityViewFlipper) { 461 return (KeyguardSecurityViewFlipper) child; 462 } 463 } 464 return null; 465 } 466 showBouncer(int duration)467 public void showBouncer(int duration) { 468 KeyguardSecurityViewFlipper flipper = getFlipper(); 469 if (flipper != null) { 470 flipper.showBouncer(duration); 471 } 472 } 473 hideBouncer(int duration)474 public void hideBouncer(int duration) { 475 KeyguardSecurityViewFlipper flipper = getFlipper(); 476 if (flipper != null) { 477 flipper.hideBouncer(duration); 478 } 479 } 480 481 private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() { 482 483 public void userActivity() { 484 if (mSecurityCallback != null) { 485 mSecurityCallback.userActivity(); 486 } 487 } 488 489 public void dismiss(boolean authenticated) { 490 mSecurityCallback.dismiss(authenticated); 491 } 492 493 public boolean isVerifyUnlockOnly() { 494 return mIsVerifyUnlockOnly; 495 } 496 497 public void reportUnlockAttempt(boolean success) { 498 KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); 499 if (success) { 500 monitor.clearFailedUnlockAttempts(); 501 mLockPatternUtils.reportSuccessfulPasswordAttempt(); 502 } else { 503 if (mCurrentSecuritySelection == SecurityMode.Biometric) { 504 monitor.reportFailedBiometricUnlockAttempt(); 505 } else { 506 KeyguardSecurityContainer.this.reportFailedUnlockAttempt(); 507 } 508 } 509 } 510 511 @Override 512 public void showBackupSecurity() { 513 KeyguardSecurityContainer.this.showBackupSecurityScreen(); 514 } 515 516 }; 517 518 // The following is used to ignore callbacks from SecurityViews that are no longer current 519 // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the 520 // state for the current security method. 521 private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() { 522 @Override 523 public void userActivity() { } 524 @Override 525 public void showBackupSecurity() { } 526 @Override 527 public void reportUnlockAttempt(boolean success) { } 528 @Override 529 public boolean isVerifyUnlockOnly() { return false; } 530 @Override 531 public void dismiss(boolean securityVerified) { } 532 }; 533 getSecurityViewIdForMode(SecurityMode securityMode)534 private int getSecurityViewIdForMode(SecurityMode securityMode) { 535 switch (securityMode) { 536 case Pattern: return R.id.keyguard_pattern_view; 537 case PIN: return R.id.keyguard_pin_view; 538 case Password: return R.id.keyguard_password_view; 539 case Biometric: return R.id.keyguard_face_unlock_view; 540 case Account: return R.id.keyguard_account_view; 541 case SimPin: return R.id.keyguard_sim_pin_view; 542 case SimPuk: return R.id.keyguard_sim_puk_view; 543 } 544 return 0; 545 } 546 getLayoutIdFor(SecurityMode securityMode)547 private int getLayoutIdFor(SecurityMode securityMode) { 548 switch (securityMode) { 549 case Pattern: return R.layout.keyguard_pattern_view; 550 case PIN: return R.layout.keyguard_pin_view; 551 case Password: return R.layout.keyguard_password_view; 552 case Biometric: return R.layout.keyguard_face_unlock_view; 553 case Account: return R.layout.keyguard_account_view; 554 case SimPin: return R.layout.keyguard_sim_pin_view; 555 case SimPuk: return R.layout.keyguard_sim_puk_view; 556 default: 557 return 0; 558 } 559 } 560 getSecurityMode()561 public SecurityMode getSecurityMode() { 562 return mSecurityModel.getSecurityMode(); 563 } 564 getCurrentSecurityMode()565 public SecurityMode getCurrentSecurityMode() { 566 return mCurrentSecuritySelection; 567 } 568 verifyUnlock()569 public void verifyUnlock() { 570 mIsVerifyUnlockOnly = true; 571 showSecurityScreen(getSecurityMode()); 572 } 573 getCurrentSecuritySelection()574 public SecurityMode getCurrentSecuritySelection() { 575 return mCurrentSecuritySelection; 576 } 577 dismiss(boolean authenticated)578 public void dismiss(boolean authenticated) { 579 mCallback.dismiss(authenticated); 580 } 581 needsInput()582 public boolean needsInput() { 583 return mSecurityViewFlipper.needsInput(); 584 } 585 586 @Override setKeyguardCallback(KeyguardSecurityCallback callback)587 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 588 mSecurityViewFlipper.setKeyguardCallback(callback); 589 } 590 591 @Override reset()592 public void reset() { 593 mSecurityViewFlipper.reset(); 594 } 595 596 @Override getCallback()597 public KeyguardSecurityCallback getCallback() { 598 return mSecurityViewFlipper.getCallback(); 599 } 600 601 @Override showUsabilityHint()602 public void showUsabilityHint() { 603 mSecurityViewFlipper.showUsabilityHint(); 604 } 605 606 } 607 608