diff options
11 files changed, 208 insertions, 29 deletions
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index e2af6313fc97..8d7556568015 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -149,6 +149,7 @@ android_library { "device_state_flags_lib", "kotlinx_coroutines_android", "kotlinx_coroutines", + "kotlinx_coroutines_guava", "//frameworks/libs/systemui:iconloader_base", "SystemUI-tags", "SystemUI-proto", @@ -169,6 +170,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", @@ -328,6 +330,7 @@ android_library { "androidx.activity_activity-compose", "androidx.compose.animation_animation-graphics", "TraceurCommon", + "kotlinx_coroutines_guava", ], } @@ -409,6 +412,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 aa70c45d3ff3..0b5a019b499a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -17,6 +17,8 @@ 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.telephony.TelephonyManager @@ -148,6 +150,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: @@ -274,6 +277,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { mSelectedUserInteractor, deviceProvisionedController, faceAuthAccessibilityDelegate, + devicePolicyManager, keyguardTransitionInteractor, { primaryBouncerInteractor }, ) { @@ -929,6 +933,45 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any()) } + @Test + fun showAlmostAtWipeDialog_calledOnMainUser_setsCorrectUserType() { + mSetFlagsRule.enableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) + 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 + fun showAlmostAtWipeDialog_calledOnNonMainUser_setsCorrectUserType() { + mSetFlagsRule.enableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) + 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 b916fc297ed8..b93eba655596 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -35,12 +35,14 @@ 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; import android.content.res.Resources; import android.media.AudioManager; import android.metrics.LogMaker; +import android.os.Looper; import android.os.SystemClock; import android.os.UserHandle; import android.telephony.TelephonyManager; @@ -97,12 +99,15 @@ import com.android.systemui.util.ViewController; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; +import com.google.common.util.concurrent.ListenableFuture; + import dagger.Lazy; import kotlinx.coroutines.Job; import java.io.File; import java.util.Arrays; +import java.util.concurrent.ExecutionException; import javax.inject.Inject; import javax.inject.Provider; @@ -135,6 +140,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. @@ -461,6 +467,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard SelectedUserInteractor selectedUserInteractor, DeviceProvisionedController deviceProvisionedController, FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate, + DevicePolicyManager devicePolicyManager, KeyguardTransitionInteractor keyguardTransitionInteractor, Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor, Provider<DeviceEntryInteractor> deviceEntryInteractor @@ -496,6 +503,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mKeyguardTransitionInteractor = keyguardTransitionInteractor; mDeviceProvisionedController = deviceProvisionedController; mPrimaryBouncerInteractor = primaryBouncerInteractor; + mDevicePolicyManager = devicePolicyManager; } @Override @@ -1106,35 +1114,36 @@ 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; + // 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); + ListenableFuture<Integer> getMainUserIdFuture = + mSelectedUserInteractor.getMainUserIdAsync(); + getMainUserIdFuture.addListener(() -> { + Looper.prepare(); + Integer mainUser; + try { + mainUser = getMainUserIdFuture.get(); + } catch (InterruptedException | ExecutionException e) { + // Nothing we can, keep using the system user as the primary + // user. + mainUser = null; } - } 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); - } + showMessageForFailedUnlockAttempt( + userId, expiringUser, mainUser, remainingBeforeWipe, failedAttempts); + Looper.loop(); + }, ThreadUtils.getBackgroundExecutor()); } mLockPatternUtils.reportFailedPasswordAttempt(userId); if (timeoutMs > 0) { @@ -1146,6 +1155,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..a5728d061d48 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,17 +2,27 @@ 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 +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.user.data.repository.UserRepository +import com.google.common.util.concurrent.ListenableFuture import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.guava.future /** Encapsulates business logic to interact the selected user */ @SysUISingleton -class SelectedUserInteractor @Inject constructor(private val repository: UserRepository) { +class SelectedUserInteractor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val repository: UserRepository +) { /** Flow providing the ID of the currently selected user. */ val selectedUser = repository.selectedUserInfo.map { it.id }.distinctUntilChanged() @@ -38,4 +48,41 @@ 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 + suspend fun getMainUserId(): Int? { + return repository.getMainUserId() + } + + /** + * Returns a [ListenableFuture] for 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] + */ + fun getMainUserIdAsync(): ListenableFuture<Int?> { + return applicationScope.future { getMainUserId() } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt index 140e919d613f..78d4f02e74a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt @@ -7,6 +7,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.user.data.repository.FakeUserRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.TestCoroutineScope import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -23,7 +24,7 @@ class SelectedUserInteractorTest : SysuiTestCase() { @Before fun setUp() { userRepository.setUserInfos(USER_INFOS) - underTest = SelectedUserInteractor(userRepository) + underTest = SelectedUserInteractor(TestCoroutineScope(), userRepository) } @Test diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt index 7eef704c1622..c7b06b6bcd8f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt @@ -58,7 +58,7 @@ object KeyguardDismissInteractorFactory { bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(), keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java), powerRepository: FakePowerRepository = FakePowerRepository(), - userRepository: FakeUserRepository = FakeUserRepository(), + userRepository: FakeUserRepository = FakeUserRepository() ): WithDependencies { val primaryBouncerInteractor = PrimaryBouncerInteractor( @@ -95,7 +95,11 @@ object KeyguardDismissInteractorFactory { PowerInteractorFactory.create( repository = powerRepository, ) - val selectedUserInteractor = SelectedUserInteractor(repository = userRepository) + val selectedUserInteractor = + SelectedUserInteractor( + applicationScope = testScope.backgroundScope, + repository = userRepository + ) return WithDependencies( trustRepository = trustRepository, keyguardRepository = keyguardRepository, 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 diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt index 89672f109657..9dddfcdddc8c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt @@ -17,6 +17,8 @@ package com.android.systemui.user.domain.interactor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.user.data.repository.userRepository -val Kosmos.selectedUserInteractor by Kosmos.Fixture { SelectedUserInteractor(userRepository) } +val Kosmos.selectedUserInteractor by + Kosmos.Fixture { SelectedUserInteractor(applicationCoroutineScope, userRepository) } |