summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Anton Potapov <apotapov@google.com> 2024-09-03 20:21:42 +0100
committer Anton Potapov <apotapov@google.com> 2024-09-06 13:28:13 +0100
commit18375b1bdf1e0bdd6ba54f1710486d90c44ea619 (patch)
treea539e6d5a524dbd62891c4f78fc6ab4dc751f7a3
parent4d96988c635d722ef794625e0e91d379a303ef2d (diff)
Refactor CaptioningRepository to take current UserHandle into account.
CaptioningRepository now uses `createContextAsUser` to get a CaptioningManager for current user. Flag: EXEMPT BUGFIX Fixes: 361042246 Test: atest VolumePanelScreenshotTest Test: atest CaptioningViewModelTest Change-Id: I32cec55231db16bc3b7401716326b8b8f2a631d7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/CaptioningRepositoryTest.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/model/CaptioningModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt126
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/CaptioningInteractor.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeCaptioningRepository.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/utils/FakeUserScopedService.kt33
9 files changed, 183 insertions, 134 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/CaptioningRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/CaptioningRepositoryTest.kt
index dd85d9bd2d7c..fc57757c9a8c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/CaptioningRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/CaptioningRepositoryTest.kt
@@ -20,11 +20,15 @@ import android.view.accessibility.CaptioningManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.user.utils.FakeUserScopedService
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -39,10 +43,11 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@Suppress("UnspecifiedRegisterReceiverFlag")
@RunWith(AndroidJUnit4::class)
class CaptioningRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Captor
private lateinit var listenerCaptor: ArgumentCaptor<CaptioningManager.CaptioningChangeListener>
@@ -50,34 +55,33 @@ class CaptioningRepositoryTest : SysuiTestCase() {
private lateinit var underTest: CaptioningRepository
- private val testScope = TestScope()
-
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
underTest =
- CaptioningRepositoryImpl(
- captioningManager,
- testScope.testScheduler,
- testScope.backgroundScope
- )
+ with(kosmos) {
+ CaptioningRepositoryImpl(
+ FakeUserScopedService(captioningManager),
+ userRepository,
+ testScope.testScheduler,
+ applicationCoroutineScope,
+ )
+ }
}
@Test
fun isSystemAudioCaptioningEnabled_change_repositoryEmits() {
- testScope.runTest {
- `when`(captioningManager.isEnabled).thenReturn(false)
- val isSystemAudioCaptioningEnabled = mutableListOf<Boolean>()
- underTest.isSystemAudioCaptioningEnabled
- .onEach { isSystemAudioCaptioningEnabled.add(it) }
- .launchIn(backgroundScope)
+ kosmos.testScope.runTest {
+ `when`(captioningManager.isSystemAudioCaptioningEnabled).thenReturn(false)
+ val models by collectValues(underTest.captioningModel.filterNotNull())
runCurrent()
+ `when`(captioningManager.isSystemAudioCaptioningEnabled).thenReturn(true)
triggerOnSystemAudioCaptioningChange()
runCurrent()
- assertThat(isSystemAudioCaptioningEnabled)
+ assertThat(models.map { it.isSystemAudioCaptioningEnabled })
.containsExactlyElementsIn(listOf(false, true))
.inOrder()
}
@@ -85,18 +89,16 @@ class CaptioningRepositoryTest : SysuiTestCase() {
@Test
fun isSystemAudioCaptioningUiEnabled_change_repositoryEmits() {
- testScope.runTest {
- `when`(captioningManager.isSystemAudioCaptioningUiEnabled).thenReturn(false)
- val isSystemAudioCaptioningUiEnabled = mutableListOf<Boolean>()
- underTest.isSystemAudioCaptioningUiEnabled
- .onEach { isSystemAudioCaptioningUiEnabled.add(it) }
- .launchIn(backgroundScope)
+ kosmos.testScope.runTest {
+ `when`(captioningManager.isEnabled).thenReturn(false)
+ val models by collectValues(underTest.captioningModel.filterNotNull())
runCurrent()
+ `when`(captioningManager.isSystemAudioCaptioningUiEnabled).thenReturn(true)
triggerSystemAudioCaptioningUiChange()
runCurrent()
- assertThat(isSystemAudioCaptioningUiEnabled)
+ assertThat(models.map { it.isSystemAudioCaptioningUiEnabled })
.containsExactlyElementsIn(listOf(false, true))
.inOrder()
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/CaptioningModel.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/CaptioningModel.kt
new file mode 100644
index 000000000000..4eb2274cf129
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/CaptioningModel.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.systemui.accessibility.data.model
+
+data class CaptioningModel(
+ val isSystemAudioCaptioningUiEnabled: Boolean,
+ val isSystemAudioCaptioningEnabled: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
index bf749d4cfc35..5414b623ff97 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
@@ -16,98 +16,90 @@
package com.android.systemui.accessibility.data.repository
+import android.annotation.SuppressLint
import android.view.accessibility.CaptioningManager
+import com.android.systemui.accessibility.data.model.CaptioningModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
interface CaptioningRepository {
- /** The system audio caption enabled state. */
- val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
+ /** Current state of Live Captions. */
+ val captioningModel: StateFlow<CaptioningModel?>
- /** The system audio caption UI enabled state. */
- val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
-
- /** Sets [isSystemAudioCaptioningEnabled]. */
+ /** Sets [CaptioningModel.isSystemAudioCaptioningEnabled]. */
suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean)
}
-class CaptioningRepositoryImpl(
- private val captioningManager: CaptioningManager,
- private val backgroundCoroutineContext: CoroutineContext,
- coroutineScope: CoroutineScope,
+@OptIn(ExperimentalCoroutinesApi::class)
+class CaptioningRepositoryImpl
+@Inject
+constructor(
+ private val userScopedCaptioningManagerProvider: UserScopedService<CaptioningManager>,
+ userRepository: UserRepository,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
+ @Application coroutineScope: CoroutineScope,
) : CaptioningRepository {
- private val captioningChanges: SharedFlow<CaptioningChange> =
- callbackFlow {
- val listener = CaptioningChangeProducingListener(this)
- captioningManager.addCaptioningChangeListener(listener)
- awaitClose { captioningManager.removeCaptioningChangeListener(listener) }
- }
- .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
-
- override val isSystemAudioCaptioningEnabled: StateFlow<Boolean> =
- captioningChanges
- .filterIsInstance(CaptioningChange.IsSystemAudioCaptioningEnabled::class)
- .map { it.isEnabled }
- .onStart { emit(captioningManager.isSystemAudioCaptioningEnabled) }
- .stateIn(
- coroutineScope,
- SharingStarted.WhileSubscribed(),
- captioningManager.isSystemAudioCaptioningEnabled,
- )
+ @SuppressLint("NonInjectedService") // this uses user-aware context
+ private val captioningManager: StateFlow<CaptioningManager?> =
+ userRepository.selectedUser
+ .map { userScopedCaptioningManagerProvider.forUser(it.userInfo.userHandle) }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
- override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean> =
- captioningChanges
- .filterIsInstance(CaptioningChange.IsSystemUICaptioningEnabled::class)
- .map { it.isEnabled }
- .onStart { emit(captioningManager.isSystemAudioCaptioningUiEnabled) }
- .stateIn(
- coroutineScope,
- SharingStarted.WhileSubscribed(),
- captioningManager.isSystemAudioCaptioningUiEnabled,
- )
+ override val captioningModel: StateFlow<CaptioningModel?> =
+ captioningManager
+ .filterNotNull()
+ .flatMapLatest { it.captioningModel() }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
withContext(backgroundCoroutineContext) {
- captioningManager.isSystemAudioCaptioningEnabled = isEnabled
+ captioningManager.value?.isSystemAudioCaptioningEnabled = isEnabled
}
}
- private sealed interface CaptioningChange {
-
- data class IsSystemAudioCaptioningEnabled(val isEnabled: Boolean) : CaptioningChange
-
- data class IsSystemUICaptioningEnabled(val isEnabled: Boolean) : CaptioningChange
- }
-
- private class CaptioningChangeProducingListener(
- private val scope: ProducerScope<CaptioningChange>
- ) : CaptioningManager.CaptioningChangeListener() {
-
- override fun onSystemAudioCaptioningChanged(enabled: Boolean) {
- emitChange(CaptioningChange.IsSystemAudioCaptioningEnabled(enabled))
- }
-
- override fun onSystemAudioCaptioningUiChanged(enabled: Boolean) {
- emitChange(CaptioningChange.IsSystemUICaptioningEnabled(enabled))
- }
-
- private fun emitChange(change: CaptioningChange) {
- scope.launch { scope.send(change) }
- }
+ private fun CaptioningManager.captioningModel(): Flow<CaptioningModel> {
+ return conflatedCallbackFlow {
+ val listener =
+ object : CaptioningManager.CaptioningChangeListener() {
+
+ override fun onSystemAudioCaptioningChanged(enabled: Boolean) {
+ trySend(Unit)
+ }
+
+ override fun onSystemAudioCaptioningUiChanged(enabled: Boolean) {
+ trySend(Unit)
+ }
+ }
+ addCaptioningChangeListener(listener)
+ awaitClose { removeCaptioningChangeListener(listener) }
+ }
+ .onStart { emit(Unit) }
+ .map {
+ CaptioningModel(
+ isSystemAudioCaptioningEnabled = isSystemAudioCaptioningEnabled,
+ isSystemAudioCaptioningUiEnabled = isSystemAudioCaptioningUiEnabled,
+ )
+ }
+ .flowOn(backgroundCoroutineContext)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/CaptioningInteractor.kt b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/CaptioningInteractor.kt
index 1d493c697652..840edf44ecf5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/CaptioningInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/CaptioningInteractor.kt
@@ -17,16 +17,22 @@
package com.android.systemui.accessibility.domain.interactor
import com.android.systemui.accessibility.data.repository.CaptioningRepository
-import kotlinx.coroutines.flow.StateFlow
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
-class CaptioningInteractor(private val repository: CaptioningRepository) {
+@SysUISingleton
+class CaptioningInteractor @Inject constructor(private val repository: CaptioningRepository) {
- val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
- get() = repository.isSystemAudioCaptioningEnabled
+ val isSystemAudioCaptioningEnabled: Flow<Boolean> =
+ repository.captioningModel.filterNotNull().map { it.isSystemAudioCaptioningEnabled }
- val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
- get() = repository.isSystemAudioCaptioningUiEnabled
+ val isSystemAudioCaptioningUiEnabled: Flow<Boolean> =
+ repository.captioningModel.filterNotNull().map { it.isSystemAudioCaptioningUiEnabled }
- suspend fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) =
+ suspend fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) {
repository.setIsSystemAudioCaptioningEnabled(enabled)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 21a704df074e..8818c3af4916 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -202,6 +202,13 @@ public class FrameworkServicesModule {
return context.getSystemService(CaptioningManager.class);
}
+ @Provides
+ @Singleton
+ static UserScopedService<CaptioningManager> provideUserScopedCaptioningManager(
+ Context context) {
+ return new UserScopedServiceImpl<>(context, CaptioningManager.class);
+ }
+
/** */
@Provides
@Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
index 9715772f089f..28a43df2bfb3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
@@ -16,35 +16,16 @@
package com.android.systemui.volume.dagger
-import android.view.accessibility.CaptioningManager
import com.android.systemui.accessibility.data.repository.CaptioningRepository
import com.android.systemui.accessibility.data.repository.CaptioningRepositoryImpl
-import com.android.systemui.accessibility.domain.interactor.CaptioningInteractor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import dagger.Binds
import dagger.Module
-import dagger.Provides
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
@Module
interface CaptioningModule {
- companion object {
-
- @Provides
- @SysUISingleton
- fun provideCaptioningRepository(
- captioningManager: CaptioningManager,
- @Background coroutineContext: CoroutineContext,
- @Application coroutineScope: CoroutineScope,
- ): CaptioningRepository =
- CaptioningRepositoryImpl(captioningManager, coroutineContext, coroutineScope)
-
- @Provides
- @SysUISingleton
- fun provideCaptioningInteractor(repository: CaptioningRepository): CaptioningInteractor =
- CaptioningInteractor(repository)
- }
+ @Binds
+ @SysUISingleton
+ fun bindCaptioningRepository(impl: CaptioningRepositoryImpl): CaptioningRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
index 52f2ce63ba21..2e5e389eba9c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
@@ -26,7 +26,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
@VolumePanelScope
class CaptioningAvailabilityCriteria
@@ -45,7 +45,7 @@ constructor(
else VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_GONE
)
}
- .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override fun isAvailable(): Flow<Boolean> = availability
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeCaptioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeCaptioningRepository.kt
index 2a0e764279d6..a6394631d236 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeCaptioningRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeCaptioningRepository.kt
@@ -16,25 +16,31 @@
package com.android.systemui.accessibility.data.repository
+import com.android.systemui.accessibility.data.model.CaptioningModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeCaptioningRepository : CaptioningRepository {
- private val mutableIsSystemAudioCaptioningEnabled = MutableStateFlow(false)
- override val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
- get() = mutableIsSystemAudioCaptioningEnabled.asStateFlow()
-
- private val mutableIsSystemAudioCaptioningUiEnabled = MutableStateFlow(false)
- override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
- get() = mutableIsSystemAudioCaptioningUiEnabled.asStateFlow()
+ private val mutableCaptioningModel = MutableStateFlow<CaptioningModel?>(null)
+ override val captioningModel: StateFlow<CaptioningModel?> = mutableCaptioningModel.asStateFlow()
override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
- mutableIsSystemAudioCaptioningEnabled.value = isEnabled
+ mutableCaptioningModel.value =
+ CaptioningModel(
+ isSystemAudioCaptioningEnabled = isEnabled,
+ isSystemAudioCaptioningUiEnabled =
+ mutableCaptioningModel.value?.isSystemAudioCaptioningUiEnabled == true,
+ )
}
- fun setIsSystemAudioCaptioningUiEnabled(isSystemAudioCaptioningUiEnabled: Boolean) {
- mutableIsSystemAudioCaptioningUiEnabled.value = isSystemAudioCaptioningUiEnabled
+ fun setIsSystemAudioCaptioningUiEnabled(isEnabled: Boolean) {
+ mutableCaptioningModel.value =
+ CaptioningModel(
+ isSystemAudioCaptioningEnabled =
+ mutableCaptioningModel.value?.isSystemAudioCaptioningEnabled == true,
+ isSystemAudioCaptioningUiEnabled = isEnabled,
+ )
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/utils/FakeUserScopedService.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/utils/FakeUserScopedService.kt
new file mode 100644
index 000000000000..78763f97adc3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/utils/FakeUserScopedService.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.systemui.user.utils
+
+import android.os.UserHandle
+
+class FakeUserScopedService<T>(private val defaultImplementation: T) : UserScopedService<T> {
+
+ private val implementations = mutableMapOf<UserHandle, T>()
+
+ fun addImplementation(user: UserHandle, implementation: T) {
+ implementations[user] = implementation
+ }
+
+ fun removeImplementation(user: UserHandle): T? = implementations.remove(user)
+
+ override fun forUser(user: UserHandle): T =
+ implementations.getOrDefault(user, defaultImplementation)
+}