[Sb refactor] Mobile icon view model logging

Add logging to the mobile icon view models. In order to keep the logs
from duplicating, this CL also adds:

1. The reuse of a single instance of the MobileIconsViewModel
   (top-level), which
2. Caches the individual MobileIconViewModel common implementations on a
   per-subscription basis. There is now a 1-1 correspondence of the
   common mobile icon view model implementations and the number of
   subscriptions.

Also updated the MobileIconViewModelTest and
LocationBasedMobileIconViewModelTest classes to use the TestScope method
of running tests.

Test: adb logcat | grep SbConnectivity
Test: tests in tests/src/com/android/systemui/statusbar/pipeline/mobile/*
Bug: 238425913
Change-Id: I5157cdcfb054d4629db0f9b11da8b583760ce471
This commit is contained in:
Evan Laird
2022-12-16 11:04:31 -05:00
parent 4e53da6d8d
commit 81799a75f7
22 changed files with 406 additions and 106 deletions

View File

@@ -387,7 +387,7 @@ public interface StatusBarIconController {
if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
// This starts the flow for the new pipeline, and will notify us of changes if
// {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
} else {
mMobileIconsViewModel = null;

View File

@@ -20,6 +20,7 @@ import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import kotlinx.coroutines.flow.Flow
@@ -39,6 +40,13 @@ import kotlinx.coroutines.flow.StateFlow
interface MobileConnectionRepository {
/** The subscriptionId that this connection represents */
val subId: Int
/**
* The table log buffer created for this connection. Will have the name "MobileConnectionLog
* [subId]"
*/
val tableLogBuffer: TableLogBuffer
/**
* A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
* listener + model.

View File

@@ -24,6 +24,8 @@ import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
@@ -60,6 +62,7 @@ constructor(
private val dataSource: DemoModeMobileConnectionDataSource,
@Application private val scope: CoroutineScope,
context: Context,
private val logFactory: TableLogBufferFactory,
) : MobileConnectionsRepository {
private var demoCommandJob: Job? = null
@@ -149,7 +152,16 @@ constructor(
override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
return connectionRepoCache[subId]
?: DemoMobileConnectionRepository(subId).also { connectionRepoCache[subId] = it }
?: createDemoMobileConnectionRepo(subId).also { connectionRepoCache[subId] = it }
}
private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository {
val tableLogBuffer = logFactory.create("DemoMobileConnectionLog [$subId]", 100)
return DemoMobileConnectionRepository(
subId,
tableLogBuffer,
)
}
override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
@@ -260,7 +272,10 @@ constructor(
}
}
class DemoMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
class DemoMobileConnectionRepository(
override val subId: Int,
override val tableLogBuffer: TableLogBuffer,
) : MobileConnectionRepository {
override val connectionInfo = MutableStateFlow(MobileConnectionModel())
override val dataEnabled = MutableStateFlow(true)

View File

@@ -96,6 +96,8 @@ class MobileConnectionRepositoryImpl(
private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
override val tableLogBuffer: TableLogBuffer = mobileLogger
override val connectionInfo: StateFlow<MobileConnectionModel> = run {
var state = MobileConnectionModel()
conflatedCallbackFlow {

View File

@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -35,6 +36,9 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor {
/** The table log created for this connection */
val tableLogBuffer: TableLogBuffer
/** The current mobile data activity */
val activity: Flow<DataActivityModel>
@@ -97,6 +101,8 @@ class MobileIconInteractorImpl(
) : MobileIconInteractor {
private val connectionInfo = connectionRepository.connectionInfo
override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled

View File

@@ -20,7 +20,6 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
@@ -70,6 +69,9 @@ constructor(
private val mobileSubIdsState: StateFlow<List<Int>> =
mobileSubIds.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)
override fun start() {
// Only notify the icon controller if we want to *render* the new icons.
// Note that this flow may still run if
@@ -81,12 +83,4 @@ constructor(
}
}
}
/**
* Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
* lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
* the old view system.
*/
fun createMobileIconsViewModel(): MobileIconsViewModel =
iconsViewModelFactory.create(mobileSubIdsState)
}

View File

@@ -18,7 +18,10 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import android.graphics.Color
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
/**
@@ -30,34 +33,50 @@ import kotlinx.coroutines.flow.flowOf
*/
abstract class LocationBasedMobileViewModel(
val commonImpl: MobileIconViewModelCommon,
val logger: ConnectivityPipelineLogger,
) : MobileIconViewModelCommon by commonImpl {
abstract val tint: Flow<Int>
companion object {
fun viewModelForLocation(
commonImpl: MobileIconViewModelCommon,
logger: ConnectivityPipelineLogger,
loc: StatusBarLocation,
): LocationBasedMobileViewModel =
when (loc) {
StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl)
StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl)
StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl)
StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl, logger)
StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, logger)
StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, logger)
}
}
}
class HomeMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl) {
override val tint: Flow<Int> = flowOf(Color.CYAN)
logger: ConnectivityPipelineLogger,
) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
override val tint: Flow<Int> =
flowOf(Color.CYAN)
.distinctUntilChanged()
.logOutputChange(logger, "HOME tint(${commonImpl.subscriptionId})")
}
class QsMobileIconViewModel(commonImpl: MobileIconViewModelCommon) :
MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl) {
override val tint: Flow<Int> = flowOf(Color.GREEN)
class QsMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
logger: ConnectivityPipelineLogger,
) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
override val tint: Flow<Int> =
flowOf(Color.GREEN)
.distinctUntilChanged()
.logOutputChange(logger, "QS tint(${commonImpl.subscriptionId})")
}
class KeyguardMobileIconViewModel(commonImpl: MobileIconViewModelCommon) :
MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl) {
override val tint: Flow<Int> = flowOf(Color.MAGENTA)
class KeyguardMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
logger: ConnectivityPipelineLogger,
) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
override val tint: Flow<Int> =
flowOf(Color.MAGENTA)
.distinctUntilChanged()
.logOutputChange(logger, "KEYGUARD tint(${commonImpl.subscriptionId})")
}

View File

@@ -16,23 +16,27 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import android.graphics.Color
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.logDiffsForTable
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.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
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.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** Common interface for all of the location-based mobile icon view models. */
interface MobileIconViewModelCommon {
@@ -53,7 +57,12 @@ interface MobileIconViewModelCommon {
* subscription's information.
*
* There will be exactly one [MobileIconViewModel] per filtered subscription offered from
* [MobileIconsInteractor.filteredSubscriptions]
* [MobileIconsInteractor.filteredSubscriptions].
*
* For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon]
* interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view
* model gets the exact same information, as well as allows us to log that unified state only once
* per icon.
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@@ -63,12 +72,14 @@ constructor(
iconInteractor: MobileIconInteractor,
logger: ConnectivityPipelineLogger,
constants: ConnectivityConstants,
scope: CoroutineScope,
) : MobileIconViewModelCommon {
/** Whether or not to show the error state of [SignalDrawable] */
private val showExclamationMark: Flow<Boolean> =
iconInteractor.isDefaultDataEnabled.mapLatest { !it }
override val iconId: Flow<Int> =
override val iconId: Flow<Int> = run {
val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value)
combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
level,
numberOfLevels,
@@ -76,31 +87,56 @@ constructor(
SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
}
.distinctUntilChanged()
.logOutputChange(logger, "iconId($subscriptionId)")
.logDiffsForTable(
iconInteractor.tableLogBuffer,
columnPrefix = "",
columnName = "iconId",
initialValue = initial,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
override val networkTypeIcon: Flow<Icon?> =
combine(
iconInteractor.networkTypeIconGroup,
iconInteractor.isDataConnected,
iconInteractor.isDataEnabled,
iconInteractor.isDefaultConnectionFailed,
iconInteractor.alwaysShowDataRatIcon,
) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
val desc =
if (networkTypeIconGroup.dataContentDescription != 0)
ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
else null
val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
return@combine when {
alwaysShow -> icon
!dataConnected -> null
!dataEnabled -> null
failedConnection -> null
else -> icon
iconInteractor.networkTypeIconGroup,
iconInteractor.isDataConnected,
iconInteractor.isDataEnabled,
iconInteractor.isDefaultConnectionFailed,
iconInteractor.alwaysShowDataRatIcon,
) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
val desc =
if (networkTypeIconGroup.dataContentDescription != 0)
ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
else null
val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
return@combine when {
alwaysShow -> icon
!dataConnected -> null
!dataEnabled -> null
failedConnection -> null
else -> icon
}
}
}
.distinctUntilChanged()
.onEach {
// This is done as an onEach side effect since Icon is not Diffable (yet)
iconInteractor.tableLogBuffer.logChange(
prefix = "",
columnName = "networkTypeIcon",
value = it.toString(),
)
}
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
override val roaming: Flow<Boolean> = iconInteractor.isRoaming
override val roaming: StateFlow<Boolean> =
iconInteractor.isRoaming
.logDiffsForTable(
iconInteractor.tableLogBuffer,
columnPrefix = "",
columnName = "roaming",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
private val activity: Flow<DataActivityModel?> =
if (!constants.shouldShowActivityConfig) {
@@ -109,10 +145,39 @@ constructor(
iconInteractor.activity
}
override val activityInVisible: Flow<Boolean> = activity.map { it?.hasActivityIn ?: false }
override val activityOutVisible: Flow<Boolean> = activity.map { it?.hasActivityOut ?: false }
override val activityContainerVisible: Flow<Boolean> =
activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
override val activityInVisible: Flow<Boolean> =
activity
.map { it?.hasActivityIn ?: false }
.distinctUntilChanged()
.logDiffsForTable(
iconInteractor.tableLogBuffer,
columnPrefix = "",
columnName = "activityInVisible",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
val tint: Flow<Int> = flowOf(Color.CYAN)
override val activityOutVisible: Flow<Boolean> =
activity
.map { it?.hasActivityOut ?: false }
.distinctUntilChanged()
.logDiffsForTable(
iconInteractor.tableLogBuffer,
columnPrefix = "",
columnName = "activityOutVisible",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val activityContainerVisible: Flow<Boolean> =
activity
.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
.distinctUntilChanged()
.logDiffsForTable(
iconInteractor.tableLogBuffer,
columnPrefix = "",
columnName = "activityContainerVisible",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
}

View File

@@ -14,18 +14,19 @@
* limitations under the License.
*/
@file:OptIn(InternalCoroutinesApi::class)
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
/**
* View model for describing the system's current mobile cellular connections. The result is a list
@@ -39,18 +40,32 @@ constructor(
private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
) {
/** TODO: do we need to cache these? */
@VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
init {
scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } }
}
fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel {
val common =
MobileIconViewModel(
subId,
interactor.createMobileConnectionInteractorForSubId(subId),
logger,
constants,
)
mobileIconSubIdCache[subId]
?: MobileIconViewModel(
subId,
interactor.createMobileConnectionInteractorForSubId(subId),
logger,
constants,
scope,
)
.also { mobileIconSubIdCache[subId] = it }
return LocationBasedMobileViewModel.viewModelForLocation(common, location)
return LocationBasedMobileViewModel.viewModelForLocation(common, logger, location)
}
private fun removeInvalidModelsFromCache(subIds: List<Int>) {
val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
}
class Factory
@@ -59,6 +74,7 @@ constructor(
private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
) {
fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel(
@@ -66,6 +82,7 @@ constructor(
interactor,
logger,
constants,
scope,
)
}
}

View File

@@ -16,12 +16,16 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
class FakeMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
class FakeMobileConnectionRepository(
override val subId: Int,
override val tableLogBuffer: TableLogBuffer,
) : MobileConnectionRepository {
private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
override val connectionInfo = _connectionInfo

View File

@@ -22,14 +22,17 @@ import android.telephony.TelephonyManager
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) :
MobileConnectionsRepository {
class FakeMobileConnectionsRepository(
mobileMappings: MobileMappingsProxy,
val tableLogBuffer: TableLogBuffer,
) : MobileConnectionsRepository {
val GSM_KEY = mobileMappings.toIconKey(GSM)
val LTE_KEY = mobileMappings.toIconKey(LTE)
val UMTS_KEY = mobileMappings.toIconKey(UMTS)
@@ -63,7 +66,7 @@ class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) :
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
return subIdRepos[subId]
?: FakeMobileConnectionRepository(subId).also { subIdRepos[subId] = it }
?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it }
}
private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)

View File

@@ -24,6 +24,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
@@ -37,6 +39,7 @@ import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -69,12 +72,14 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
private lateinit var realRepo: MobileConnectionsRepositoryImpl
private lateinit var demoRepo: DemoMobileConnectionsRepository
private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
private lateinit var logFactory: TableLogBufferFactory
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var demoModeController: DemoModeController
@Mock private lateinit var dumpManager: DumpManager
private val globalSettings = FakeSettings()
private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
@@ -86,6 +91,8 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
// Never start in demo mode
whenever(demoModeController.isInDemoMode).thenReturn(false)
@@ -114,6 +121,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
dataSource = mockDataSource,
scope = scope,
context = context,
logFactory = logFactory,
)
underTest =

View File

@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -30,6 +31,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
@@ -54,6 +56,9 @@ import org.junit.runners.Parameterized.Parameters
@RunWith(Parameterized::class)
internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
SysuiTestCase() {
private val logFactory = TableLogBufferFactory(mock(), FakeSystemClock())
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -76,6 +81,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
dataSource = mockDataSource,
scope = testScope.backgroundScope,
context = context,
logFactory = logFactory,
)
connectionsRepo.startProcessingCommands()

View File

@@ -23,6 +23,8 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -32,6 +34,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,6 +50,9 @@ import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
private val dumpManager: DumpManager = mock()
private val logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -68,6 +74,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
dataSource = mockDataSource,
scope = testScope.backgroundScope,
context = context,
logFactory = logFactory,
)
underTest.startProcessingCommands()

View File

@@ -88,6 +88,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
class MobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
private lateinit var connectionsRepo: FakeMobileConnectionsRepository
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
@@ -96,7 +97,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
private val scope = CoroutineScope(IMMEDIATE)
private val mobileMappings = FakeMobileMappingsProxy()
private val globalSettings = FakeSettings()
private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings)
@Before
fun setUp() {
@@ -104,6 +104,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
globalSettings.userId = UserHandle.USER_ALL
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
underTest =
MobileConnectionRepositoryImpl(
context,

View File

@@ -19,11 +19,14 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CellSignalStrength
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconInteractor : MobileIconInteractor {
class FakeMobileIconInteractor(
override val tableLogBuffer: TableLogBuffer,
) : MobileIconInteractor {
override val alwaysShowDataRatIcon = MutableStateFlow(false)
override val activity =

View File

@@ -22,12 +22,15 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
class FakeMobileIconsInteractor(
mobileMappings: MobileMappingsProxy,
val tableLogBuffer: TableLogBuffer,
) : MobileIconsInteractor {
val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
val LTE_KEY = mobileMappings.toIconKey(LTE)
val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
@@ -48,8 +51,7 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco
override val isDefaultConnectionFailed = MutableStateFlow(false)
private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
override val filteredSubscriptions: Flow<List<SubscriptionModel>> = _filteredSubscriptions
override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
@@ -67,7 +69,7 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco
/** Always returns a new fake interactor */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
return FakeMobileIconInteractor()
return FakeMobileIconInteractor(tableLogBuffer)
}
companion object {

View File

@@ -49,8 +49,8 @@ import org.junit.Test
class MobileIconInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconInteractor
private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID)
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock())
private val scope = CoroutineScope(IMMEDIATE)

View File

@@ -20,6 +20,7 @@ import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
@@ -28,6 +29,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSe
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -44,9 +46,9 @@ import org.mockito.MockitoAnnotations
@SmallTest
class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor
private lateinit var connectionsRepository: FakeMobileConnectionsRepository
private val userSetupRepository = FakeUserSetupRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy)
private val scope = CoroutineScope(IMMEDIATE)
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -55,6 +57,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
connectionsRepository.setMobileConnectionRepositoryMap(
mapOf(
SUB_1_ID to CONNECTION_1,
@@ -290,21 +293,23 @@ class MobileIconsInteractorTest : SysuiTestCase() {
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private val tableLogBuffer =
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
private const val SUB_1_ID = 1
private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID)
private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
private const val SUB_2_ID = 2
private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID)
private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, tableLogBuffer)
private const val SUB_3_ID = 3
private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true)
private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID)
private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, tableLogBuffer)
private const val SUB_4_ID = 4
private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true)
private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID)
private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer)
}
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
@@ -43,9 +44,10 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
private lateinit var homeIcon: HomeMobileIconViewModel
private lateinit var qsIcon: QsMobileIconViewModel
private lateinit var keyguardIcon: KeyguardMobileIconViewModel
private val interactor = FakeMobileIconInteractor()
private lateinit var interactor: FakeMobileIconInteractor
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -53,6 +55,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
interactor = FakeMobileIconInteractor(tableLogBuffer)
interactor.apply {
setLevel(1)
setIsDefaultDataEnabled(true)
@@ -62,11 +65,12 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
setNumberOfLevels(4)
isDataConnected.value = true
}
commonImpl = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
commonImpl =
MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
homeIcon = HomeMobileIconViewModel(commonImpl)
qsIcon = QsMobileIconViewModel(commonImpl)
keyguardIcon = KeyguardMobileIconViewModel(commonImpl)
homeIcon = HomeMobileIconViewModel(commonImpl, logger)
qsIcon = QsMobileIconViewModel(commonImpl, logger)
keyguardIcon = KeyguardMobileIconViewModel(commonImpl, logger)
}
@Test

View File

@@ -22,32 +22,42 @@ import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileIconViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconViewModel
private val interactor = FakeMobileIconInteractor()
private lateinit var interactor: FakeMobileIconInteractor
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
interactor = FakeMobileIconInteractor(tableLogBuffer)
interactor.apply {
setLevel(1)
setIsDefaultDataEnabled(true)
@@ -57,12 +67,13 @@ class MobileIconViewModelTest : SysuiTestCase() {
setNumberOfLevels(4)
isDataConnected.value = true
}
underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
underTest =
MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
}
@Test
fun iconId_correctLevel_notCutout() =
runBlocking(IMMEDIATE) {
testScope.runTest {
var latest: Int? = null
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
val expected = defaultSignal()
@@ -74,7 +85,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun iconId_cutout_whenDefaultDataDisabled() =
runBlocking(IMMEDIATE) {
testScope.runTest {
interactor.setIsDefaultDataEnabled(false)
var latest: Int? = null
@@ -88,7 +99,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun networkType_dataEnabled_groupIsRepresented() =
runBlocking(IMMEDIATE) {
testScope.runTest {
val expected =
Icon.Resource(
THREE_G.dataType,
@@ -106,7 +117,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun networkType_nullWhenDisabled() =
runBlocking(IMMEDIATE) {
testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(false)
var latest: Icon? = null
@@ -119,7 +130,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun networkType_nullWhenFailedConnection() =
runBlocking(IMMEDIATE) {
testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(true)
interactor.setIsFailedConnection(true)
@@ -133,7 +144,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun networkType_nullWhenDataDisconnects() =
runBlocking(IMMEDIATE) {
testScope.runTest {
val initial =
Icon.Resource(
THREE_G.dataType,
@@ -157,7 +168,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun networkType_null_changeToDisabled() =
runBlocking(IMMEDIATE) {
testScope.runTest {
val expected =
Icon.Resource(
THREE_G.dataType,
@@ -180,7 +191,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun networkType_alwaysShow_shownEvenWhenDisabled() =
runBlocking(IMMEDIATE) {
testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(true)
interactor.alwaysShowDataRatIcon.value = true
@@ -200,7 +211,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun networkType_alwaysShow_shownEvenWhenDisconnected() =
runBlocking(IMMEDIATE) {
testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.isDataConnected.value = false
interactor.alwaysShowDataRatIcon.value = true
@@ -220,7 +231,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
runBlocking(IMMEDIATE) {
testScope.runTest {
interactor.setIconGroup(THREE_G)
interactor.setIsFailedConnection(true)
interactor.alwaysShowDataRatIcon.value = true
@@ -240,7 +251,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun roaming() =
runBlocking(IMMEDIATE) {
testScope.runTest {
interactor.isRoaming.value = true
var latest: Boolean? = null
val job = underTest.roaming.onEach { latest = it }.launchIn(this)
@@ -256,10 +267,17 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun `data activity - null when config is off`() =
runBlocking(IMMEDIATE) {
testScope.runTest {
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(false)
underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
underTest =
MobileIconViewModel(
SUB_1_ID,
interactor,
logger,
constants,
testScope.backgroundScope,
)
var inVisible: Boolean? = null
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -288,10 +306,17 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test
fun `data activity - config on - test indicators`() =
runBlocking(IMMEDIATE) {
testScope.runTest {
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(true)
underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
underTest =
MobileIconViewModel(
SUB_1_ID,
interactor,
logger,
constants,
testScope.backgroundScope,
)
var inVisible: Boolean? = null
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -341,7 +366,6 @@ class MobileIconViewModelTest : SysuiTestCase() {
}
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
/** Convenience constructor for these tests */

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
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.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileIconsViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsViewModel
private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
val subscriptionIdsFlow =
interactor.filteredSubscriptions
.map { subs -> subs.map { it.subscriptionId } }
.stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf())
underTest =
MobileIconsViewModel(
subscriptionIdsFlow,
interactor,
logger,
constants,
testScope.backgroundScope,
)
interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
}
@Test
fun `caching - mobile icon view model is reused for same sub id`() =
testScope.runTest {
val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
val model2 = underTest.viewModelForSub(1, StatusBarLocation.QS)
assertThat(model1.commonImpl).isSameInstanceAs(model2.commonImpl)
}
@Test
fun `caching - invalid view models are removed from cache when sub disappears`() =
testScope.runTest {
// Retrieve models to trigger caching
val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)
// Both impls are cached
assertThat(underTest.mobileIconSubIdCache)
.containsExactly(1, model1.commonImpl, 2, model2.commonImpl)
// 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)
}
companion object {
private val SUB_1 = SubscriptionModel(subscriptionId = 1, isOpportunistic = false)
private val SUB_2 = SubscriptionModel(subscriptionId = 2, isOpportunistic = false)
}
}