| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.keyguard; |
| |
| import android.R.style; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.admin.DevicePolicyManager; |
| import android.content.Context; |
| import android.os.UserHandle; |
| import android.support.annotation.VisibleForTesting; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.view.ContextThemeWrapper; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.WindowManager; |
| import android.widget.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 = KeyguardConstants.DEBUG; |
| private static final String TAG = "KeyguardSecurityView"; |
| |
| private static final int USER_TYPE_PRIMARY = 1; |
| private static final int USER_TYPE_WORK_PROFILE = 2; |
| private static final int USER_TYPE_SECONDARY_USER = 3; |
| |
| private KeyguardSecurityModel mSecurityModel; |
| private LockPatternUtils mLockPatternUtils; |
| |
| private KeyguardSecurityViewFlipper mSecurityViewFlipper; |
| private boolean mIsVerifyUnlockOnly; |
| private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid; |
| private SecurityCallback mSecurityCallback; |
| private AlertDialog mAlertDialog; |
| |
| private final KeyguardUpdateMonitor mUpdateMonitor; |
| |
| // Used to notify the container when something interesting happens. |
| public interface SecurityCallback { |
| public boolean dismiss(boolean authenticated, int targetUserId); |
| public void userActivity(); |
| public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); |
| |
| /** |
| * @param strongAuth wheher the user has authenticated with strong authentication like |
| * pattern, password or PIN but not by trust agents or fingerprint |
| * @param targetUserId a user that needs to be the foreground user at the finish completion. |
| */ |
| public void finish(boolean strongAuth, int targetUserId); |
| public void reset(); |
| } |
| |
| public KeyguardSecurityContainer(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public KeyguardSecurityContainer(Context context) { |
| this(context, null, 0); |
| } |
| |
| public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| mSecurityModel = new KeyguardSecurityModel(context); |
| mLockPatternUtils = new LockPatternUtils(context); |
| mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); |
| } |
| |
| public void setSecurityCallback(SecurityCallback callback) { |
| mSecurityCallback = callback; |
| } |
| |
| @Override |
| public void onResume(int reason) { |
| if (mCurrentSecuritySelection != SecurityMode.None) { |
| getSecurityView(mCurrentSecuritySelection).onResume(reason); |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| if (mAlertDialog != null) { |
| mAlertDialog.dismiss(); |
| mAlertDialog = null; |
| } |
| if (mCurrentSecuritySelection != SecurityMode.None) { |
| getSecurityView(mCurrentSecuritySelection).onPause(); |
| } |
| } |
| |
| public void startAppearAnimation() { |
| if (mCurrentSecuritySelection != SecurityMode.None) { |
| getSecurityView(mCurrentSecuritySelection).startAppearAnimation(); |
| } |
| } |
| |
| public boolean startDisappearAnimation(Runnable onFinishRunnable) { |
| if (mCurrentSecuritySelection != SecurityMode.None) { |
| return getSecurityView(mCurrentSecuritySelection).startDisappearAnimation( |
| onFinishRunnable); |
| } |
| return false; |
| } |
| |
| public void announceCurrentSecurityMethod() { |
| View v = (View) getSecurityView(mCurrentSecuritySelection); |
| if (v != null) { |
| v.announceForAccessibility(v.getContentDescription()); |
| } |
| } |
| |
| public CharSequence getCurrentSecurityModeContentDescription() { |
| View v = (View) getSecurityView(mCurrentSecuritySelection); |
| if (v != null) { |
| return v.getContentDescription(); |
| } |
| return ""; |
| } |
| |
| 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); |
| view = (KeyguardSecurityView)v; |
| } |
| |
| return view; |
| } |
| |
| private void updateSecurityView(View view) { |
| if (view instanceof KeyguardSecurityView) { |
| KeyguardSecurityView ksv = (KeyguardSecurityView) view; |
| ksv.setKeyguardCallback(mCallback); |
| ksv.setLockPatternUtils(mLockPatternUtils); |
| } else { |
| Log.w(TAG, "View " + view + " is not a KeyguardSecurityView"); |
| } |
| } |
| |
| protected void onFinishInflate() { |
| mSecurityViewFlipper = 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) { |
| if (mAlertDialog != null) { |
| mAlertDialog.dismiss(); |
| } |
| |
| mAlertDialog = new AlertDialog.Builder(mContext) |
| .setTitle(title) |
| .setMessage(message) |
| .setCancelable(false) |
| .setNeutralButton(R.string.ok, null) |
| .create(); |
| if (!(mContext instanceof Activity)) { |
| mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); |
| } |
| mAlertDialog.show(); |
| } |
| |
| private void showTimeoutDialog(int userId, int timeoutMs) { |
| int timeoutInSeconds = (int) timeoutMs / 1000; |
| int messageId = 0; |
| |
| switch (mSecurityModel.getSecurityMode(userId)) { |
| 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 Invalid: |
| case None: |
| case SimPin: |
| case SimPuk: |
| break; |
| } |
| |
| if (messageId != 0) { |
| final String message = mContext.getString(messageId, |
| KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(userId), |
| timeoutInSeconds); |
| showDialog(null, message); |
| } |
| } |
| |
| private void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { |
| String message = null; |
| switch (userType) { |
| case USER_TYPE_PRIMARY: |
| message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe, |
| attempts, remaining); |
| break; |
| case USER_TYPE_SECONDARY_USER: |
| message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_user, |
| attempts, remaining); |
| break; |
| case USER_TYPE_WORK_PROFILE: |
| message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_profile, |
| attempts, remaining); |
| break; |
| } |
| showDialog(null, message); |
| } |
| |
| private void showWipeDialog(int attempts, int userType) { |
| String message = null; |
| switch (userType) { |
| case USER_TYPE_PRIMARY: |
| message = mContext.getString(R.string.kg_failed_attempts_now_wiping, |
| attempts); |
| break; |
| case USER_TYPE_SECONDARY_USER: |
| message = mContext.getString(R.string.kg_failed_attempts_now_erasing_user, |
| attempts); |
| break; |
| case USER_TYPE_WORK_PROFILE: |
| message = mContext.getString(R.string.kg_failed_attempts_now_erasing_profile, |
| attempts); |
| break; |
| } |
| showDialog(null, message); |
| } |
| |
| private void reportFailedUnlockAttempt(int userId, int timeoutMs) { |
| final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); |
| final int failedAttempts = monitor.getFailedUnlockAttempts(userId) + 1; // +1 for this time |
| |
| if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); |
| |
| final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager(); |
| final int failedAttemptsBeforeWipe = |
| dpm.getMaximumFailedPasswordsForWipe(null, userId); |
| |
| final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ? |
| (failedAttemptsBeforeWipe - failedAttempts) |
| : Integer.MAX_VALUE; // because DPM returns 0 if no restriction |
| if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) { |
| // The user has installed a DevicePolicyManager that requests a user/profile to be wiped |
| // N attempts. Once we get below the grace period, we post this dialog every time as a |
| // clear warning until the deletion fires. |
| // Check which profile has the strictest policy for failed password attempts |
| final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId); |
| int userType = USER_TYPE_PRIMARY; |
| if (expiringUser == userId) { |
| // TODO: http://b/23522538 |
| if (expiringUser != UserHandle.USER_SYSTEM) { |
| userType = USER_TYPE_SECONDARY_USER; |
| } |
| } else if (expiringUser != UserHandle.USER_NULL) { |
| userType = USER_TYPE_WORK_PROFILE; |
| } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY |
| if (remainingBeforeWipe > 0) { |
| showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType); |
| } else { |
| // Too many attempts. The device will be wiped shortly. |
| Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!"); |
| showWipeDialog(failedAttempts, userType); |
| } |
| } |
| monitor.reportFailedStrongAuthUnlockAttempt(userId); |
| mLockPatternUtils.reportFailedPasswordAttempt(userId); |
| if (timeoutMs > 0) { |
| mLockPatternUtils.reportPasswordLockout(timeoutMs, userId); |
| showTimeoutDialog(userId, timeoutMs); |
| } |
| } |
| |
| /** |
| * 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( |
| KeyguardUpdateMonitor.getCurrentUser()); |
| if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); |
| showSecurityScreen(securityMode); |
| } |
| |
| /** |
| * Shows the next security screen if there is one. |
| * @param authenticated true if the user entered the correct authentication |
| * @param targetUserId a user that needs to be the foreground user at the finish (if called) |
| * completion. |
| * @return true if keyguard is done |
| */ |
| boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId) { |
| if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); |
| boolean finish = false; |
| boolean strongAuth = false; |
| if (mUpdateMonitor.getUserCanSkipBouncer(targetUserId)) { |
| finish = true; |
| } else if (SecurityMode.None == mCurrentSecuritySelection) { |
| SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); |
| 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: |
| strongAuth = true; |
| 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(targetUserId); |
| if (securityMode != SecurityMode.None |
| || !mLockPatternUtils.isLockScreenDisabled( |
| KeyguardUpdateMonitor.getCurrentUser())) { |
| showSecurityScreen(securityMode); |
| } else { |
| finish = true; |
| } |
| break; |
| |
| default: |
| Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe"); |
| showPrimarySecurityScreen(false); |
| break; |
| } |
| } |
| if (finish) { |
| mSecurityCallback.finish(strongAuth, targetUserId); |
| } |
| 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 |
| } |
| if (securityMode != SecurityMode.None) { |
| 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; |
| mSecurityCallback.onSecurityModeChanged(securityMode, |
| securityMode != SecurityMode.None && newView.needsInput()); |
| } |
| |
| private KeyguardSecurityViewFlipper getFlipper() { |
| for (int i = 0; i < getChildCount(); i++) { |
| View child = getChildAt(i); |
| if (child instanceof KeyguardSecurityViewFlipper) { |
| return (KeyguardSecurityViewFlipper) child; |
| } |
| } |
| return null; |
| } |
| |
| private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() { |
| public void userActivity() { |
| if (mSecurityCallback != null) { |
| mSecurityCallback.userActivity(); |
| } |
| } |
| |
| public void dismiss(boolean authenticated, int targetId) { |
| mSecurityCallback.dismiss(authenticated, targetId); |
| } |
| |
| public boolean isVerifyUnlockOnly() { |
| return mIsVerifyUnlockOnly; |
| } |
| |
| public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { |
| KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); |
| if (success) { |
| monitor.clearFailedUnlockAttempts(); |
| mLockPatternUtils.reportSuccessfulPasswordAttempt(userId); |
| } else { |
| KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs); |
| } |
| } |
| |
| public void reset() { |
| mSecurityCallback.reset(); |
| } |
| }; |
| |
| // 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() { } |
| @Override |
| public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { } |
| @Override |
| public boolean isVerifyUnlockOnly() { return false; } |
| @Override |
| public void dismiss(boolean securityVerified, int targetUserId) { } |
| @Override |
| public void reset() {} |
| }; |
| |
| private int getSecurityViewIdForMode(SecurityMode securityMode) { |
| switch (securityMode) { |
| 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 SimPin: return R.id.keyguard_sim_pin_view; |
| case SimPuk: return R.id.keyguard_sim_puk_view; |
| } |
| return 0; |
| } |
| |
| @VisibleForTesting |
| public int getLayoutIdFor(SecurityMode securityMode) { |
| switch (securityMode) { |
| 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 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(KeyguardUpdateMonitor.getCurrentUser()); |
| } |
| |
| public SecurityMode getCurrentSecurityMode() { |
| return mCurrentSecuritySelection; |
| } |
| |
| public void verifyUnlock() { |
| mIsVerifyUnlockOnly = true; |
| showSecurityScreen(getSecurityMode()); |
| } |
| |
| public SecurityMode getCurrentSecuritySelection() { |
| return mCurrentSecuritySelection; |
| } |
| |
| public void dismiss(boolean authenticated, int targetUserId) { |
| mCallback.dismiss(authenticated, targetUserId); |
| } |
| |
| 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 showPromptReason(int reason) { |
| if (mCurrentSecuritySelection != SecurityMode.None) { |
| if (reason != PROMPT_REASON_NONE) { |
| Log.i(TAG, "Strong auth required, reason: " + reason); |
| } |
| getSecurityView(mCurrentSecuritySelection).showPromptReason(reason); |
| } |
| } |
| |
| |
| public void showMessage(String message, int color) { |
| if (mCurrentSecuritySelection != SecurityMode.None) { |
| getSecurityView(mCurrentSecuritySelection).showMessage(message, color); |
| } |
| } |
| |
| @Override |
| public void showUsabilityHint() { |
| mSecurityViewFlipper.showUsabilityHint(); |
| } |
| |
| } |
| |