diff options
| author | 2024-02-14 20:26:59 +0000 | |
|---|---|---|
| committer | 2024-02-14 20:26:59 +0000 | |
| commit | 463863246a1beb3b445b50bb7c1fda79689892e6 (patch) | |
| tree | f681ec144174efa9fa4904f58548688be3c48567 | |
| parent | 2f0c5f208e8355099b19ece44ae0e175540d818b (diff) | |
| parent | c728c37f3aad7a5323a4d0950904e59ff52d82ab (diff) | |
Merge changes Iec5752e3,Ie6140a86,If15115e4,I1cbc41dd into main
* changes:
Add Keyguard bottom area string for adaptive auth
Add a bouncer string for adaptive auth
Add adaptive authentication service
Add flag for enabling adaptive auth
18 files changed, 720 insertions, 7 deletions
diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig index 39e46bbdfa6a..de4e607b50f1 100644 --- a/core/java/android/adaptiveauth/flags.aconfig +++ b/core/java/android/adaptiveauth/flags.aconfig @@ -1,6 +1,13 @@ package: "android.adaptiveauth" flag { + name: "enable_adaptive_auth" + namespace: "biometrics" + description: "Feature flag for enabling the new adaptive auth service" + bug: "285053096" +} + +flag { name: "report_biometric_auth_attempts" namespace: "biometrics" description: "Control the usage of the biometric auth signal in adaptive auth" diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index b5b3a48dacb7..e46b8d7c5fae 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1615,7 +1615,8 @@ public class LockPatternUtils { STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT, - SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED}) + SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, + SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST}) @Retention(RetentionPolicy.SOURCE) public @interface StrongAuthFlags {} @@ -1641,7 +1642,8 @@ public class LockPatternUtils { /** * Strong authentication is required because the user has been locked out after too many - * attempts. + * attempts using primary auth methods (i.e. PIN/pattern/password) from the lock screen, + * Android Settings, and BiometricPrompt where user authentication is required. */ public static final int STRONG_AUTH_REQUIRED_AFTER_LOCKOUT = 0x8; @@ -1674,12 +1676,23 @@ public class LockPatternUtils { public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100; /** + * Some authentication is required because adaptive auth has requested to lock device due to + * repeated failed primary auth (i.e. PIN/pattern/password) or biometric auth attempts which + * can come from Android Settings or BiometricPrompt where user authentication is required, + * in addition to from the lock screen. When a risk is determined, adaptive auth will + * proactively prompt the lock screen and will require users to re-enter the device with + * either primary auth or biometric auth (if not prohibited by other flags). + */ + public static final int SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST = 0x200; + + /** * Strong auth flags that do not prevent biometric methods from being accepted as auth. * If any other flags are set, biometric authentication is disabled. */ private static final int ALLOWING_BIOMETRIC = STRONG_AUTH_NOT_REQUIRED | SOME_AUTH_REQUIRED_AFTER_USER_REQUEST - | SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED; + | SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED + | SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; private final SparseIntArray mStrongAuthRequiredForUser = new SparseIntArray(); private final H mHandler; diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e401c71b3716..57c4230c51b0 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1416,6 +1416,9 @@ <!-- Indication on the keyguard that appears when a trust agents unlocks the device. [CHAR LIMIT=40] --> <string name="keyguard_indication_trust_unlocked">Kept unlocked by TrustAgent</string> + <!-- Message asking the user to authenticate with primary authentication methods (PIN/pattern/password) or biometrics after the device is locked by adaptive auth. [CHAR LIMIT=60] --> + <string name="kg_prompt_after_adaptive_auth_lock">Theft protection\nDevice locked, too many unlock attempts</string> + <!-- Accessibility string for current zen mode and selected exit condition. A template that simply concatenates existing mode string and the current condition description. [CHAR LIMIT=20] --> <string name="zen_mode_and_condition"><xliff:g id="zen_mode" example="Priority interruptions only">%1$s</xliff:g>. <xliff:g id="exit_condition" example="For one hour">%2$s</xliff:g></string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 3585feb3442d..84c8ea708031 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -19,6 +19,7 @@ package com.android.keyguard; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.WindowInsets.Type.ime; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_ADAPTIVE_AUTH_REQUEST; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT; @@ -126,6 +127,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { return R.string.kg_prompt_reason_timeout_password; case PROMPT_REASON_TRUSTAGENT_EXPIRED: return R.string.kg_prompt_reason_timeout_password; + case PROMPT_REASON_ADAPTIVE_AUTH_REQUEST: + return R.string.kg_prompt_after_adaptive_auth_lock; case PROMPT_REASON_NONE: return 0; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index db7ff888356c..bf8900da887a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -331,6 +331,9 @@ public class KeyguardPatternViewController case PROMPT_REASON_TRUSTAGENT_EXPIRED: resId = R.string.kg_prompt_reason_timeout_pattern; break; + case PROMPT_REASON_ADAPTIVE_AUTH_REQUEST: + resId = R.string.kg_prompt_after_adaptive_auth_lock; + break; case PROMPT_REASON_NONE: break; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index fcff0dbc0878..bcab6f054dd6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_ADAPTIVE_AUTH_REQUEST; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT; @@ -138,6 +139,8 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView return R.string.kg_prompt_reason_timeout_pin; case PROMPT_REASON_TRUSTAGENT_EXPIRED: return R.string.kg_prompt_reason_timeout_pin; + case PROMPT_REASON_ADAPTIVE_AUTH_REQUEST: + return R.string.kg_prompt_after_adaptive_auth_lock; case PROMPT_REASON_NONE: return 0; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java index 83b1a2cbbf52..3e87c1b60581 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java @@ -67,6 +67,11 @@ public interface KeyguardSecurityView { int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8; /** + * Some auth is required because adaptive auth has determined risk + */ + int PROMPT_REASON_ADAPTIVE_AUTH_REQUEST = 9; + + /** * Strong auth is required because the device has just booted because of an automatic * mainline update. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 536f3afdd575..5d073f16e0e2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -35,6 +35,7 @@ import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; @@ -1570,6 +1571,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return isEncrypted || isLockDown; } + /** + * Whether the device is locked by adaptive auth + */ + public boolean isDeviceLockedByAdaptiveAuth(int userId) { + return containsFlag(mStrongAuthTracker.getStrongAuthForUser(userId), + SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST); + } + private boolean containsFlag(int haystack, int needle) { return (haystack & needle) != 0; } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt index 8197145f9646..c25e748f8668 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt @@ -50,6 +50,7 @@ import com.android.systemui.res.R.string.kg_fp_not_recognized import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin +import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock import com.android.systemui.res.R.string.kg_prompt_after_update_password import com.android.systemui.res.R.string.kg_prompt_after_update_pattern @@ -208,6 +209,11 @@ constructor( } else { faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) } + } else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) { + authRequiredAfterAdaptiveAuthRequest( + currentSecurityMode, + isFingerprintAuthCurrentlyAllowed.value + ) } else if ( trustOrBiometricsAvailable && flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout @@ -464,6 +470,34 @@ private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerM }.toMessage() } +private fun authRequiredAfterAdaptiveAuthRequest( + securityMode: SecurityMode, + fpAuthIsAllowed: Boolean +): BouncerMessageModel { + return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode) + else + return when (securityMode) { + SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock) + SecurityMode.Password -> + Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock) + SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock) + else -> Pair(0, 0) + }.toMessage() +} + +private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed( + securityMode: SecurityMode +): BouncerMessageModel { + return when (securityMode) { + SecurityMode.Pattern -> + Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock) + SecurityMode.Password -> + Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock) + SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock) + else -> Pair(0, 0) + }.toMessage() +} + private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel { return when (securityMode) { SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index e23ec894f8f3..00ec1a14bb93 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -404,6 +404,7 @@ public class KeyguardIndicationRotateTextViewController extends public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11; public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP = 12; public static final int INDICATION_IS_DISMISSIBLE = 13; + public static final int INDICATION_TYPE_ADAPTIVE_AUTH = 14; @IntDef({ INDICATION_TYPE_NONE, @@ -419,7 +420,8 @@ public class KeyguardIndicationRotateTextViewController extends INDICATION_TYPE_REVERSE_CHARGING, INDICATION_TYPE_BIOMETRIC_MESSAGE, INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, - INDICATION_IS_DISMISSIBLE + INDICATION_IS_DISMISSIBLE, + INDICATION_TYPE_ADAPTIVE_AUTH }) @Retention(RetentionPolicy.SOURCE) public @interface IndicationType{} @@ -455,6 +457,8 @@ public class KeyguardIndicationRotateTextViewController extends return "biometric_message"; case INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP: return "biometric_message_followup"; + case INDICATION_TYPE_ADAPTIVE_AUTH: + return "adaptive_auth"; default: return "unknown[" + type + "]"; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 63fd6087e587..641b9677c2e5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -30,6 +30,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BA import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; @@ -920,15 +921,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; } else if ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) != 0) { return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; + } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0 + || mUpdateMonitor.isFingerprintLockedOut())) { + return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT; + } else if ((strongAuth & SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST) != 0) { + return KeyguardSecurityView.PROMPT_REASON_ADAPTIVE_AUTH_REQUEST; } else if (trustAgentsEnabled && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) { return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; } else if (trustAgentsEnabled && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) { return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; - } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0 - || mUpdateMonitor.isFingerprintLockedOut())) { - return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT; } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) { return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; } else if (any && (strongAuth diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt index cf5b88fde3dc..08904b6ffa86 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt @@ -60,6 +60,12 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) { LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT ) + + val isSomeAuthRequiredAfterAdaptiveAuthRequest = + containsFlag( + flag, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST + ) } private fun containsFlag(haystack: Int, needle: Int): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 14230ba43f59..19fe60a60bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; +import static android.adaptiveauth.Flags.enableAdaptiveAuth; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE; @@ -32,6 +33,7 @@ import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_IS_DISMISSIBLE; +import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ADAPTIVE_AUTH; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE; @@ -454,6 +456,9 @@ public class KeyguardIndicationController { updateLockScreenAlignmentMsg(); updateLockScreenLogoutView(); updateLockScreenPersistentUnlockMsg(); + if (enableAdaptiveAuth()) { + updateLockScreenAdaptiveAuthMsg(userId); + } } private void updateOrganizedOwnedDevice() { @@ -740,6 +745,22 @@ public class KeyguardIndicationController { } } + private void updateLockScreenAdaptiveAuthMsg(int userId) { + final boolean deviceLocked = mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(userId); + if (deviceLocked) { + mRotateTextViewController.updateIndication( + INDICATION_TYPE_ADAPTIVE_AUTH, + new KeyguardIndication.Builder() + .setMessage(mContext + .getString(R.string.kg_prompt_after_adaptive_auth_lock)) + .setTextColor(mInitialTextColorState) + .build(), + true); + } else { + mRotateTextViewController.hideIndication(INDICATION_TYPE_ADAPTIVE_AUTH); + } + } + private boolean isOrganizationOwnedDevice() { return mDevicePolicyManager.isDeviceManaged() || mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 2732047b4eba..0957748c9938 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT; import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; @@ -650,6 +651,25 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @Test + public void testBouncerPrompt_deviceLockedByAdaptiveAuth() { + // GIVEN no trust agents enabled and biometrics aren't enrolled + when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(false); + when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(false); + + // WHEN the strong auth reason is SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST + KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker = + mock(KeyguardUpdateMonitor.StrongAuthTracker.class); + when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(strongAuthTracker); + when(strongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true); + when(strongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn( + SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST); + + // THEN the bouncer prompt reason should return PROMPT_REASON_ADAPTIVE_AUTH_REQUEST + assertEquals(KeyguardSecurityView.PROMPT_REASON_ADAPTIVE_AUTH_REQUEST, + mViewMediator.mViewMediatorCallback.getBouncerPromptReason()); + } + + @Test public void testBouncerPrompt_deviceRestartedDueToMainlineUpdate() { // GIVEN biometrics enrolled when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(true); diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java new file mode 100644 index 000000000000..3312be231516 --- /dev/null +++ b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2024 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.server.adaptiveauth; + +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; + +import android.app.KeyguardManager; +import android.content.Context; +import android.hardware.biometrics.AuthenticationStateListener; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.BiometricSourceType; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.util.Log; +import android.util.Slog; +import android.util.SparseIntArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockSettingsInternal; +import com.android.internal.widget.LockSettingsStateListener; +import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.pm.UserManagerInternal; +import com.android.server.wm.WindowManagerInternal; + +import java.util.Objects; + +/** + * @hide + */ +public class AdaptiveAuthService extends SystemService { + private static final String TAG = "AdaptiveAuthService"; + private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG); + + @VisibleForTesting + static final int MAX_ALLOWED_FAILED_AUTH_ATTEMPTS = 5; + private static final int MSG_REPORT_PRIMARY_AUTH_ATTEMPT = 1; + private static final int MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT = 2; + private static final int AUTH_SUCCESS = 1; + private static final int AUTH_FAILURE = 0; + + private final LockPatternUtils mLockPatternUtils; + private final LockSettingsInternal mLockSettings; + private final BiometricManager mBiometricManager; + private final KeyguardManager mKeyguardManager; + private final PowerManager mPowerManager; + private final WindowManagerInternal mWindowManager; + private final UserManagerInternal mUserManager; + @VisibleForTesting + final SparseIntArray mFailedAttemptsForUser = new SparseIntArray(); + + public AdaptiveAuthService(Context context) { + this(context, new LockPatternUtils(context)); + } + + @VisibleForTesting + public AdaptiveAuthService(Context context, LockPatternUtils lockPatternUtils) { + super(context); + mLockPatternUtils = lockPatternUtils; + mLockSettings = Objects.requireNonNull( + LocalServices.getService(LockSettingsInternal.class)); + mBiometricManager = Objects.requireNonNull( + context.getSystemService(BiometricManager.class)); + mKeyguardManager = Objects.requireNonNull(context.getSystemService(KeyguardManager.class)); + mPowerManager = Objects.requireNonNull(context.getSystemService(PowerManager.class)); + mWindowManager = Objects.requireNonNull( + LocalServices.getService(WindowManagerInternal.class)); + mUserManager = Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class)); + } + + @Override + public void onStart() {} + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + init(); + } + } + + @VisibleForTesting + void init() { + mLockSettings.registerLockSettingsStateListener(mLockSettingsStateListener); + mBiometricManager.registerAuthenticationStateListener(mAuthenticationStateListener); + } + + private final LockSettingsStateListener mLockSettingsStateListener = + new LockSettingsStateListener() { + @Override + public void onAuthenticationSucceeded(int userId) { + if (DEBUG) { + Slog.d(TAG, "LockSettingsStateListener#onAuthenticationSucceeded"); + } + mHandler.obtainMessage(MSG_REPORT_PRIMARY_AUTH_ATTEMPT, AUTH_SUCCESS, userId) + .sendToTarget(); + } + + @Override + public void onAuthenticationFailed(int userId) { + Slog.i(TAG, "LockSettingsStateListener#onAuthenticationFailed"); + mHandler.obtainMessage(MSG_REPORT_PRIMARY_AUTH_ATTEMPT, AUTH_FAILURE, userId) + .sendToTarget(); + } + }; + + private final AuthenticationStateListener mAuthenticationStateListener = + new AuthenticationStateListener.Stub() { + @Override + public void onAuthenticationStarted(int requestReason) {} + + @Override + public void onAuthenticationStopped() {} + + @Override + public void onAuthenticationSucceeded(int requestReason, int userId) { + if (DEBUG) { + Slog.d(TAG, "AuthenticationStateListener#onAuthenticationSucceeded"); + } + mHandler.obtainMessage(MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT, AUTH_SUCCESS, userId) + .sendToTarget(); + } + + @Override + public void onAuthenticationFailed(int requestReason, int userId) { + Slog.i(TAG, "AuthenticationStateListener#onAuthenticationFailed"); + mHandler.obtainMessage(MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT, AUTH_FAILURE, userId) + .sendToTarget(); + } + + @Override + public void onAuthenticationAcquired(BiometricSourceType biometricSourceType, + int requestReason, int acquiredInfo) {} + }; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REPORT_PRIMARY_AUTH_ATTEMPT: + handleReportPrimaryAuthAttempt(msg.arg1 != AUTH_FAILURE, msg.arg2); + break; + case MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT: + handleReportBiometricAuthAttempt(msg.arg1 != AUTH_FAILURE, msg.arg2); + break; + } + } + }; + + private void handleReportPrimaryAuthAttempt(boolean success, int userId) { + if (DEBUG) { + Slog.d(TAG, "handleReportPrimaryAuthAttempt: success=" + success + + ", userId=" + userId); + } + reportAuthAttempt(success, userId); + } + + private void handleReportBiometricAuthAttempt(boolean success, int userId) { + if (DEBUG) { + Slog.d(TAG, "handleReportBiometricAuthAttempt: success=" + success + + ", userId=" + userId); + } + reportAuthAttempt(success, userId); + } + + private void reportAuthAttempt(boolean success, int userId) { + if (success) { + // Deleting the entry effectively resets the counter of failed attempts for the user + mFailedAttemptsForUser.delete(userId); + return; + } + + final int numFailedAttempts = mFailedAttemptsForUser.get(userId, 0) + 1; + Slog.i(TAG, "reportAuthAttempt: numFailedAttempts=" + numFailedAttempts + + ", userId=" + userId); + mFailedAttemptsForUser.put(userId, numFailedAttempts); + + // Don't lock again if the device is already locked and if Keyguard is already showing and + // isn't trivially dismissible + if (mKeyguardManager.isDeviceLocked(userId) && mKeyguardManager.isKeyguardLocked()) { + Slog.d(TAG, "Not locking the device because the device is already locked."); + return; + } + + if (numFailedAttempts < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS) { + Slog.d(TAG, "Not locking the device because the number of failed attempts is below" + + " the threshold."); + return; + } + + //TODO: additionally consider the trust signal before locking device + lockDevice(userId); + } + + /** + * Locks the device and requires primary auth or biometric auth for unlocking + */ + private void lockDevice(int userId) { + // Require either primary auth or biometric auth to unlock the device again. Keyguard and + // bouncer will also check the StrongAuthFlag for the user to display correct strings for + // explaining why the device is locked + mLockPatternUtils.requireStrongAuth(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST, userId); + + // If userId is a profile that has a different parent userId (regardless of its profile + // type, or whether it's a profile with unified challenges or not), its parent userId that + // owns the Keyguard will also be locked + final int parentUserId = mUserManager.getProfileParentId(userId); + Slog.i(TAG, "lockDevice: userId=" + userId + ", parentUserId=" + parentUserId); + if (parentUserId != userId) { + mLockPatternUtils.requireStrongAuth(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST, + parentUserId); + } + + // Power off the display + mPowerManager.goToSleep(SystemClock.uptimeMillis()); + + // Lock the device + mWindowManager.lockNow(); + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 14e448103f33..ee758dbd0516 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -107,6 +107,7 @@ import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockSettingsInternal; +import com.android.server.adaptiveauth.AdaptiveAuthService; import com.android.server.am.ActivityManagerService; import com.android.server.appbinding.AppBindingService; import com.android.server.appop.AppOpMigrationHelper; @@ -2615,6 +2616,12 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(AuthService.class); t.traceEnd(); + if (android.adaptiveauth.Flags.enableAdaptiveAuth()) { + t.traceBegin("StartAdaptiveAuthService"); + mSystemServiceManager.startService(AdaptiveAuthService.class); + t.traceEnd(); + } + if (!isWatch) { // We don't run this on watches as there are no plans to use the data logged // on watch devices. diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java b/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java new file mode 100644 index 000000000000..08a65292cf20 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2024 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.server.adaptiveauth; + +import static android.adaptiveauth.Flags.FLAG_ENABLE_ADAPTIVE_AUTH; +import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS; +import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS; + +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; +import static com.android.server.adaptiveauth.AdaptiveAuthService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.KeyguardManager; +import android.content.Context; +import android.hardware.biometrics.AuthenticationStateListener; +import android.hardware.biometrics.BiometricManager; +import android.os.RemoteException; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockSettingsInternal; +import com.android.internal.widget.LockSettingsStateListener; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; +import com.android.server.wm.WindowManagerInternal; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * atest FrameworksServicesTests:AdaptiveAuthServiceTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AdaptiveAuthServiceTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final int PRIMARY_USER_ID = 0; + private static final int MANAGED_PROFILE_USER_ID = 12; + private static final int DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS = 0; + private static final int REASON_UNKNOWN = 0; // BiometricRequestConstants.RequestReason + + private Context mContext; + private AdaptiveAuthService mAdaptiveAuthService; + + @Mock + LockPatternUtils mLockPatternUtils; + @Mock + private LockSettingsInternal mLockSettings; + @Mock + private BiometricManager mBiometricManager; + @Mock + private KeyguardManager mKeyguardManager; + @Mock + private WindowManagerInternal mWindowManager; + @Mock + private UserManagerInternal mUserManager; + + @Captor + ArgumentCaptor<LockSettingsStateListener> mLockSettingsStateListenerCaptor; + @Captor + ArgumentCaptor<AuthenticationStateListener> mAuthenticationStateListenerCaptor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mSetFlagsRule.enableFlags(FLAG_ENABLE_ADAPTIVE_AUTH); + mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); + mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS); + + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager); + when(mContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager); + + LocalServices.removeServiceForTest(LockSettingsInternal.class); + LocalServices.addService(LockSettingsInternal.class, mLockSettings); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.addService(WindowManagerInternal.class, mWindowManager); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mUserManager); + + mAdaptiveAuthService = new AdaptiveAuthService(mContext, mLockPatternUtils); + mAdaptiveAuthService.init(); + + verify(mLockSettings).registerLockSettingsStateListener( + mLockSettingsStateListenerCaptor.capture()); + verify(mBiometricManager).registerAuthenticationStateListener( + mAuthenticationStateListenerCaptor.capture()); + + // Set PRIMARY_USER_ID as the parent of MANAGED_PROFILE_USER_ID + when(mUserManager.getProfileParentId(eq(MANAGED_PROFILE_USER_ID))) + .thenReturn(PRIMARY_USER_ID); + } + + @After + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(LockSettingsInternal.class); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.removeServiceForTest(UserManagerInternal.class); + } + + @Test + public void testReportAuthAttempt_primaryAuthSucceeded() + throws RemoteException { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationSucceeded(PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthFailed_once() + throws RemoteException { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(1 /* expectedCntFailedAttempts */, PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthFailed_multiple_deviceCurrentlyLocked() + throws RemoteException { + // Device is currently locked and Keyguard is showing + when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(true); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); + + for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); + } + waitForAuthCompletion(); + + verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthFailed_multiple_deviceCurrentlyNotLocked() + throws RemoteException { + // Device is currently not locked and Keyguard is not showing + when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(false); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); + + for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); + } + waitForAuthCompletion(); + + verifyLockDevice(PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_biometricAuthSucceeded() + throws RemoteException { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationSucceeded(REASON_UNKNOWN, PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_biometricAuthFailed_once() + throws RemoteException { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(1 /* expectedCntFailedAttempts */, PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyLocked() + throws RemoteException { + // Device is currently locked and Keyguard is showing + when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(true); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); + + for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, PRIMARY_USER_ID); + } + waitForAuthCompletion(); + + verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked() + throws RemoteException { + // Device is currently not locked and Keyguard is not showing + when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(false); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); + + for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, PRIMARY_USER_ID); + } + waitForAuthCompletion(); + + verifyLockDevice(PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_biometricAuthFailedThenPrimaryAuthSucceeded() + throws RemoteException { + // Three failed biometric auth attempts + for (int i = 0; i < 3; i++) { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, PRIMARY_USER_ID); + } + // One successful primary auth attempt + mLockSettingsStateListenerCaptor.getValue().onAuthenticationSucceeded(PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthFailedThenBiometricAuthSucceeded() + throws RemoteException { + // Three failed primary auth attempts + for (int i = 0; i < 3; i++) { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); + } + // One successful biometric auth attempt + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationSucceeded(REASON_UNKNOWN, PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser() + throws RemoteException { + // Three failed primary auth attempts + for (int i = 0; i < 3; i++) { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); + } + // Two failed biometric auth attempts + for (int i = 0; i < 2; i++) { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, PRIMARY_USER_ID); + } + waitForAuthCompletion(); + + verifyLockDevice(PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_profileOfPrimaryUser() + throws RemoteException { + // Three failed primary auth attempts + for (int i = 0; i < 3; i++) { + mLockSettingsStateListenerCaptor.getValue() + .onAuthenticationFailed(MANAGED_PROFILE_USER_ID); + } + // Two failed biometric auth attempts + for (int i = 0; i < 2; i++) { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, MANAGED_PROFILE_USER_ID); + } + waitForAuthCompletion(); + + verifyLockDevice(MANAGED_PROFILE_USER_ID); + } + + private void verifyNotLockDevice(int expectedCntFailedAttempts, int userId) { + assertEquals(expectedCntFailedAttempts, + mAdaptiveAuthService.mFailedAttemptsForUser.get(userId)); + verify(mWindowManager, never()).lockNow(); + } + + private void verifyLockDevice(int userId) { + assertEquals(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, + mAdaptiveAuthService.mFailedAttemptsForUser.get(userId)); + verify(mLockPatternUtils).requireStrongAuth( + eq(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST), eq(userId)); + // If userId is MANAGED_PROFILE_USER_ID, the StrongAuthFlag of its parent (PRIMARY_USER_ID) + // should also be verified + if (userId == MANAGED_PROFILE_USER_ID) { + verify(mLockPatternUtils).requireStrongAuth( + eq(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST), eq(PRIMARY_USER_ID)); + } + verify(mWindowManager).lockNow(); + } + + /** + * Wait for all auth events to complete before verification + */ + private static void waitForAuthCompletion() { + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS b/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS new file mode 100644 index 000000000000..0218a7835586 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file |