| /* |
| * Copyright (C) 2020 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 com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC; |
| import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS; |
| import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY; |
| import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD; |
| import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM; |
| import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY; |
| import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER; |
| import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE; |
| import static com.android.systemui.DejankUtils.whitelistIpcs; |
| |
| import android.app.admin.DevicePolicyManager; |
| import android.content.Intent; |
| import android.content.res.ColorStateList; |
| import android.content.res.Configuration; |
| import android.metrics.LogMaker; |
| import android.os.UserHandle; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.view.MotionEvent; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.UiEventLogger; |
| import com.android.internal.logging.nano.MetricsProto; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.internal.widget.LockPatternUtils; |
| import com.android.keyguard.KeyguardSecurityContainer.BouncerUiEvent; |
| import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback; |
| import com.android.keyguard.KeyguardSecurityContainer.SwipeListener; |
| import com.android.keyguard.KeyguardSecurityModel.SecurityMode; |
| import com.android.keyguard.dagger.KeyguardBouncerScope; |
| import com.android.settingslib.utils.ThreadUtils; |
| import com.android.systemui.Gefingerpoken; |
| import com.android.systemui.shared.system.SysUiStatsLog; |
| import com.android.systemui.statusbar.policy.ConfigurationController; |
| import com.android.systemui.statusbar.policy.KeyguardStateController; |
| import com.android.systemui.util.ViewController; |
| |
| import javax.inject.Inject; |
| |
| /** Controller for {@link KeyguardSecurityContainer} */ |
| @KeyguardBouncerScope |
| public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer> |
| implements KeyguardSecurityView { |
| |
| private static final boolean DEBUG = KeyguardConstants.DEBUG; |
| private static final String TAG = "KeyguardSecurityView"; |
| |
| private final AdminSecondaryLockScreenController mAdminSecondaryLockScreenController; |
| private final LockPatternUtils mLockPatternUtils; |
| private final KeyguardUpdateMonitor mUpdateMonitor; |
| private final KeyguardSecurityModel mSecurityModel; |
| private final MetricsLogger mMetricsLogger; |
| private final UiEventLogger mUiEventLogger; |
| private final KeyguardStateController mKeyguardStateController; |
| private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController; |
| private final SecurityCallback mSecurityCallback; |
| private final ConfigurationController mConfigurationController; |
| |
| private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; |
| |
| private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid; |
| |
| private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() { |
| private MotionEvent mTouchDown; |
| @Override |
| public boolean onInterceptTouchEvent(MotionEvent ev) { |
| return false; |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| // Do just a bit of our own falsing. People should only be tapping on the input, not |
| // swiping. |
| if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { |
| if (mTouchDown != null) { |
| mTouchDown.recycle(); |
| mTouchDown = null; |
| } |
| mTouchDown = MotionEvent.obtain(ev); |
| } else if (mTouchDown != null) { |
| if (ev.getActionMasked() == MotionEvent.ACTION_UP |
| || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { |
| mTouchDown.recycle(); |
| mTouchDown = null; |
| } |
| } |
| return false; |
| } |
| }; |
| |
| private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() { |
| public void userActivity() { |
| if (mSecurityCallback != null) { |
| mSecurityCallback.userActivity(); |
| } |
| } |
| |
| @Override |
| public void onUserInput() { |
| mUpdateMonitor.cancelFaceAuth(); |
| } |
| |
| @Override |
| public void dismiss(boolean authenticated, int targetId) { |
| dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false); |
| } |
| |
| @Override |
| public void dismiss(boolean authenticated, int targetId, |
| boolean bypassSecondaryLockScreen) { |
| mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen); |
| } |
| |
| public boolean isVerifyUnlockOnly() { |
| return false; |
| } |
| |
| 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) { } |
| System.gc(); |
| System.runFinalization(); |
| System.gc(); |
| }); |
| } else { |
| SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, |
| SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE); |
| reportFailedUnlockAttempt(userId, timeoutMs); |
| } |
| mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) |
| .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE)); |
| mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS |
| : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE); |
| } |
| |
| public void reset() { |
| mSecurityCallback.reset(); |
| } |
| |
| public void onCancelClicked() { |
| mSecurityCallback.onCancelClicked(); |
| } |
| }; |
| |
| |
| private SwipeListener mSwipeListener = new SwipeListener() { |
| @Override |
| public void onSwipeUp() { |
| if (!mUpdateMonitor.isFaceDetectionRunning()) { |
| mUpdateMonitor.requestFaceAuth(true); |
| mKeyguardSecurityCallback.userActivity(); |
| showMessage(null, null); |
| } |
| } |
| }; |
| private ConfigurationController.ConfigurationListener mConfigurationListener = |
| new ConfigurationController.ConfigurationListener() { |
| @Override |
| public void onOverlayChanged() { |
| mSecurityViewFlipperController.reloadColors(); |
| } |
| |
| @Override |
| public void onUiModeChanged() { |
| mSecurityViewFlipperController.reloadColors(); |
| } |
| }; |
| |
| private KeyguardSecurityContainerController(KeyguardSecurityContainer view, |
| AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory, |
| LockPatternUtils lockPatternUtils, |
| KeyguardUpdateMonitor keyguardUpdateMonitor, |
| KeyguardSecurityModel keyguardSecurityModel, |
| MetricsLogger metricsLogger, |
| UiEventLogger uiEventLogger, |
| KeyguardStateController keyguardStateController, |
| SecurityCallback securityCallback, |
| KeyguardSecurityViewFlipperController securityViewFlipperController, |
| ConfigurationController configurationController) { |
| super(view); |
| mLockPatternUtils = lockPatternUtils; |
| mUpdateMonitor = keyguardUpdateMonitor; |
| mSecurityModel = keyguardSecurityModel; |
| mMetricsLogger = metricsLogger; |
| mUiEventLogger = uiEventLogger; |
| mKeyguardStateController = keyguardStateController; |
| mSecurityCallback = securityCallback; |
| mSecurityViewFlipperController = securityViewFlipperController; |
| mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create( |
| mKeyguardSecurityCallback); |
| mConfigurationController = configurationController; |
| mLastOrientation = getResources().getConfiguration().orientation; |
| } |
| |
| @Override |
| public void onInit() { |
| mSecurityViewFlipperController.init(); |
| } |
| |
| @Override |
| protected void onViewAttached() { |
| mView.setSwipeListener(mSwipeListener); |
| mView.addMotionEventListener(mGlobalTouchListener); |
| mConfigurationController.addCallback(mConfigurationListener); |
| } |
| |
| @Override |
| protected void onViewDetached() { |
| mConfigurationController.removeCallback(mConfigurationListener); |
| mView.removeMotionEventListener(mGlobalTouchListener); |
| } |
| |
| /** */ |
| public void onPause() { |
| mAdminSecondaryLockScreenController.hide(); |
| if (mCurrentSecurityMode != SecurityMode.None) { |
| getCurrentSecurityController().onPause(); |
| } |
| mView.onPause(); |
| } |
| |
| |
| /** |
| * 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 |
| */ |
| public void showPrimarySecurityScreen(boolean turningOff) { |
| SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode( |
| KeyguardUpdateMonitor.getCurrentUser())); |
| if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); |
| showSecurityScreen(securityMode); |
| } |
| |
| @Override |
| public void showPromptReason(int reason) { |
| if (mCurrentSecurityMode != SecurityMode.None) { |
| if (reason != PROMPT_REASON_NONE) { |
| Log.i(TAG, "Strong auth required, reason: " + reason); |
| } |
| getCurrentSecurityController().showPromptReason(reason); |
| } |
| } |
| |
| public void showMessage(CharSequence message, ColorStateList colorState) { |
| if (mCurrentSecurityMode != SecurityMode.None) { |
| getCurrentSecurityController().showMessage(message, colorState); |
| } |
| } |
| |
| public SecurityMode getCurrentSecurityMode() { |
| return mCurrentSecurityMode; |
| } |
| |
| public void dismiss(boolean authenticated, int targetUserId) { |
| mKeyguardSecurityCallback.dismiss(authenticated, targetUserId); |
| } |
| |
| public void reset() { |
| mView.reset(); |
| mSecurityViewFlipperController.reset(); |
| } |
| |
| public CharSequence getTitle() { |
| return mView.getTitle(); |
| } |
| |
| @Override |
| public void onResume(int reason) { |
| if (mCurrentSecurityMode != SecurityMode.None) { |
| getCurrentSecurityController().onResume(reason); |
| } |
| mView.onResume( |
| mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()), |
| mKeyguardStateController.isFaceAuthEnabled()); |
| } |
| |
| public void startAppearAnimation() { |
| if (mCurrentSecurityMode != SecurityMode.None) { |
| getCurrentSecurityController().startAppearAnimation(); |
| } |
| } |
| |
| public boolean startDisappearAnimation(Runnable onFinishRunnable) { |
| mView.startDisappearAnimation(getCurrentSecurityMode()); |
| |
| if (mCurrentSecurityMode != SecurityMode.None) { |
| return getCurrentSecurityController().startDisappearAnimation(onFinishRunnable); |
| } |
| |
| return false; |
| } |
| |
| public void onStartingToHide() { |
| if (mCurrentSecurityMode != SecurityMode.None) { |
| getCurrentSecurityController().onStartingToHide(); |
| } |
| } |
| |
| /** |
| * 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. |
| * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary |
| * secondary lock screen requirement, if any. |
| * @return true if keyguard is done |
| */ |
| public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId, |
| boolean bypassSecondaryLockScreen) { |
| |
| if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); |
| boolean finish = false; |
| boolean strongAuth = false; |
| int eventSubtype = -1; |
| BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN; |
| if (mUpdateMonitor.getUserHasTrust(targetUserId)) { |
| finish = true; |
| eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS; |
| uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS; |
| } else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) { |
| finish = true; |
| eventSubtype = BOUNCER_DISMISS_BIOMETRIC; |
| uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC; |
| } else if (SecurityMode.None == getCurrentSecurityMode()) { |
| SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); |
| if (SecurityMode.None == securityMode) { |
| finish = true; // no security required |
| eventSubtype = BOUNCER_DISMISS_NONE_SECURITY; |
| uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY; |
| } else { |
| showSecurityScreen(securityMode); // switch to the alternate security view |
| } |
| } else if (authenticated) { |
| switch (getCurrentSecurityMode()) { |
| case Pattern: |
| case Password: |
| case PIN: |
| strongAuth = true; |
| finish = true; |
| eventSubtype = BOUNCER_DISMISS_PASSWORD; |
| uiEvent = BouncerUiEvent.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; |
| uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; |
| } else { |
| showSecurityScreen(securityMode); |
| } |
| break; |
| |
| default: |
| Log.v(TAG, "Bad security screen " + getCurrentSecurityMode() |
| + ", fail safe"); |
| showPrimarySecurityScreen(false); |
| break; |
| } |
| } |
| // Check for device admin specified additional security measures. |
| if (finish && !bypassSecondaryLockScreen) { |
| Intent secondaryLockscreenIntent = |
| mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId); |
| if (secondaryLockscreenIntent != null) { |
| mAdminSecondaryLockScreenController.show(secondaryLockscreenIntent); |
| return false; |
| } |
| } |
| if (eventSubtype != -1) { |
| mMetricsLogger.write(new LogMaker(MetricsProto.MetricsEvent.BOUNCER) |
| .setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype)); |
| } |
| if (uiEvent != BouncerUiEvent.UNKNOWN) { |
| mUiEventLogger.log(uiEvent); |
| } |
| if (finish) { |
| mSecurityCallback.finish(strongAuth, targetUserId); |
| } |
| return finish; |
| } |
| |
| public boolean needsInput() { |
| return getCurrentSecurityController().needsInput(); |
| } |
| |
| /** |
| * Switches to the given security view unless it's already being shown, in which case |
| * this is a no-op. |
| * |
| * @param securityMode |
| */ |
| @VisibleForTesting |
| void showSecurityScreen(SecurityMode securityMode) { |
| if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")"); |
| |
| if (securityMode == SecurityMode.Invalid || securityMode == mCurrentSecurityMode) { |
| return; |
| } |
| |
| KeyguardInputViewController<KeyguardInputView> oldView = getCurrentSecurityController(); |
| |
| // Emulate Activity life cycle |
| if (oldView != null) { |
| oldView.onPause(); |
| } |
| |
| KeyguardInputViewController<KeyguardInputView> newView = changeSecurityMode(securityMode); |
| if (newView != null) { |
| newView.onResume(KeyguardSecurityView.VIEW_REVEALED); |
| mSecurityViewFlipperController.show(newView); |
| mView.updateLayoutForSecurityMode(securityMode); |
| } |
| |
| mSecurityCallback.onSecurityModeChanged( |
| securityMode, newView != null && newView.needsInput()); |
| } |
| |
| public 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) { |
| mView.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!"); |
| mView.showWipeDialog(failedAttempts, userType); |
| } |
| } |
| mLockPatternUtils.reportFailedPasswordAttempt(userId); |
| if (timeoutMs > 0) { |
| mLockPatternUtils.reportPasswordLockout(timeoutMs, userId); |
| mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils, |
| mSecurityModel.getSecurityMode(userId)); |
| } |
| } |
| |
| private KeyguardInputViewController<KeyguardInputView> getCurrentSecurityController() { |
| return mSecurityViewFlipperController |
| .getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback); |
| } |
| |
| private KeyguardInputViewController<KeyguardInputView> changeSecurityMode( |
| SecurityMode securityMode) { |
| mCurrentSecurityMode = securityMode; |
| return getCurrentSecurityController(); |
| } |
| |
| /** |
| * Apply keyguard configuration from the currently active resources. This can be called when the |
| * device configuration changes, to re-apply some resources that are qualified on the device |
| * configuration. |
| */ |
| public void updateResources() { |
| int newOrientation = getResources().getConfiguration().orientation; |
| if (newOrientation != mLastOrientation) { |
| mLastOrientation = newOrientation; |
| mView.updateLayoutForSecurityMode(mCurrentSecurityMode); |
| } |
| } |
| |
| /** Update keyguard position based on a tapped X coordinate. */ |
| public void updateKeyguardPosition(float x) { |
| mView.updateKeyguardPosition(x); |
| } |
| |
| static class Factory { |
| |
| private final KeyguardSecurityContainer mView; |
| private final AdminSecondaryLockScreenController.Factory |
| mAdminSecondaryLockScreenControllerFactory; |
| private final LockPatternUtils mLockPatternUtils; |
| private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; |
| private final KeyguardSecurityModel mKeyguardSecurityModel; |
| private final MetricsLogger mMetricsLogger; |
| private final UiEventLogger mUiEventLogger; |
| private final KeyguardStateController mKeyguardStateController; |
| private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController; |
| private final ConfigurationController mConfigurationController; |
| |
| @Inject |
| Factory(KeyguardSecurityContainer view, |
| AdminSecondaryLockScreenController.Factory |
| adminSecondaryLockScreenControllerFactory, |
| LockPatternUtils lockPatternUtils, |
| KeyguardUpdateMonitor keyguardUpdateMonitor, |
| KeyguardSecurityModel keyguardSecurityModel, |
| MetricsLogger metricsLogger, |
| UiEventLogger uiEventLogger, |
| KeyguardStateController keyguardStateController, |
| KeyguardSecurityViewFlipperController securityViewFlipperController, |
| ConfigurationController configurationController) { |
| mView = view; |
| mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory; |
| mLockPatternUtils = lockPatternUtils; |
| mKeyguardUpdateMonitor = keyguardUpdateMonitor; |
| mKeyguardSecurityModel = keyguardSecurityModel; |
| mMetricsLogger = metricsLogger; |
| mUiEventLogger = uiEventLogger; |
| mKeyguardStateController = keyguardStateController; |
| mSecurityViewFlipperController = securityViewFlipperController; |
| mConfigurationController = configurationController; |
| } |
| |
| public KeyguardSecurityContainerController create( |
| SecurityCallback securityCallback) { |
| return new KeyguardSecurityContainerController(mView, |
| mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, |
| mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, |
| mKeyguardStateController, securityCallback, mSecurityViewFlipperController, |
| mConfigurationController); |
| } |
| |
| } |
| } |