diff options
8 files changed, 152 insertions, 25 deletions
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 65c570840368..fce7a00f9744 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -382,6 +382,7 @@ android_library { "androidx.compose.material_material-icons-extended", "androidx.activity_activity-compose", "androidx.compose.animation_animation-graphics", + "device_policy_aconfig_flags_lib", ], libs: [ "keepanno-annotations", @@ -622,6 +623,7 @@ android_app { "//frameworks/libs/systemui:compilelib", "SystemUI-tests-base", "androidx.compose.runtime_runtime", + "SystemUI-core", ], libs: [ "keepanno-annotations", diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 447c28067200..6c3f3c181d45 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -17,8 +17,11 @@ package com.android.keyguard +import android.app.admin.DevicePolicyManager +import android.app.admin.flags.Flags as DevicePolicyFlags import android.content.res.Configuration import android.media.AudioManager +import android.platform.test.annotations.EnableFlags import android.telephony.TelephonyManager import android.testing.TestableLooper.RunWithLooper import android.testing.TestableResources @@ -148,6 +151,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var postureController: DevicePostureController + @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Captor private lateinit var swipeListenerArgumentCaptor: @@ -273,6 +277,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { mSelectedUserInteractor, deviceProvisionedController, faceAuthAccessibilityDelegate, + devicePolicyManager, keyguardTransitionInteractor, { primaryBouncerInteractor }, ) { @@ -934,6 +939,45 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any()) } + @Test + @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) + fun showAlmostAtWipeDialog_calledOnMainUser_setsCorrectUserType() { + val mainUserId = 10 + + underTest.showMessageForFailedUnlockAttempt( + /* userId = */ mainUserId, + /* expiringUserId = */ mainUserId, + /* mainUserId = */ mainUserId, + /* remainingBeforeWipe = */ 1, + /* failedAttempts = */ 1 + ) + + verify(view) + .showAlmostAtWipeDialog(any(), any(), eq(KeyguardSecurityContainer.USER_TYPE_PRIMARY)) + } + + @Test + @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) + fun showAlmostAtWipeDialog_calledOnNonMainUser_setsCorrectUserType() { + val secondaryUserId = 10 + val mainUserId = 0 + + underTest.showMessageForFailedUnlockAttempt( + /* userId = */ secondaryUserId, + /* expiringUserId = */ secondaryUserId, + /* mainUserId = */ mainUserId, + /* remainingBeforeWipe = */ 1, + /* failedAttempts = */ 1 + ) + + verify(view) + .showAlmostAtWipeDialog( + any(), + any(), + eq(KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER) + ) + } + private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener get() { underTest.onViewAttached() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 81878aaf4a18..0c5e726e17aa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.authentication.domain.interactor import android.app.admin.DevicePolicyManager +import android.app.admin.flags.Flags as DevicePolicyFlags +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils @@ -32,6 +34,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationWipeModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.fakeUserRepository import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -410,12 +414,16 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) fun upcomingWipe() = testScope.runTest { val upcomingWipe by collectLastValue(underTest.upcomingWipe) kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) val correctPin = FakeAuthenticationRepository.DEFAULT_PIN val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 } + kosmos.fakeUserRepository.asMainUser() + kosmos.fakeAuthenticationRepository.profileWithMinFailedUnlockAttemptsForWipe = + FakeUserRepository.MAIN_USER_ID underTest.authenticate(correctPin) assertThat(upcomingWipe).isNull() diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 91fb6888bf06..905a98c2e181 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -35,6 +35,7 @@ import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; +import android.app.admin.flags.Flags; import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -134,6 +135,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final BouncerMessageInteractor mBouncerMessageInteractor; private int mTranslationY; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; + private final DevicePolicyManager mDevicePolicyManager; // Whether the volume keys should be handled by keyguard. If true, then // they will be handled here for specific media types such as music, otherwise // the audio service will bring up the volume dialog. @@ -460,6 +462,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard SelectedUserInteractor selectedUserInteractor, DeviceProvisionedController deviceProvisionedController, FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate, + DevicePolicyManager devicePolicyManager, KeyguardTransitionInteractor keyguardTransitionInteractor, Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor, Provider<DeviceEntryInteractor> deviceEntryInteractor @@ -495,6 +498,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mKeyguardTransitionInteractor = keyguardTransitionInteractor; mDeviceProvisionedController = deviceProvisionedController; mPrimaryBouncerInteractor = primaryBouncerInteractor; + mDevicePolicyManager = devicePolicyManager; } @Override @@ -1105,35 +1109,23 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); - final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager(); final int failedAttemptsBeforeWipe = - dpm.getMaximumFailedPasswordsForWipe(null, userId); + mDevicePolicyManager.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); - } + // 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 = + mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(userId); + Integer mainUser = mSelectedUserInteractor.getMainUserId(); + showMessageForFailedUnlockAttempt( + userId, expiringUser, mainUser, remainingBeforeWipe, failedAttempts); } mLockPatternUtils.reportFailedPasswordAttempt(userId); if (timeoutMs > 0) { @@ -1145,6 +1137,35 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } } + @VisibleForTesting + void showMessageForFailedUnlockAttempt(int userId, int expiringUserId, Integer mainUserId, + int remainingBeforeWipe, int failedAttempts) { + int userType = USER_TYPE_PRIMARY; + if (expiringUserId == userId) { + int primaryUser = UserHandle.USER_SYSTEM; + if (Flags.headlessSingleUserFixes()) { + if (mainUserId != null) { + primaryUser = mainUserId; + } + } + // TODO: http://b/23522538 + if (expiringUserId != primaryUser) { + userType = USER_TYPE_SECONDARY_USER; + } + } else if (expiringUserId != 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 " + expiringUserId + + " will be wiped!"); + mView.showWipeDialog(failedAttempts, userType); + } + } + private void getCurrentSecurityController( KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback) { mSecurityViewFlipperController diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 5df7fc9865ff..fcba425f0956 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.authentication.domain.interactor +import android.app.admin.flags.Flags import android.os.UserHandle import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView @@ -288,9 +289,15 @@ constructor( private suspend fun getWipeTarget(): WipeTarget { // Check which profile has the strictest policy for failed authentication attempts. val userToBeWiped = repository.getProfileWithMinFailedUnlockAttemptsForWipe() + val primaryUser = + if (Flags.headlessSingleUserFixes()) { + selectedUserInteractor.getMainUserId() ?: UserHandle.USER_SYSTEM + } else { + UserHandle.USER_SYSTEM + } return when (userToBeWiped) { selectedUserInteractor.getSelectedUserId() -> - if (userToBeWiped == UserHandle.USER_SYSTEM) { + if (userToBeWiped == primaryUser) { WipeTarget.WholeDevice } else { WipeTarget.User diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 37be1c6aa73d..a817b31070a1 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.user.data.repository import android.annotation.SuppressLint +import android.annotation.UserIdInt import android.content.Context import android.content.pm.UserInfo import android.os.UserHandle @@ -107,6 +108,22 @@ interface UserRepository { fun isSimpleUserSwitcher(): Boolean fun isUserSwitcherEnabled(): Boolean + + /** + * Returns the user ID of the "main user" of the device. This user may have access to certain + * features which are limited to at most one user. There will never be more than one main user + * on a device. + * + * <p>Currently, on most form factors the first human user on the device will be the main user; + * in the future, the concept may be transferable, so a different user (or even no user at all) + * may be designated the main user instead. On other form factors there might not be a main + * user. + * + * <p> When the device doesn't have a main user, this will return {@code null}. + * + * @see [UserManager.getMainUser] + */ + @UserIdInt suspend fun getMainUserId(): Int? } @SysUISingleton @@ -239,6 +256,10 @@ constructor( return _userSwitcherSettings.value.isUserSwitcherEnabled } + override suspend fun getMainUserId(): Int? { + return withContext(backgroundDispatcher) { manager.mainUser?.identifier } + } + private suspend fun getSettings(): UserSwitcherSettingsModel { return withContext(backgroundDispatcher) { val isSimpleUserSwitcher = diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt index 38b381ac543e..59c819d41493 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt @@ -2,6 +2,7 @@ package com.android.systemui.user.domain.interactor import android.annotation.UserIdInt import android.content.pm.UserInfo +import android.os.UserManager import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Flags.refactorGetCurrentUser import com.android.systemui.dagger.SysUISingleton @@ -38,4 +39,23 @@ class SelectedUserInteractor @Inject constructor(private val repository: UserRep KeyguardUpdateMonitor.getCurrentUser() } } + + /** + * Returns the user ID of the "main user" of the device. This user may have access to certain + * features which are limited to at most one user. There will never be more than one main user + * on a device. + * + * <p>Currently, on most form factors the first human user on the device will be the main user; + * in the future, the concept may be transferable, so a different user (or even no user at all) + * may be designated the main user instead. On other form factors there might not be a main + * user. + * + * <p> When the device doesn't have a main user, this will return {@code null}. + * + * @see [UserManager.getMainUser] + */ + @UserIdInt + fun getMainUserId(): Int? { + return repository.mainUserId + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 3e9ae4d2e354..1f2ecb7d172d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -37,7 +37,7 @@ import kotlinx.coroutines.yield class FakeUserRepository @Inject constructor() : UserRepository { companion object { // User id to represent a non system (human) user id. We presume this is the main user. - private const val MAIN_USER_ID = 10 + const val MAIN_USER_ID = 10 private const val DEFAULT_SELECTED_USER = 0 private val DEFAULT_SELECTED_USER_INFO = @@ -84,6 +84,10 @@ class FakeUserRepository @Inject constructor() : UserRepository { override var isRefreshUsersPaused: Boolean = false + override suspend fun getMainUserId(): Int? { + return MAIN_USER_ID + } + var refreshUsersCallCount: Int = 0 private set |