Loading packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +2 −33 Original line number Original line 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 java.io.PrintWriter import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope 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.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 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. * the list of available mobile lines of service for which we want to show icons. */ */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @SysUISingleton class MobileUiAdapter class MobileUiAdapter @Inject @Inject constructor( constructor( interactor: MobileIconsInteractor, private val iconController: StatusBarIconController, private val iconController: StatusBarIconController, private val iconsViewModelFactory: MobileIconsViewModel.Factory, val mobileIconsViewModel: MobileIconsViewModel, private val logger: MobileViewLogger, private val logger: MobileViewLogger, @Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope, private val statusBarPipelineFlags: StatusBarPipelineFlags, private val statusBarPipelineFlags: StatusBarPipelineFlags, ) : CoreStartable { ) : 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 isCollecting: Boolean = false private var lastValue: List<Int>? = null private var lastValue: List<Int>? = null Loading @@ -90,7 +59,7 @@ constructor( if (statusBarPipelineFlags.useNewMobileIcons()) { if (statusBarPipelineFlags.useNewMobileIcons()) { scope.launch { scope.launch { isCollecting = true isCollecting = true mobileSubIds.collectLatest { mobileIconsViewModel.subscriptionIdsFlow.collectLatest { logger.logUiAdapterSubIdsSentToIconController(it) logger.logUiAdapterSubIdsSentToIconController(it) lastValue = it lastValue = it iconController.setNewMobileIconSubIds(it) iconController.setNewMobileIconSubIds(it) Loading packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt +0 −9 Original line number Original line Diff line number Diff line Loading @@ -41,15 +41,6 @@ constructor( private val collectionStatuses = mutableMapOf<String, Boolean>() 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>) { fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) { buffer.log( buffer.log( TAG, TAG, Loading packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +14 −28 Original line number Original line 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 com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.launch /** /** * View model for describing the system's current mobile cellular connections. The result is a list * 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 * of [MobileIconViewModel]s which describe the individual icons and can be bound to * [ModernStatusBarMobileView] * [ModernStatusBarMobileView]. */ */ @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MobileIconsViewModel class MobileIconsViewModel @Inject @Inject constructor( constructor( val subscriptionIdsFlow: StateFlow<List<Int>>, val logger: MobileViewLogger, val logger: MobileViewLogger, private val verboseLogger: VerboseMobileViewLogger, private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, private val interactor: MobileIconsInteractor, Loading @@ -51,6 +56,13 @@ constructor( ) { ) { @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() @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 { init { scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } } } Loading Loading @@ -79,30 +91,4 @@ constructor( val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } subIdsToRemove.forEach { mobileIconSubIdCache.remove(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 Original line 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.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest Loading Loading @@ -69,14 +68,8 @@ class MobileIconsViewModelTest : SysuiTestCase() { FakeConnectivityRepository(), FakeConnectivityRepository(), ) ) val subscriptionIdsFlow = interactor.filteredSubscriptions .map { subs -> subs.map { it.subscriptionId } } .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf()) underTest = underTest = MobileIconsViewModel( MobileIconsViewModel( subscriptionIdsFlow, logger, logger, verboseLogger, verboseLogger, interactor, interactor, Loading @@ -89,6 +82,32 @@ class MobileIconsViewModelTest : SysuiTestCase() { interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) 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 @Test fun `caching - mobile icon view model is reused for same sub id`() = fun `caching - mobile icon view model is reused for same sub id`() = testScope.runTest { testScope.runTest { Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +2 −33 Original line number Original line 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 java.io.PrintWriter import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope 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.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 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. * the list of available mobile lines of service for which we want to show icons. */ */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @SysUISingleton class MobileUiAdapter class MobileUiAdapter @Inject @Inject constructor( constructor( interactor: MobileIconsInteractor, private val iconController: StatusBarIconController, private val iconController: StatusBarIconController, private val iconsViewModelFactory: MobileIconsViewModel.Factory, val mobileIconsViewModel: MobileIconsViewModel, private val logger: MobileViewLogger, private val logger: MobileViewLogger, @Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope, private val statusBarPipelineFlags: StatusBarPipelineFlags, private val statusBarPipelineFlags: StatusBarPipelineFlags, ) : CoreStartable { ) : 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 isCollecting: Boolean = false private var lastValue: List<Int>? = null private var lastValue: List<Int>? = null Loading @@ -90,7 +59,7 @@ constructor( if (statusBarPipelineFlags.useNewMobileIcons()) { if (statusBarPipelineFlags.useNewMobileIcons()) { scope.launch { scope.launch { isCollecting = true isCollecting = true mobileSubIds.collectLatest { mobileIconsViewModel.subscriptionIdsFlow.collectLatest { logger.logUiAdapterSubIdsSentToIconController(it) logger.logUiAdapterSubIdsSentToIconController(it) lastValue = it lastValue = it iconController.setNewMobileIconSubIds(it) iconController.setNewMobileIconSubIds(it) Loading
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt +0 −9 Original line number Original line Diff line number Diff line Loading @@ -41,15 +41,6 @@ constructor( private val collectionStatuses = mutableMapOf<String, Boolean>() 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>) { fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) { buffer.log( buffer.log( TAG, TAG, Loading
packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +14 −28 Original line number Original line 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 com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.launch /** /** * View model for describing the system's current mobile cellular connections. The result is a list * 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 * of [MobileIconViewModel]s which describe the individual icons and can be bound to * [ModernStatusBarMobileView] * [ModernStatusBarMobileView]. */ */ @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MobileIconsViewModel class MobileIconsViewModel @Inject @Inject constructor( constructor( val subscriptionIdsFlow: StateFlow<List<Int>>, val logger: MobileViewLogger, val logger: MobileViewLogger, private val verboseLogger: VerboseMobileViewLogger, private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, private val interactor: MobileIconsInteractor, Loading @@ -51,6 +56,13 @@ constructor( ) { ) { @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() @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 { init { scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } } } Loading Loading @@ -79,30 +91,4 @@ constructor( val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } subIdsToRemove.forEach { mobileIconSubIdCache.remove(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 Original line 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.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest Loading Loading @@ -69,14 +68,8 @@ class MobileIconsViewModelTest : SysuiTestCase() { FakeConnectivityRepository(), FakeConnectivityRepository(), ) ) val subscriptionIdsFlow = interactor.filteredSubscriptions .map { subs -> subs.map { it.subscriptionId } } .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf()) underTest = underTest = MobileIconsViewModel( MobileIconsViewModel( subscriptionIdsFlow, logger, logger, verboseLogger, verboseLogger, interactor, interactor, Loading @@ -89,6 +82,32 @@ class MobileIconsViewModelTest : SysuiTestCase() { interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) 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 @Test fun `caching - mobile icon view model is reused for same sub id`() = fun `caching - mobile icon view model is reused for same sub id`() = testScope.runTest { testScope.runTest { Loading