1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.pipeline.mobile.data.repository 18 19 import android.telephony.SubscriptionInfo 20 import android.telephony.SubscriptionManager 21 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET 22 import android.telephony.TelephonyManager 23 import androidx.test.ext.junit.runners.AndroidJUnit4 24 import androidx.test.filters.SmallTest 25 import com.android.systemui.SysuiTestCase 26 import com.android.systemui.demomode.DemoMode 27 import com.android.systemui.demomode.DemoModeController 28 import com.android.systemui.dump.DumpManager 29 import com.android.systemui.log.table.TableLogBuffer 30 import com.android.systemui.log.table.tableLogBufferFactory 31 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository 32 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger 33 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel 34 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository 35 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource 36 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel 37 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent 38 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl 39 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy 40 import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy 41 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository 42 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository 43 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository 44 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource 45 import com.android.systemui.testKosmos 46 import com.android.systemui.util.mockito.any 47 import com.android.systemui.util.mockito.kotlinArgumentCaptor 48 import com.android.systemui.util.mockito.mock 49 import com.android.systemui.util.mockito.whenever 50 import com.google.common.truth.Truth.assertThat 51 import kotlinx.coroutines.CoroutineScope 52 import kotlinx.coroutines.Dispatchers 53 import kotlinx.coroutines.ExperimentalCoroutinesApi 54 import kotlinx.coroutines.cancel 55 import kotlinx.coroutines.flow.MutableStateFlow 56 import kotlinx.coroutines.flow.launchIn 57 import kotlinx.coroutines.flow.onEach 58 import kotlinx.coroutines.runBlocking 59 import org.junit.After 60 import org.junit.Before 61 import org.junit.Test 62 import org.junit.runner.RunWith 63 import org.mockito.Mock 64 import org.mockito.Mockito.verify 65 import org.mockito.MockitoAnnotations 66 67 /** 68 * The switcher acts as a dispatcher to either the `prod` or `demo` versions of the repository 69 * interface it's switching on. These tests just need to verify that the entire interface properly 70 * switches over when the value of `demoMode` changes 71 */ 72 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") 73 @OptIn(ExperimentalCoroutinesApi::class) 74 @SmallTest 75 @RunWith(AndroidJUnit4::class) 76 class MobileRepositorySwitcherTest : SysuiTestCase() { 77 private val kosmos = testKosmos() 78 79 private lateinit var underTest: MobileRepositorySwitcher 80 private lateinit var realRepo: MobileConnectionsRepositoryImpl 81 private lateinit var demoRepo: DemoMobileConnectionsRepository 82 private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource 83 private lateinit var wifiDataSource: DemoModeWifiDataSource 84 private lateinit var wifiRepository: FakeWifiRepository 85 private lateinit var connectivityRepository: ConnectivityRepository 86 87 @Mock private lateinit var subscriptionManager: SubscriptionManager 88 @Mock private lateinit var telephonyManager: TelephonyManager 89 @Mock private lateinit var logger: MobileInputLogger 90 @Mock private lateinit var summaryLogger: TableLogBuffer 91 @Mock private lateinit var demoModeController: DemoModeController 92 @Mock private lateinit var dumpManager: DumpManager 93 94 private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null) 95 private val mobileMappings = FakeMobileMappingsProxy() 96 private val subscriptionManagerProxy = FakeSubscriptionManagerProxy() 97 98 private val scope = CoroutineScope(IMMEDIATE) 99 100 @Before setUpnull101 fun setUp() { 102 MockitoAnnotations.initMocks(this) 103 104 // Never start in demo mode 105 whenever(demoModeController.isInDemoMode).thenReturn(false) 106 107 mobileDataSource = 108 mock<DemoModeMobileConnectionDataSource>().also { 109 whenever(it.mobileEvents).thenReturn(fakeNetworkEventsFlow) 110 } 111 wifiDataSource = 112 mock<DemoModeWifiDataSource>().also { 113 whenever(it.wifiEvents).thenReturn(MutableStateFlow(null)) 114 } 115 wifiRepository = FakeWifiRepository() 116 117 connectivityRepository = FakeConnectivityRepository() 118 119 realRepo = 120 MobileConnectionsRepositoryImpl( 121 connectivityRepository, 122 subscriptionManager, 123 subscriptionManagerProxy, 124 telephonyManager, 125 logger, 126 summaryLogger, 127 mobileMappings, 128 fakeBroadcastDispatcher, 129 context, 130 /* bgDispatcher = */ IMMEDIATE, 131 scope, 132 /* mainDispatcher = */ IMMEDIATE, 133 FakeAirplaneModeRepository(), 134 wifiRepository, 135 mock(), 136 mock(), 137 mock(), 138 ) 139 140 demoRepo = 141 DemoMobileConnectionsRepository( 142 mobileDataSource = mobileDataSource, 143 wifiDataSource = wifiDataSource, 144 scope = scope, 145 context = context, 146 logFactory = kosmos.tableLogBufferFactory, 147 ) 148 149 underTest = 150 MobileRepositorySwitcher( 151 scope = scope, 152 realRepository = realRepo, 153 demoMobileConnectionsRepository = demoRepo, 154 demoModeController = demoModeController, 155 ) 156 } 157 158 @After tearDownnull159 fun tearDown() { 160 scope.cancel() 161 } 162 163 @Test activeRepoMatchesDemoModeSettingnull164 fun activeRepoMatchesDemoModeSetting() = 165 runBlocking(IMMEDIATE) { 166 whenever(demoModeController.isInDemoMode).thenReturn(false) 167 168 var latest: MobileConnectionsRepository? = null 169 val job = underTest.activeRepo.onEach { latest = it }.launchIn(this) 170 171 assertThat(latest).isEqualTo(realRepo) 172 173 startDemoMode() 174 175 assertThat(latest).isEqualTo(demoRepo) 176 177 finishDemoMode() 178 179 assertThat(latest).isEqualTo(realRepo) 180 181 job.cancel() 182 } 183 184 @Test subscriptionListUpdatesWhenDemoModeChangesnull185 fun subscriptionListUpdatesWhenDemoModeChanges() = 186 runBlocking(IMMEDIATE) { 187 whenever(demoModeController.isInDemoMode).thenReturn(false) 188 189 whenever(subscriptionManager.completeActiveSubscriptionInfoList) 190 .thenReturn(listOf(SUB_1, SUB_2)) 191 192 var latest: List<SubscriptionModel>? = null 193 val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) 194 195 // The real subscriptions has 2 subs 196 whenever(subscriptionManager.completeActiveSubscriptionInfoList) 197 .thenReturn(listOf(SUB_1, SUB_2)) 198 getSubscriptionCallback().onSubscriptionsChanged() 199 200 assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2)) 201 202 // Demo mode turns on, and we should see only the demo subscriptions 203 startDemoMode() 204 fakeNetworkEventsFlow.value = validMobileEvent(subId = 3) 205 206 // Demo mobile connections repository makes arbitrarily-formed subscription info 207 // objects, so just validate the data we care about 208 assertThat(latest).hasSize(1) 209 assertThat(latest!![0].subscriptionId).isEqualTo(3) 210 211 finishDemoMode() 212 213 assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2)) 214 215 job.cancel() 216 } 217 startDemoModenull218 private fun startDemoMode() { 219 whenever(demoModeController.isInDemoMode).thenReturn(true) 220 getDemoModeCallback().onDemoModeStarted() 221 } 222 finishDemoModenull223 private fun finishDemoMode() { 224 whenever(demoModeController.isInDemoMode).thenReturn(false) 225 getDemoModeCallback().onDemoModeFinished() 226 } 227 getSubscriptionCallbacknull228 private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener { 229 val callbackCaptor = 230 kotlinArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>() 231 verify(subscriptionManager) 232 .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture()) 233 return callbackCaptor.value 234 } 235 getDemoModeCallbacknull236 private fun getDemoModeCallback(): DemoMode { 237 val captor = kotlinArgumentCaptor<DemoMode>() 238 verify(demoModeController).addCallback(captor.capture()) 239 return captor.value 240 } 241 242 companion object { 243 private val IMMEDIATE = Dispatchers.Main.immediate 244 245 private const val SUB_1_ID = 1 246 private const val SUB_1_NAME = "Carrier $SUB_1_ID" 247 private val SUB_1 = <lambda>null248 mock<SubscriptionInfo>().also { 249 whenever(it.subscriptionId).thenReturn(SUB_1_ID) 250 whenever(it.carrierName).thenReturn(SUB_1_NAME) 251 whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) 252 } 253 private val MODEL_1 = 254 SubscriptionModel( 255 subscriptionId = SUB_1_ID, 256 carrierName = SUB_1_NAME, 257 profileClass = PROFILE_CLASS_UNSET, 258 ) 259 260 private const val SUB_2_ID = 2 261 private const val SUB_2_NAME = "Carrier $SUB_2_ID" 262 private val SUB_2 = <lambda>null263 mock<SubscriptionInfo>().also { 264 whenever(it.subscriptionId).thenReturn(SUB_2_ID) 265 whenever(it.carrierName).thenReturn(SUB_2_NAME) 266 whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) 267 } 268 private val MODEL_2 = 269 SubscriptionModel( 270 subscriptionId = SUB_2_ID, 271 carrierName = SUB_2_NAME, 272 profileClass = PROFILE_CLASS_UNSET, 273 ) 274 } 275 } 276