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