| /* |
| * 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 static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; |
| import static android.view.ViewRootImpl.sNewInsetsMode; |
| import static android.view.WindowInsets.Type.ime; |
| import static android.view.WindowInsets.Type.systemBars; |
| import static com.android.systemui.DejankUtils.whitelistIpcs; |
| import static java.lang.Integer.max; |
| |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.admin.DevicePolicyManager; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.ColorStateList; |
| import android.graphics.Rect; |
| import android.metrics.LogMaker; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.UserHandle; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.util.TypedValue; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.VelocityTracker; |
| import android.view.View; |
| import android.view.ViewConfiguration; |
| import android.view.WindowInsets; |
| import android.view.WindowManager; |
| import android.widget.FrameLayout; |
| |
| import androidx.annotation.VisibleForTesting; |
| import androidx.dynamicanimation.animation.DynamicAnimation; |
| import androidx.dynamicanimation.animation.SpringAnimation; |
| |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.internal.widget.LockPatternUtils; |
| import com.android.keyguard.KeyguardSecurityModel.SecurityMode; |
| import com.android.settingslib.utils.ThreadUtils; |
| import com.android.systemui.Dependency; |
| import com.android.systemui.R; |
| import com.android.systemui.SystemUIFactory; |
| import com.android.systemui.shared.system.SysUiStatsLog; |
| import com.android.systemui.statusbar.policy.KeyguardStateController; |
| import com.android.systemui.util.InjectionInflationController; |
| |
| 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; |
| |
| // Bouncer is dismissed due to no security. |
| private static final int BOUNCER_DISMISS_NONE_SECURITY = 0; |
| // Bouncer is dismissed due to pin, password or pattern entered. |
| private static final int BOUNCER_DISMISS_PASSWORD = 1; |
| // Bouncer is dismissed due to biometric (face, fingerprint or iris) authenticated. |
| private static final int BOUNCER_DISMISS_BIOMETRIC = 2; |
| // Bouncer is dismissed due to extended access granted. |
| private static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3; |
| // Bouncer is dismissed due to sim card unlock code entered. |
| private static final int BOUNCER_DISMISS_SIM = 4; |
| |
| // Make the view move slower than the finger, as if the spring were applying force. |
| private static final float TOUCH_Y_MULTIPLIER = 0.25f; |
| // How much you need to drag the bouncer to trigger an auth retry (in dps.) |
| private static final float MIN_DRAG_SIZE = 10; |
| // How much to scale the default slop by, to avoid accidental drags. |
| private static final float SLOP_SCALE = 4f; |
| |
| private KeyguardSecurityModel mSecurityModel; |
| private LockPatternUtils mLockPatternUtils; |
| |
| private KeyguardSecurityViewFlipper mSecurityViewFlipper; |
| private boolean mIsVerifyUnlockOnly; |
| private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid; |
| private KeyguardSecurityView mCurrentSecurityView; |
| private SecurityCallback mSecurityCallback; |
| private AlertDialog mAlertDialog; |
| private InjectionInflationController mInjectionInflationController; |
| private boolean mSwipeUpToRetry; |
| private AdminSecondaryLockScreenController mSecondaryLockScreenController; |
| |
| private final ViewConfiguration mViewConfiguration; |
| private final SpringAnimation mSpringAnimation; |
| private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); |
| private final KeyguardUpdateMonitor mUpdateMonitor; |
| private final KeyguardStateController mKeyguardStateController; |
| |
| private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); |
| private float mLastTouchY = -1; |
| private int mActivePointerId = -1; |
| private boolean mIsDragging; |
| private float mStartTouchY = -1; |
| |
| // 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 void onCancelClicked(); |
| } |
| |
| 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 = Dependency.get(KeyguardSecurityModel.class); |
| mLockPatternUtils = new LockPatternUtils(context); |
| mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); |
| mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y); |
| mInjectionInflationController = new InjectionInflationController( |
| SystemUIFactory.getInstance().getRootComponent()); |
| mViewConfiguration = ViewConfiguration.get(context); |
| mKeyguardStateController = Dependency.get(KeyguardStateController.class); |
| mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this, |
| mUpdateMonitor, mCallback, new Handler(Looper.myLooper())); |
| } |
| |
| public void setSecurityCallback(SecurityCallback callback) { |
| mSecurityCallback = callback; |
| } |
| |
| @Override |
| public void onResume(int reason) { |
| if (mCurrentSecuritySelection != SecurityMode.None) { |
| getSecurityView(mCurrentSecuritySelection).onResume(reason); |
| } |
| updateBiometricRetry(); |
| } |
| |
| @Override |
| public void onPause() { |
| if (mAlertDialog != null) { |
| mAlertDialog.dismiss(); |
| mAlertDialog = null; |
| } |
| mSecondaryLockScreenController.hide(); |
| if (mCurrentSecuritySelection != SecurityMode.None) { |
| getSecurityView(mCurrentSecuritySelection).onPause(); |
| } |
| } |
| |
| @Override |
| public boolean shouldDelayChildPressedState() { |
| return true; |
| } |
| |
| @Override |
| public boolean onInterceptTouchEvent(MotionEvent event) { |
| switch (event.getActionMasked()) { |
| case MotionEvent.ACTION_DOWN: |
| int pointerIndex = event.getActionIndex(); |
| mStartTouchY = event.getY(pointerIndex); |
| mActivePointerId = event.getPointerId(pointerIndex); |
| mVelocityTracker.clear(); |
| break; |
| case MotionEvent.ACTION_MOVE: |
| if (mIsDragging) { |
| return true; |
| } |
| if (!mSwipeUpToRetry) { |
| return false; |
| } |
| // Avoid dragging the pattern view |
| if (mCurrentSecurityView.disallowInterceptTouch(event)) { |
| return false; |
| } |
| int index = event.findPointerIndex(mActivePointerId); |
| float touchSlop = mViewConfiguration.getScaledTouchSlop() * SLOP_SCALE; |
| if (mCurrentSecurityView != null && index != -1 |
| && mStartTouchY - event.getY(index) > touchSlop) { |
| mIsDragging = true; |
| return true; |
| } |
| break; |
| case MotionEvent.ACTION_CANCEL: |
| case MotionEvent.ACTION_UP: |
| mIsDragging = false; |
| break; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| final int action = event.getActionMasked(); |
| switch (action) { |
| case MotionEvent.ACTION_MOVE: |
| mVelocityTracker.addMovement(event); |
| int pointerIndex = event.findPointerIndex(mActivePointerId); |
| float y = event.getY(pointerIndex); |
| if (mLastTouchY != -1) { |
| float dy = y - mLastTouchY; |
| setTranslationY(getTranslationY() + dy * TOUCH_Y_MULTIPLIER); |
| } |
| mLastTouchY = y; |
| break; |
| case MotionEvent.ACTION_UP: |
| case MotionEvent.ACTION_CANCEL: |
| mActivePointerId = -1; |
| mLastTouchY = -1; |
| mIsDragging = false; |
| startSpringAnimation(mVelocityTracker.getYVelocity()); |
| break; |
| case MotionEvent.ACTION_POINTER_UP: |
| int index = event.getActionIndex(); |
| int pointerId = event.getPointerId(index); |
| if (pointerId == mActivePointerId) { |
| // This was our active pointer going up. Choose a new |
| // active pointer and adjust accordingly. |
| final int newPointerIndex = index == 0 ? 1 : 0; |
| mLastTouchY = event.getY(newPointerIndex); |
| mActivePointerId = event.getPointerId(newPointerIndex); |
| } |
| break; |
| } |
| if (action == MotionEvent.ACTION_UP) { |
| if (-getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, |
| MIN_DRAG_SIZE, getResources().getDisplayMetrics()) |
| && !mUpdateMonitor.isFaceDetectionRunning()) { |
| mUpdateMonitor.requestFaceAuth(); |
| mCallback.userActivity(); |
| showMessage(null, null); |
| } |
| } |
| return true; |
| } |
| |
| private void startSpringAnimation(float startVelocity) { |
| mSpringAnimation |
| .setStartVelocity(startVelocity) |
| .animateToFinalPosition(0); |
| } |
| |
| 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; |
| } |
| |
| /** |
| * Enables/disables swipe up to retry on the bouncer. |
| */ |
| private void updateBiometricRetry() { |
| SecurityMode securityMode = getSecurityMode(); |
| mSwipeUpToRetry = mKeyguardStateController.isFaceAuthEnabled() |
| && securityMode != SecurityMode.SimPin |
| && securityMode != SecurityMode.SimPuk |
| && securityMode != SecurityMode.None; |
| } |
| |
| public CharSequence getTitle() { |
| return mSecurityViewFlipper.getTitle(); |
| } |
| |
| 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 = mInjectionInflationController.injectable(inflater) |
| .inflate(layoutId, mSecurityViewFlipper, false); |
| mSecurityViewFlipper.addView(v); |
| updateSecurityView(v); |
| view = (KeyguardSecurityView)v; |
| view.reset(); |
| } |
| |
| 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); |
| } |
| |
| @Override |
| public WindowInsets onApplyWindowInsets(WindowInsets insets) { |
| |
| // Consume bottom insets because we're setting the padding locally (for IME and navbar.) |
| int inset; |
| if (sNewInsetsMode == NEW_INSETS_MODE_FULL) { |
| int bottomInset = insets.getInsetsIgnoringVisibility(systemBars()).bottom; |
| int imeInset = insets.getInsets(ime()).bottom; |
| inset = max(bottomInset, imeInset); |
| } else { |
| inset = insets.getSystemWindowInsetBottom(); |
| } |
| setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), inset); |
| return insets.inset(0, 0, 0, inset); |
| } |
| |
| |
| 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, |
| mLockPatternUtils.getCurrentFailedPasswordAttempts(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) { |
| // +1 for this time |
| final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1; |
| |
| 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); |
| } |
| } |
| 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 = whitelistIpcs(() -> 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; |
| int eventSubtype = -1; |
| if (mUpdateMonitor.getUserHasTrust(targetUserId)) { |
| finish = true; |
| eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS; |
| } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) { |
| finish = true; |
| eventSubtype = BOUNCER_DISMISS_BIOMETRIC; |
| } else if (SecurityMode.None == mCurrentSecuritySelection) { |
| SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); |
| if (SecurityMode.None == securityMode) { |
| finish = true; // no security required |
| eventSubtype = BOUNCER_DISMISS_NONE_SECURITY; |
| } else { |
| showSecurityScreen(securityMode); // switch to the alternate security view |
| } |
| } else if (authenticated) { |
| switch (mCurrentSecuritySelection) { |
| case Pattern: |
| case Password: |
| case PIN: |
| strongAuth = true; |
| finish = true; |
| eventSubtype = BOUNCER_DISMISS_PASSWORD; |
| 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())) { |
| finish = true; |
| eventSubtype = BOUNCER_DISMISS_SIM; |
| } else { |
| showSecurityScreen(securityMode); |
| } |
| break; |
| |
| default: |
| Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe"); |
| showPrimarySecurityScreen(false); |
| break; |
| } |
| } |
| // Check for device admin specified additional security measures. |
| if (finish) { |
| Intent secondaryLockscreenIntent = |
| mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId); |
| if (secondaryLockscreenIntent != null) { |
| mSecondaryLockScreenController.show(secondaryLockscreenIntent); |
| return false; |
| } |
| } |
| if (eventSubtype != -1) { |
| mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) |
| .setType(MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype)); |
| } |
| 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; |
| mCurrentSecurityView = newView; |
| 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(); |
| } |
| } |
| |
| @Override |
| public void onUserInput() { |
| mUpdateMonitor.cancelFaceAuth(); |
| } |
| |
| 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) { |
| if (success) { |
| SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, |
| SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS); |
| mLockPatternUtils.reportSuccessfulPasswordAttempt(userId); |
| // Force a garbage collection in an attempt to erase any lockscreen password left in |
| // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard |
| // dismiss animation janky. |
| ThreadUtils.postOnBackgroundThread(() -> { |
| try { |
| Thread.sleep(5000); |
| } catch (InterruptedException ignored) { } |
| Runtime.getRuntime().gc(); |
| }); |
| } else { |
| SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, |
| SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE); |
| KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs); |
| } |
| mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) |
| .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE)); |
| } |
| |
| public void reset() { |
| mSecurityCallback.reset(); |
| } |
| |
| public void onCancelClicked() { |
| mSecurityCallback.onCancelClicked(); |
| } |
| }; |
| |
| // 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 onUserInput() { } |
| @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 KeyguardSecurityView getCurrentSecurityView() { |
| return mCurrentSecurityView; |
| } |
| |
| 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(CharSequence message, ColorStateList colorState) { |
| if (mCurrentSecuritySelection != SecurityMode.None) { |
| getSecurityView(mCurrentSecuritySelection).showMessage(message, colorState); |
| } |
| } |
| |
| @Override |
| public void showUsabilityHint() { |
| mSecurityViewFlipper.showUsabilityHint(); |
| } |
| } |
| |