Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 763ea84e authored by Caitlin Shkuratov's avatar Caitlin Shkuratov Committed by Automerger Merge Worker
Browse files

Merge "[SB Refactor] Make the parent mobile view model a singleton." into udc-dev am: 656d5335

parents fc9f5bfd 656d5335
Loading
Loading
Loading
Loading
+2 −33
Original line number Diff line number Diff line
@@ -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

/**
@@ -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

@@ -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)
+0 −9
Original line number Diff line number Diff line
@@ -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,
+14 −28
Original line number Diff line number Diff line
@@ -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,
@@ -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) } }
    }
@@ -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,
            )
        }
    }
}
+28 −9
Original line number Diff line number Diff line
@@ -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
@@ -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,
@@ -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 {