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

Commit 7feb58a1 authored by Evan Laird's avatar Evan Laird Committed by Android (Google) Code Review
Browse files

Merge "[Mobile] Create a child scope for mobile view models" into main

parents dea4d49a 9cc60a05
Loading
Loading
Loading
Loading
+31 −18
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
@@ -31,6 +30,8 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
@@ -58,9 +59,8 @@ constructor(
    private val flags: FeatureFlagsClassic,
    @Application private val scope: CoroutineScope,
) {
    @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
    @VisibleForTesting
    val mobileIconInteractorSubIdCache = mutableMapOf<Int, MobileIconInteractor>()
    val reuseCache = mutableMapOf<Int, Pair<MobileIconViewModel, CoroutineScope>>()

    val subscriptionIdsFlow: StateFlow<List<Int>> =
        interactor.filteredSubscriptions
@@ -109,24 +109,37 @@ constructor(
    }

    private fun commonViewModelForSub(subId: Int): MobileIconViewModelCommon {
        return mobileIconSubIdCache[subId]
            ?: MobileIconViewModel(
        return reuseCache.getOrPut(subId) { createViewModel(subId) }.first
    }

    private fun createViewModel(subId: Int): Pair<MobileIconViewModel, CoroutineScope> {
        // Create a child scope so we can cancel it
        val vmScope = scope.createChildScope()
        val vm =
            MobileIconViewModel(
                subId,
                interactor.getMobileConnectionInteractorForSubId(subId),
                airplaneModeInteractor,
                constants,
                flags,
                    scope,
                vmScope,
            )
                .also { mobileIconSubIdCache[subId] = it }

        return Pair(vm, vmScope)
    }

    private fun invalidateCaches(subIds: List<Int>) {
        val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
        subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
    private fun CoroutineScope.createChildScope() =
        CoroutineScope(coroutineContext + Job(coroutineContext[Job]))

        mobileIconInteractorSubIdCache.keys
    private fun invalidateCaches(subIds: List<Int>) {
        reuseCache.keys
            .filter { !subIds.contains(it) }
            .forEach { subId -> mobileIconInteractorSubIdCache.remove(subId) }
            .forEach { id ->
                reuseCache
                    .remove(id)
                    // Cancel the view model's scope after removing it
                    ?.second
                    ?.cancel()
            }
    }
}
+27 −3
Original line number Diff line number Diff line
@@ -38,9 +38,12 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.isActive
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -156,14 +159,35 @@ class MobileIconsViewModelTest : SysuiTestCase() {
            val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)

            // Both impls are cached
            assertThat(underTest.mobileIconSubIdCache)
                .containsExactly(1, model1.commonImpl, 2, model2.commonImpl)
            assertThat(underTest.reuseCache.keys).containsExactly(1, 2)

            // SUB_1 is removed from the list...
            interactor.filteredSubscriptions.value = listOf(SUB_2)

            // ... and dropped from the cache
            assertThat(underTest.mobileIconSubIdCache).containsExactly(2, model2.commonImpl)
            assertThat(underTest.reuseCache.keys).containsExactly(2)
        }

    @Test
    fun caching_invalidatedViewModelsAreCanceled() =
        testScope.runTest {
            // Retrieve models to trigger caching
            val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
            val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)

            var scope1 = underTest.reuseCache[1]?.second
            var scope2 = underTest.reuseCache[2]?.second

            // Scopes are not canceled
            assertTrue(scope1!!.isActive)
            assertTrue(scope2!!.isActive)

            // SUB_1 is removed from the list...
            interactor.filteredSubscriptions.value = listOf(SUB_2)

            // scope1 is canceled
            assertFalse(scope1!!.isActive)
            assertTrue(scope2!!.isActive)
        }

    @Test