diff options
author | Jim Miller <jaggies@google.com> | 2014-01-14 18:57:03 -0800 |
---|---|---|
committer | Jim Miller <jaggies@google.com> | 2014-01-27 15:21:39 -0800 |
commit | 7751ff6cd079e59e3c1f2404198774cd371ea69f (patch) | |
tree | 30030a609b5152d9c42a03af1bcd4409add2ad01 /packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java | |
parent | db7516e3bd24589525c42b9b4761381b547dc3be (diff) | |
download | frameworks_base-7751ff6cd079e59e3c1f2404198774cd371ea69f.zip frameworks_base-7751ff6cd079e59e3c1f2404198774cd371ea69f.tar.gz frameworks_base-7751ff6cd079e59e3c1f2404198774cd371ea69f.tar.bz2 |
Move majority of security-related logic from KeyguardHostView
to KeyguardSecurityContainer.
This removes and/or simplifies the interface between modules to
allow easier separation of KeyguardSecurityContainer into a stand-alone
component.
Bug 12135931
Diffstat (limited to 'packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java')
-rw-r--r-- | packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java | 483 |
1 files changed, 481 insertions, 2 deletions
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java index 9d03c6a..7606689 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -1,11 +1,39 @@ package com.android.keyguard; +import android.app.Activity; +import android.app.AlertDialog; import android.content.Context; import android.util.AttributeSet; +import android.util.Log; +import android.util.Slog; +import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager; import android.widget.FrameLayout; -public class KeyguardSecurityContainer extends FrameLayout { +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; + +public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView { + private static final boolean DEBUG = KeyguardViewMediator.DEBUG; + private static final String TAG = "KeyguardSecurityView"; + private KeyguardSecurityModel mSecurityModel; + private boolean mEnableFallback; // TODO: This should get the value from KeyguardPatternView + private LockPatternUtils mLockPatternUtils; + + private KeyguardSecurityViewFlipper mSecurityViewFlipper; + private boolean mIsVerifyUnlockOnly; + private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid; + private boolean mIsBouncing; + private SecurityCallback mSecurityCallback; + + // Used to notify the container when something interesting happens. + public interface SecurityCallback { + public void dismiss(boolean authenticated); + public void userActivity(long timeout); + public void finish(); + } + public KeyguardSecurityContainer(Context context, AttributeSet attrs) { this(context, attrs, 0); } @@ -16,9 +44,336 @@ public class KeyguardSecurityContainer extends FrameLayout { public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mSecurityModel = new KeyguardSecurityModel(context); + mLockPatternUtils = new LockPatternUtils(context); + } + + public void setSecurityCallback(SecurityCallback callback) { + mSecurityCallback = callback; + } + + @Override + public void onResume(int reason) { + getSecurityView(mCurrentSecuritySelection).onResume(reason); + } + + @Override + public void onPause() { + getSecurityView(mCurrentSecuritySelection).onPause(); + } + + void updateSecurityViews(boolean isBouncing) { + int children = mSecurityViewFlipper.getChildCount(); + for (int i = 0; i < children; i++) { + updateSecurityView(mSecurityViewFlipper.getChildAt(i), isBouncing); + } + } + + public void announceCurrentSecurityMethod() { + View v = (View) getSecurityView(mCurrentSecuritySelection); + if (v != null) { + v.announceForAccessibility(v.getContentDescription()); + } + } + + private KeyguardSecurityView getSecurityView(SecurityMode securityMode) { + final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); + KeyguardSecurityView view = null; + final int children = mSecurityViewFlipper.getChildCount(); + for (int child = 0; child < children; child++) { + if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) { + view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child)); + break; + } + } + int layoutId = getLayoutIdFor(securityMode); + if (view == null && layoutId != 0) { + final LayoutInflater inflater = LayoutInflater.from(mContext); + if (DEBUG) Log.v(TAG, "inflating id = " + layoutId); + View v = inflater.inflate(layoutId, mSecurityViewFlipper, false); + mSecurityViewFlipper.addView(v); + updateSecurityView(v, mIsBouncing); + view = (KeyguardSecurityView)v; + } + + if (view instanceof KeyguardSelectorView) { + KeyguardSelectorView selectorView = (KeyguardSelectorView) view; + View carrierText = selectorView.findViewById(R.id.keyguard_selector_fade_container); + selectorView.setCarrierArea(carrierText); + } + + return view; + } + + private void updateSecurityView(View view, boolean isBouncing) { + mIsBouncing = isBouncing; + if (view instanceof KeyguardSecurityView) { + KeyguardSecurityView ksv = (KeyguardSecurityView) view; + ksv.setKeyguardCallback(mCallback); + ksv.setLockPatternUtils(mLockPatternUtils); + if (isBouncing) { + ksv.showBouncer(0); + } else { + ksv.hideBouncer(0); + } + } else { + Log.w(TAG, "View " + view + " is not a KeyguardSecurityView"); + } + } + + protected void onFinishInflate() { + mSecurityViewFlipper = (KeyguardSecurityViewFlipper) findViewById(R.id.view_flipper); + mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils); + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + mSecurityModel.setLockPatternUtils(utils); + mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils); + } + + private void showDialog(String title, String message) { + final AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(title) + .setMessage(message) + .setNeutralButton(R.string.ok, null) + .create(); + if (!(mContext instanceof Activity)) { + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + dialog.show(); + } + + private void showTimeoutDialog() { + int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; + int messageId = 0; + + switch (mSecurityModel.getSecurityMode()) { + case Pattern: + messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message; + break; + case PIN: + messageId = R.string.kg_too_many_failed_pin_attempts_dialog_message; + break; + case Password: + messageId = R.string.kg_too_many_failed_password_attempts_dialog_message; + break; + // These don't have timeout dialogs. + case Account: + case Biometric: + case Invalid: + case None: + case SimPin: + case SimPuk: + break; + } + + if (messageId != 0) { + final String message = mContext.getString(messageId, + KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(), + timeoutInSeconds); + showDialog(null, message); + } + } + + private void showAlmostAtWipeDialog(int attempts, int remaining) { + String message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe, + attempts, remaining); + showDialog(null, message); + } + + private void showWipeDialog(int attempts) { + String message = mContext.getString(R.string.kg_failed_attempts_now_wiping, attempts); + showDialog(null, message); + } + + private void showAlmostAtAccountLoginDialog() { + final int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; + final int count = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET + - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT; + String message = mContext.getString(R.string.kg_failed_attempts_almost_at_login, + count, LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, timeoutInSeconds); + showDialog(null, message); } - KeyguardSecurityViewFlipper getFlipper() { + private void reportFailedUnlockAttempt() { + final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); + final int failedAttempts = monitor.getFailedUnlockAttempts() + 1; // +1 for this time + + if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); + + SecurityMode mode = mSecurityModel.getSecurityMode(); + final boolean usingPattern = mode == KeyguardSecurityModel.SecurityMode.Pattern; + + final int failedAttemptsBeforeWipe = mLockPatternUtils.getDevicePolicyManager() + .getMaximumFailedPasswordsForWipe(null, mLockPatternUtils.getCurrentUser()); + + final int failedAttemptWarning = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET + - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT; + + final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ? + (failedAttemptsBeforeWipe - failedAttempts) + : Integer.MAX_VALUE; // because DPM returns 0 if no restriction + + boolean showTimeout = false; + if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) { + // If we reach this code, it means the user has installed a DevicePolicyManager + // that requests device wipe after N attempts. Once we get below the grace + // period, we'll post this dialog every time as a clear warning until the + // bombshell hits and the device is wiped. + if (remainingBeforeWipe > 0) { + showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe); + } else { + // Too many attempts. The device will be wiped shortly. + Slog.i(TAG, "Too many unlock attempts; device will be wiped!"); + showWipeDialog(failedAttempts); + } + } else { + showTimeout = + (failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) == 0; + if (usingPattern && mEnableFallback) { + if (failedAttempts == failedAttemptWarning) { + showAlmostAtAccountLoginDialog(); + showTimeout = false; // don't show both dialogs + } else if (failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) { + mLockPatternUtils.setPermanentlyLocked(true); + showSecurityScreen(SecurityMode.Account); + // don't show timeout dialog because we show account unlock screen next + showTimeout = false; + } + } + } + monitor.reportFailedUnlockAttempt(); + mLockPatternUtils.reportFailedPasswordAttempt(); + if (showTimeout) { + showTimeoutDialog(); + } + } + + /** + * Shows the primary security screen for the user. This will be either the multi-selector + * or the user's security method. + * @param turningOff true if the device is being turned off + */ + void showPrimarySecurityScreen(boolean turningOff) { + SecurityMode securityMode = mSecurityModel.getSecurityMode(); + if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); + if (!turningOff && + KeyguardUpdateMonitor.getInstance(mContext).isAlternateUnlockEnabled()) { + // If we're not turning off, then allow biometric alternate. + // We'll reload it when the device comes back on. + securityMode = mSecurityModel.getAlternateFor(securityMode); + } + showSecurityScreen(securityMode); + } + + /** + * Shows the backup security screen for the current security mode. This could be used for + * password recovery screens but is currently only used for pattern unlock to show the + * account unlock screen and biometric unlock to show the user's normal unlock. + */ + private void showBackupSecurityScreen() { + if (DEBUG) Log.d(TAG, "showBackupSecurity()"); + SecurityMode backup = mSecurityModel.getBackupSecurityMode(mCurrentSecuritySelection); + showSecurityScreen(backup); + } + + private boolean showNextSecurityScreenIfPresent() { + SecurityMode securityMode = mSecurityModel.getSecurityMode(); + // Allow an alternate, such as biometric unlock + securityMode = mSecurityModel.getAlternateFor(securityMode); + if (SecurityMode.None == securityMode) { + return false; + } else { + showSecurityScreen(securityMode); // switch to the alternate security view + return true; + } + } + + boolean showNextSecurityScreenOrFinish(boolean authenticated) { + if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); + boolean finish = false; + if (SecurityMode.None == mCurrentSecuritySelection) { + SecurityMode securityMode = mSecurityModel.getSecurityMode(); + // Allow an alternate, such as biometric unlock + securityMode = mSecurityModel.getAlternateFor(securityMode); + if (SecurityMode.None == securityMode) { + finish = true; // no security required + } else { + showSecurityScreen(securityMode); // switch to the alternate security view + } + } else if (authenticated) { + switch (mCurrentSecuritySelection) { + case Pattern: + case Password: + case PIN: + case Account: + case Biometric: + finish = true; + break; + + case SimPin: + case SimPuk: + // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home + SecurityMode securityMode = mSecurityModel.getSecurityMode(); + if (securityMode != SecurityMode.None) { + showSecurityScreen(securityMode); + } else { + finish = true; + } + break; + + default: + Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe"); + showPrimarySecurityScreen(false); + break; + } + } else { + showPrimarySecurityScreen(false); + } + if (finish) { + mSecurityCallback.finish(); + } + return finish; + } + + /** + * Switches to the given security view unless it's already being shown, in which case + * this is a no-op. + * + * @param securityMode + */ + private void showSecurityScreen(SecurityMode securityMode) { + if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")"); + + if (securityMode == mCurrentSecuritySelection) return; + + KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection); + KeyguardSecurityView newView = getSecurityView(securityMode); + + // Emulate Activity life cycle + if (oldView != null) { + oldView.onPause(); + oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view + } + newView.onResume(KeyguardSecurityView.VIEW_REVEALED); + newView.setKeyguardCallback(mCallback); + + // Find and show this child. + final int childCount = mSecurityViewFlipper.getChildCount(); + + final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); + for (int i = 0; i < childCount; i++) { + if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) { + mSecurityViewFlipper.setDisplayedChild(i); + break; + } + } + + mCurrentSecuritySelection = securityMode; + } + + private KeyguardSecurityViewFlipper getFlipper() { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (child instanceof KeyguardSecurityViewFlipper) { @@ -41,5 +396,129 @@ public class KeyguardSecurityContainer extends FrameLayout { flipper.hideBouncer(duration); } } + + private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() { + + public void userActivity(long timeout) { + if (mSecurityCallback != null) { + mSecurityCallback.userActivity(timeout); + } + } + + public void dismiss(boolean authenticated) { + mSecurityCallback.dismiss(authenticated); + } + + public boolean isVerifyUnlockOnly() { + return mIsVerifyUnlockOnly; + } + + public void reportUnlockAttempt(boolean success) { + KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); + if (success) { + monitor.clearFailedUnlockAttempts(); + mLockPatternUtils.reportSuccessfulPasswordAttempt(); + } else { + if (mCurrentSecuritySelection == SecurityMode.Biometric) { + monitor.reportFailedBiometricUnlockAttempt(); + } else { + KeyguardSecurityContainer.this.reportFailedUnlockAttempt(); + } + } + } + + @Override + public void showBackupSecurity() { + KeyguardSecurityContainer.this.showBackupSecurityScreen(); + } + + }; + + // The following is used to ignore callbacks from SecurityViews that are no longer current + // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the + // state for the current security method. + private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() { + @Override + public void userActivity(long timeout) { } + @Override + public void showBackupSecurity() { } + @Override + public void reportUnlockAttempt(boolean success) { } + @Override + public boolean isVerifyUnlockOnly() { return false; } + @Override + public void dismiss(boolean securityVerified) { } + }; + + private int getSecurityViewIdForMode(SecurityMode securityMode) { + switch (securityMode) { + case None: return R.id.keyguard_selector_view; + case Pattern: return R.id.keyguard_pattern_view; + case PIN: return R.id.keyguard_pin_view; + case Password: return R.id.keyguard_password_view; + case Biometric: return R.id.keyguard_face_unlock_view; + case Account: return R.id.keyguard_account_view; + case SimPin: return R.id.keyguard_sim_pin_view; + case SimPuk: return R.id.keyguard_sim_puk_view; + } + return 0; + } + + private int getLayoutIdFor(SecurityMode securityMode) { + switch (securityMode) { + case None: return R.layout.keyguard_selector_view; + case Pattern: return R.layout.keyguard_pattern_view; + case PIN: return R.layout.keyguard_pin_view; + case Password: return R.layout.keyguard_password_view; + case Biometric: return R.layout.keyguard_face_unlock_view; + case Account: return R.layout.keyguard_account_view; + case SimPin: return R.layout.keyguard_sim_pin_view; + case SimPuk: return R.layout.keyguard_sim_puk_view; + default: + return 0; + } + } + + public SecurityMode getSecurityMode() { + return mSecurityModel.getSecurityMode(); + } + + public void verifyUnlock() { + mIsVerifyUnlockOnly = true; + showSecurityScreen(getSecurityMode()); + } + + public SecurityMode getCurrentSecuritySelection() { + return mCurrentSecuritySelection; + } + + public void dismiss(boolean authenticated) { + mCallback.dismiss(authenticated); + } + + public boolean needsInput() { + return mSecurityViewFlipper.needsInput(); + } + + @Override + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mSecurityViewFlipper.setKeyguardCallback(callback); + } + + @Override + public void reset() { + mSecurityViewFlipper.reset(); + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mSecurityViewFlipper.getCallback(); + } + + @Override + public void showUsabilityHint() { + mSecurityViewFlipper.showUsabilityHint(); + } + } |