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

Commit 656d5335 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov Committed by Android (Google) Code Review
Browse files

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

parents 02016279 7d9af206
Loading
Loading
Loading
Loading
+2 −33
Original line number Original line Diff line number Diff line
@@ -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


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


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