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