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