Loading packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +2 −33 Original line number Diff line number Diff line Loading @@ -26,15 +26,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsVi import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Loading @@ -46,39 +38,16 @@ import kotlinx.coroutines.launch * the list of available mobile lines of service for which we want to show icons. */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MobileUiAdapter @Inject constructor( interactor: MobileIconsInteractor, private val iconController: StatusBarIconController, private val iconsViewModelFactory: MobileIconsViewModel.Factory, val mobileIconsViewModel: MobileIconsViewModel, private val logger: MobileViewLogger, @Application private val scope: CoroutineScope, private val statusBarPipelineFlags: StatusBarPipelineFlags, ) : CoreStartable { private val mobileSubIds: Flow<List<Int>> = interactor.filteredSubscriptions.mapLatest { subscriptions -> subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } } /** * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which * house the mobile infos. * * NOTE: this should go away as the view presenter learns more about this data pipeline */ private val mobileSubIdsState: StateFlow<List<Int>> = mobileSubIds .distinctUntilChanged() .onEach { logger.logUiAdapterSubIdsUpdated(it) } .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */ val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState) private var isCollecting: Boolean = false private var lastValue: List<Int>? = null Loading @@ -90,7 +59,7 @@ constructor( if (statusBarPipelineFlags.useNewMobileIcons()) { scope.launch { isCollecting = true mobileSubIds.collectLatest { mobileIconsViewModel.subscriptionIdsFlow.collectLatest { logger.logUiAdapterSubIdsSentToIconController(it) lastValue = it iconController.setNewMobileIconSubIds(it) Loading packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt +0 −9 Original line number Diff line number Diff line Loading @@ -41,15 +41,6 @@ constructor( private val collectionStatuses = mutableMapOf<String, Boolean>() fun logUiAdapterSubIdsUpdated(subs: List<Int>) { buffer.log( TAG, LogLevel.INFO, { str1 = subs.toString() }, { "Sub IDs in MobileUiAdapter updated internally: $str1" }, ) } fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) { buffer.log( TAG, Loading packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +14 −28 Original line number Diff line number Diff line Loading @@ -29,18 +29,23 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMob import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** * View model for describing the system's current mobile cellular connections. The result is a list * of [MobileIconViewModel]s which describe the individual icons and can be bound to * [ModernStatusBarMobileView] * [ModernStatusBarMobileView]. */ @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MobileIconsViewModel @Inject constructor( val subscriptionIdsFlow: StateFlow<List<Int>>, val logger: MobileViewLogger, private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, Loading @@ -51,6 +56,13 @@ constructor( ) { @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() val subscriptionIdsFlow: StateFlow<List<Int>> = interactor.filteredSubscriptions .mapLatest { subscriptions -> subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } } .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) init { scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } } Loading Loading @@ -79,30 +91,4 @@ constructor( val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) } } @SysUISingleton class Factory @Inject constructor( private val logger: MobileViewLogger, private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, private val airplaneModeInteractor: AirplaneModeInteractor, private val constants: ConnectivityConstants, @Application private val scope: CoroutineScope, private val statusBarPipelineFlags: StatusBarPipelineFlags, ) { fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel { return MobileIconsViewModel( subscriptionIdsFlow, logger, verboseLogger, interactor, airplaneModeInteractor, constants, scope, statusBarPipelineFlags, ) } } } packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +28 −9 Original line number Diff line number Diff line Loading @@ -32,9 +32,8 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnec import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest Loading Loading @@ -69,14 +68,8 @@ class MobileIconsViewModelTest : SysuiTestCase() { FakeConnectivityRepository(), ) val subscriptionIdsFlow = interactor.filteredSubscriptions .map { subs -> subs.map { it.subscriptionId } } .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf()) underTest = MobileIconsViewModel( subscriptionIdsFlow, logger, verboseLogger, interactor, Loading @@ -89,6 +82,32 @@ class MobileIconsViewModelTest : SysuiTestCase() { interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) } @Test fun subscriptionIdsFlow_matchesInteractor() = testScope.runTest { var latest: List<Int>? = null val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this) interactor.filteredSubscriptions.value = listOf( SubscriptionModel(subscriptionId = 1, isOpportunistic = false), ) assertThat(latest).isEqualTo(listOf(1)) interactor.filteredSubscriptions.value = listOf( SubscriptionModel(subscriptionId = 2, isOpportunistic = false), SubscriptionModel(subscriptionId = 5, isOpportunistic = true), SubscriptionModel(subscriptionId = 7, isOpportunistic = true), ) assertThat(latest).isEqualTo(listOf(2, 5, 7)) interactor.filteredSubscriptions.value = emptyList() assertThat(latest).isEmpty() job.cancel() } @Test fun `caching - mobile icon view model is reused for same sub id`() = testScope.runTest { Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +2 −33 Original line number Diff line number Diff line Loading @@ -26,15 +26,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsVi import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Loading @@ -46,39 +38,16 @@ import kotlinx.coroutines.launch * the list of available mobile lines of service for which we want to show icons. */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MobileUiAdapter @Inject constructor( interactor: MobileIconsInteractor, private val iconController: StatusBarIconController, private val iconsViewModelFactory: MobileIconsViewModel.Factory, val mobileIconsViewModel: MobileIconsViewModel, private val logger: MobileViewLogger, @Application private val scope: CoroutineScope, private val statusBarPipelineFlags: StatusBarPipelineFlags, ) : CoreStartable { private val mobileSubIds: Flow<List<Int>> = interactor.filteredSubscriptions.mapLatest { subscriptions -> subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } } /** * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which * house the mobile infos. * * NOTE: this should go away as the view presenter learns more about this data pipeline */ private val mobileSubIdsState: StateFlow<List<Int>> = mobileSubIds .distinctUntilChanged() .onEach { logger.logUiAdapterSubIdsUpdated(it) } .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */ val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState) private var isCollecting: Boolean = false private var lastValue: List<Int>? = null Loading @@ -90,7 +59,7 @@ constructor( if (statusBarPipelineFlags.useNewMobileIcons()) { scope.launch { isCollecting = true mobileSubIds.collectLatest { mobileIconsViewModel.subscriptionIdsFlow.collectLatest { logger.logUiAdapterSubIdsSentToIconController(it) lastValue = it iconController.setNewMobileIconSubIds(it) Loading
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt +0 −9 Original line number Diff line number Diff line Loading @@ -41,15 +41,6 @@ constructor( private val collectionStatuses = mutableMapOf<String, Boolean>() fun logUiAdapterSubIdsUpdated(subs: List<Int>) { buffer.log( TAG, LogLevel.INFO, { str1 = subs.toString() }, { "Sub IDs in MobileUiAdapter updated internally: $str1" }, ) } fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) { buffer.log( TAG, Loading
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +14 −28 Original line number Diff line number Diff line Loading @@ -29,18 +29,23 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMob import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** * View model for describing the system's current mobile cellular connections. The result is a list * of [MobileIconViewModel]s which describe the individual icons and can be bound to * [ModernStatusBarMobileView] * [ModernStatusBarMobileView]. */ @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MobileIconsViewModel @Inject constructor( val subscriptionIdsFlow: StateFlow<List<Int>>, val logger: MobileViewLogger, private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, Loading @@ -51,6 +56,13 @@ constructor( ) { @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() val subscriptionIdsFlow: StateFlow<List<Int>> = interactor.filteredSubscriptions .mapLatest { subscriptions -> subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } } .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) init { scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } } Loading Loading @@ -79,30 +91,4 @@ constructor( val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) } } @SysUISingleton class Factory @Inject constructor( private val logger: MobileViewLogger, private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, private val airplaneModeInteractor: AirplaneModeInteractor, private val constants: ConnectivityConstants, @Application private val scope: CoroutineScope, private val statusBarPipelineFlags: StatusBarPipelineFlags, ) { fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel { return MobileIconsViewModel( subscriptionIdsFlow, logger, verboseLogger, interactor, airplaneModeInteractor, constants, scope, statusBarPipelineFlags, ) } } }
packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +28 −9 Original line number Diff line number Diff line Loading @@ -32,9 +32,8 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnec import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest Loading Loading @@ -69,14 +68,8 @@ class MobileIconsViewModelTest : SysuiTestCase() { FakeConnectivityRepository(), ) val subscriptionIdsFlow = interactor.filteredSubscriptions .map { subs -> subs.map { it.subscriptionId } } .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf()) underTest = MobileIconsViewModel( subscriptionIdsFlow, logger, verboseLogger, interactor, Loading @@ -89,6 +82,32 @@ class MobileIconsViewModelTest : SysuiTestCase() { interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) } @Test fun subscriptionIdsFlow_matchesInteractor() = testScope.runTest { var latest: List<Int>? = null val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this) interactor.filteredSubscriptions.value = listOf( SubscriptionModel(subscriptionId = 1, isOpportunistic = false), ) assertThat(latest).isEqualTo(listOf(1)) interactor.filteredSubscriptions.value = listOf( SubscriptionModel(subscriptionId = 2, isOpportunistic = false), SubscriptionModel(subscriptionId = 5, isOpportunistic = true), SubscriptionModel(subscriptionId = 7, isOpportunistic = true), ) assertThat(latest).isEqualTo(listOf(2, 5, 7)) interactor.filteredSubscriptions.value = emptyList() assertThat(latest).isEmpty() job.cancel() } @Test fun `caching - mobile icon view model is reused for same sub id`() = testScope.runTest { Loading