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
diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig
index 39e46bb..de4e607 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 b5b3a48..e46b8d7 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1615,7 +1615,8 @@
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 @@
/**
* 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 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 e401c71..57c4230 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 3585feb..84c8ea7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -19,6 +19,7 @@
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 @@
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 db7ff88..bf8900d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -331,6 +331,9 @@
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 fcff0db..bcab6f0 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 @@
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 83b1a2c..3e87c1b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -67,6 +67,11 @@
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 536f3af..5d073f1 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.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 @@
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 8197145..c25e748 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_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 @@
} else {
faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
}
+ } else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) {
+ authRequiredAfterAdaptiveAuthRequest(
+ currentSecurityMode,
+ isFingerprintAuthCurrentlyAllowed.value
+ )
} else if (
trustOrBiometricsAvailable &&
flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
@@ -464,6 +470,34 @@
}.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 e23ec89..00ec1a1 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 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 @@
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 @@
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 63fd608..641b967 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.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 @@
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 cf5b88f..08904b6 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 @@
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 14230ba..19fe60a 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.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 @@
updateLockScreenAlignmentMsg();
updateLockScreenLogoutView();
updateLockScreenPersistentUnlockMsg();
+ if (enableAdaptiveAuth()) {
+ updateLockScreenAdaptiveAuthMsg(userId);
+ }
}
private void updateOrganizedOwnedDevice() {
@@ -740,6 +745,22 @@
}
}
+ 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 2732047..0957748 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.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 @@
}
@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 0000000..3312be2
--- /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 14e4481..ee758db 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.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 @@
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 0000000..08a6529
--- /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 0000000..0218a78
--- /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