1 /*
2  * Copyright (C) 2023 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.satellite.data.prod
18 
19 import android.os.OutcomeReceiver
20 import android.os.Process
21 import android.telephony.TelephonyCallback
22 import android.telephony.TelephonyManager
23 import android.telephony.satellite.NtnSignalStrength
24 import android.telephony.satellite.NtnSignalStrengthCallback
25 import android.telephony.satellite.SatelliteManager
26 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED
27 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING
28 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING
29 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_IDLE
30 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_LISTENING
31 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED
32 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF
33 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE
34 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN
35 import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ERROR
36 import android.telephony.satellite.SatelliteManager.SatelliteException
37 import android.telephony.satellite.SatelliteModemStateCallback
38 import android.telephony.satellite.SatelliteProvisionStateCallback
39 import android.telephony.satellite.SatelliteSupportedStateCallback
40 import androidx.test.filters.SmallTest
41 import com.android.systemui.SysuiTestCase
42 import com.android.systemui.coroutines.collectLastValue
43 import com.android.systemui.log.core.FakeLogBuffer
44 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers
45 import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.MIN_UPTIME
46 import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
47 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
48 import com.android.systemui.util.mockito.any
49 import com.android.systemui.util.mockito.whenever
50 import com.android.systemui.util.mockito.withArgCaptor
51 import com.android.systemui.util.time.FakeSystemClock
52 import com.google.common.truth.Truth.assertThat
53 import java.util.Optional
54 import kotlin.test.Test
55 import kotlinx.coroutines.ExperimentalCoroutinesApi
56 import kotlinx.coroutines.flow.launchIn
57 import kotlinx.coroutines.flow.onEach
58 import kotlinx.coroutines.test.StandardTestDispatcher
59 import kotlinx.coroutines.test.TestScope
60 import kotlinx.coroutines.test.advanceTimeBy
61 import kotlinx.coroutines.test.runCurrent
62 import kotlinx.coroutines.test.runTest
63 import org.junit.Before
64 import org.mockito.Mock
65 import org.mockito.Mockito
66 import org.mockito.Mockito.atLeastOnce
67 import org.mockito.Mockito.doAnswer
68 import org.mockito.Mockito.never
69 import org.mockito.Mockito.times
70 import org.mockito.Mockito.verify
71 import org.mockito.MockitoAnnotations
72 
73 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
74 @OptIn(ExperimentalCoroutinesApi::class)
75 @SmallTest
76 class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
77     private lateinit var underTest: DeviceBasedSatelliteRepositoryImpl
78 
79     @Mock private lateinit var satelliteManager: SatelliteManager
80     @Mock private lateinit var telephonyManager: TelephonyManager
81 
82     private val systemClock = FakeSystemClock()
83     private val dispatcher = StandardTestDispatcher()
84     private val testScope = TestScope(dispatcher)
85 
86     @Before
setUpnull87     fun setUp() {
88         MockitoAnnotations.initMocks(this)
89     }
90 
91     @Test
nullSatelliteManager_usesDefaultValuesnull92     fun nullSatelliteManager_usesDefaultValues() =
93         testScope.runTest {
94             setupDefaultRepo()
95             underTest =
96                 DeviceBasedSatelliteRepositoryImpl(
97                     Optional.empty(),
98                     telephonyManager,
99                     dispatcher,
100                     testScope.backgroundScope,
101                     logBuffer = FakeLogBuffer.Factory.create(),
102                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
103                     systemClock,
104                 )
105 
106             val connectionState by collectLastValue(underTest.connectionState)
107             val strength by collectLastValue(underTest.signalStrength)
108             val allowed by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
109 
110             assertThat(connectionState).isEqualTo(SatelliteConnectionState.Off)
111             assertThat(strength).isEqualTo(0)
112             assertThat(allowed).isFalse()
113         }
114 
115     @Test
connectionState_mapsFromSatelliteModemStatenull116     fun connectionState_mapsFromSatelliteModemState() =
117         testScope.runTest {
118             setupDefaultRepo()
119             val latest by collectLastValue(underTest.connectionState)
120             runCurrent()
121             val callback =
122                 withArgCaptor<SatelliteModemStateCallback> {
123                     verify(satelliteManager).registerForModemStateChanged(any(), capture())
124                 }
125 
126             // Mapping from modem state to SatelliteConnectionState is rote, just run all of the
127             // possibilities here
128 
129             // Off states
130             callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_OFF)
131             assertThat(latest).isEqualTo(SatelliteConnectionState.Off)
132             callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_UNAVAILABLE)
133             assertThat(latest).isEqualTo(SatelliteConnectionState.Off)
134 
135             // On states
136             callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_IDLE)
137             assertThat(latest).isEqualTo(SatelliteConnectionState.On)
138             callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_LISTENING)
139             assertThat(latest).isEqualTo(SatelliteConnectionState.On)
140             callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_NOT_CONNECTED)
141             assertThat(latest).isEqualTo(SatelliteConnectionState.On)
142 
143             // Connected states
144             callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_CONNECTED)
145             assertThat(latest).isEqualTo(SatelliteConnectionState.Connected)
146             callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING)
147             assertThat(latest).isEqualTo(SatelliteConnectionState.Connected)
148             callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_DATAGRAM_RETRYING)
149             assertThat(latest).isEqualTo(SatelliteConnectionState.Connected)
150 
151             // Unknown states
152             callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_UNKNOWN)
153             assertThat(latest).isEqualTo(SatelliteConnectionState.Unknown)
154             // Garbage value (for completeness' sake)
155             callback.onSatelliteModemStateChanged(123456)
156             assertThat(latest).isEqualTo(SatelliteConnectionState.Unknown)
157         }
158 
159     @Test
signalStrength_readsSatelliteManagerStatenull160     fun signalStrength_readsSatelliteManagerState() =
161         testScope.runTest {
162             setupDefaultRepo()
163             val latest by collectLastValue(underTest.signalStrength)
164             runCurrent()
165             val callback =
166                 withArgCaptor<NtnSignalStrengthCallback> {
167                     verify(satelliteManager).registerForNtnSignalStrengthChanged(any(), capture())
168                 }
169 
170             assertThat(latest).isEqualTo(0)
171 
172             callback.onNtnSignalStrengthChanged(NtnSignalStrength(1))
173             assertThat(latest).isEqualTo(1)
174 
175             callback.onNtnSignalStrengthChanged(NtnSignalStrength(2))
176             assertThat(latest).isEqualTo(2)
177 
178             callback.onNtnSignalStrengthChanged(NtnSignalStrength(3))
179             assertThat(latest).isEqualTo(3)
180 
181             callback.onNtnSignalStrengthChanged(NtnSignalStrength(4))
182             assertThat(latest).isEqualTo(4)
183         }
184 
185     @Test
isSatelliteAllowed_readsSatelliteManagerState_enablednull186     fun isSatelliteAllowed_readsSatelliteManagerState_enabled() =
187         testScope.runTest {
188             setupDefaultRepo()
189             // GIVEN satellite is allowed in this location
190             val allowed = true
191 
192             doAnswer {
193                     val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
194                     receiver.onResult(allowed)
195                     null
196                 }
197                 .`when`(satelliteManager)
198                 .requestIsCommunicationAllowedForCurrentLocation(
199                     any(),
200                     any<OutcomeReceiver<Boolean, SatelliteException>>()
201                 )
202 
203             val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
204 
205             assertThat(latest).isTrue()
206         }
207 
208     @Test
isSatelliteAllowed_readsSatelliteManagerState_disablednull209     fun isSatelliteAllowed_readsSatelliteManagerState_disabled() =
210         testScope.runTest {
211             setupDefaultRepo()
212             // GIVEN satellite is not allowed in this location
213             val allowed = false
214 
215             doAnswer {
216                     val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
217                     receiver.onResult(allowed)
218                     null
219                 }
220                 .`when`(satelliteManager)
221                 .requestIsCommunicationAllowedForCurrentLocation(
222                     any(),
223                     any<OutcomeReceiver<Boolean, SatelliteException>>()
224                 )
225 
226             val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
227 
228             assertThat(latest).isFalse()
229         }
230 
231     @Test
isSatelliteAllowed_pollsOnTimeoutnull232     fun isSatelliteAllowed_pollsOnTimeout() =
233         testScope.runTest {
234             setupDefaultRepo()
235             // GIVEN satellite is not allowed in this location
236             var allowed = false
237 
238             doAnswer {
239                     val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
240                     receiver.onResult(allowed)
241                     null
242                 }
243                 .`when`(satelliteManager)
244                 .requestIsCommunicationAllowedForCurrentLocation(
245                     any(),
246                     any<OutcomeReceiver<Boolean, SatelliteException>>()
247                 )
248 
249             val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
250 
251             assertThat(latest).isFalse()
252 
253             // WHEN satellite becomes enabled
254             allowed = true
255 
256             // WHEN the timeout has not yet been reached
257             advanceTimeBy(POLLING_INTERVAL_MS / 2)
258 
259             // THEN the value is still false
260             assertThat(latest).isFalse()
261 
262             // WHEN time advances beyond the polling interval
263             advanceTimeBy(POLLING_INTERVAL_MS / 2 + 1)
264 
265             // THEN then new value is emitted
266             assertThat(latest).isTrue()
267         }
268 
269     @Test
isSatelliteAllowed_pollingRestartsWhenCollectionRestartsnull270     fun isSatelliteAllowed_pollingRestartsWhenCollectionRestarts() =
271         testScope.runTest {
272             setupDefaultRepo()
273             // Use the old school launch/cancel so we can simulate subscribers arriving and leaving
274 
275             var latest: Boolean? = false
276             var job =
277                 underTest.isSatelliteAllowedForCurrentLocation.onEach { latest = it }.launchIn(this)
278 
279             // GIVEN satellite is not allowed in this location
280             var allowed = false
281 
282             doAnswer {
283                     val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
284                     receiver.onResult(allowed)
285                     null
286                 }
287                 .`when`(satelliteManager)
288                 .requestIsCommunicationAllowedForCurrentLocation(
289                     any(),
290                     any<OutcomeReceiver<Boolean, SatelliteException>>()
291                 )
292 
293             assertThat(latest).isFalse()
294 
295             // WHEN satellite becomes enabled
296             allowed = true
297 
298             // WHEN the job is restarted
299             advanceTimeBy(POLLING_INTERVAL_MS / 2)
300 
301             job.cancel()
302             job =
303                 underTest.isSatelliteAllowedForCurrentLocation.onEach { latest = it }.launchIn(this)
304 
305             // THEN the value is re-fetched
306             assertThat(latest).isTrue()
307 
308             job.cancel()
309         }
310 
311     @Test
isSatelliteAllowed_falseWhenErrorOccursnull312     fun isSatelliteAllowed_falseWhenErrorOccurs() =
313         testScope.runTest {
314             setupDefaultRepo()
315             doAnswer {
316                     val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
317                     receiver.onError(SatelliteException(1 /* unused */))
318                     null
319                 }
320                 .`when`(satelliteManager)
321                 .requestIsCommunicationAllowedForCurrentLocation(
322                     any(),
323                     any<OutcomeReceiver<Boolean, SatelliteException>>()
324                 )
325 
326             val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
327 
328             assertThat(latest).isFalse()
329         }
330 
331     @Test
satelliteProvisioned_notSupported_defaultFalsenull332     fun satelliteProvisioned_notSupported_defaultFalse() =
333         testScope.runTest {
334             // GIVEN satellite is not supported
335             setUpRepo(
336                 uptime = MIN_UPTIME,
337                 satMan = satelliteManager,
338                 satelliteSupported = false,
339             )
340 
341             assertThat(underTest.isSatelliteProvisioned.value).isFalse()
342         }
343 
344     @Test
satelliteProvisioned_supported_defaultFalsenull345     fun satelliteProvisioned_supported_defaultFalse() =
346         testScope.runTest {
347             // GIVEN satellite is supported
348             setUpRepo(
349                 uptime = MIN_UPTIME,
350                 satMan = satelliteManager,
351                 satelliteSupported = true,
352             )
353 
354             // THEN default provisioned state is false
355             assertThat(underTest.isSatelliteProvisioned.value).isFalse()
356         }
357 
358     @Test
satelliteProvisioned_returnsException_defaultsToFalsenull359     fun satelliteProvisioned_returnsException_defaultsToFalse() =
360         testScope.runTest {
361             // GIVEN satellite is supported on device
362             doAnswer {
363                 val callback: OutcomeReceiver<Boolean, SatelliteException> =
364                     it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
365                 callback.onResult(true)
366             }
367                 .whenever(satelliteManager)
368                 .requestIsSupported(any(), any())
369 
370             // GIVEN satellite returns an error when asked if provisioned
371             doAnswer {
372                 val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
373                 receiver.onError(SatelliteException(SATELLITE_RESULT_ERROR))
374                 null
375             }
376                 .whenever(satelliteManager)
377                 .requestIsProvisioned(
378                     any(),
379                     any<OutcomeReceiver<Boolean, SatelliteException>>()
380                 )
381 
382             // GIVEN we've been up long enough to start querying
383             systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME)
384 
385             underTest =
386                 DeviceBasedSatelliteRepositoryImpl(
387                     Optional.of(satelliteManager),
388                     telephonyManager,
389                     dispatcher,
390                     testScope.backgroundScope,
391                     logBuffer = FakeLogBuffer.Factory.create(),
392                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
393                     systemClock,
394                 )
395 
396             // WHEN we try to check for provisioned status
397             val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
398 
399             // THEN well, first we don't throw...
400             // AND THEN we assume that it's not provisioned
401             assertThat(provisioned).isFalse()
402         }
403 
404     @Test
satelliteProvisioned_throwsWhenQuerying_defaultsToFalsenull405     fun satelliteProvisioned_throwsWhenQuerying_defaultsToFalse() =
406         testScope.runTest {
407             // GIVEN satellite is supported on device
408             doAnswer {
409                 val callback: OutcomeReceiver<Boolean, SatelliteException> =
410                     it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
411                 callback.onResult(true)
412             }
413                 .whenever(satelliteManager)
414                 .requestIsSupported(any(), any())
415 
416             // GIVEN satellite throws when asked if provisioned
417             whenever(satelliteManager.requestIsProvisioned(any(), any()))
418                 .thenThrow(SecurityException())
419 
420             // GIVEN we've been up long enough to start querying
421             systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME)
422 
423             underTest =
424                 DeviceBasedSatelliteRepositoryImpl(
425                     Optional.of(satelliteManager),
426                     telephonyManager,
427                     dispatcher,
428                     testScope.backgroundScope,
429                     logBuffer = FakeLogBuffer.Factory.create(),
430                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
431                     systemClock,
432                 )
433 
434             // WHEN we try to check for provisioned status
435             val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
436 
437             // THEN well, first we don't throw...
438             // AND THEN we assume that it's not provisioned
439             assertThat(provisioned).isFalse()
440         }
441 
442     @Test
satelliteProvisioned_supported_provisioned_queriesInitialStateBeforeCallbacksnull443     fun satelliteProvisioned_supported_provisioned_queriesInitialStateBeforeCallbacks() =
444         testScope.runTest {
445             // GIVEN satellite is supported, and provisioned
446             setUpRepo(
447                 uptime = MIN_UPTIME,
448                 satMan = satelliteManager,
449                 satelliteSupported = true,
450                 initialSatelliteIsProvisioned = true,
451             )
452 
453             val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
454 
455             runCurrent()
456 
457             // THEN the current state is requested
458             verify(satelliteManager, atLeastOnce()).requestIsProvisioned(any(), any())
459 
460             // AND the state is correct
461             assertThat(provisioned).isTrue()
462         }
463 
464     @Test
satelliteProvisioned_supported_notProvisioned_queriesInitialStateBeforeCallbacksnull465     fun satelliteProvisioned_supported_notProvisioned_queriesInitialStateBeforeCallbacks() =
466         testScope.runTest {
467             // GIVEN satellite is supported, and provisioned
468             setUpRepo(
469                 uptime = MIN_UPTIME,
470                 satMan = satelliteManager,
471                 satelliteSupported = true,
472                 initialSatelliteIsProvisioned = false,
473             )
474 
475             val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
476 
477             runCurrent()
478 
479             // THEN the current state is requested
480             verify(satelliteManager, atLeastOnce()).requestIsProvisioned(any(), any())
481 
482             // AND the state is correct
483             assertThat(provisioned).isFalse()
484         }
485 
486     @Test
satelliteProvisioned_supported_notInitiallyProvisioned_tracksCallbacknull487     fun satelliteProvisioned_supported_notInitiallyProvisioned_tracksCallback() =
488         testScope.runTest {
489             // GIVEN satellite is not supported
490             setUpRepo(
491                 uptime = MIN_UPTIME,
492                 satMan = satelliteManager,
493                 satelliteSupported = true,
494                 initialSatelliteIsProvisioned = false,
495             )
496 
497             val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
498             runCurrent()
499 
500             val callback =
501                 withArgCaptor<SatelliteProvisionStateCallback> {
502                     verify(satelliteManager).registerForProvisionStateChanged(any(), capture())
503                 }
504 
505             // WHEN provisioning state changes
506             callback.onSatelliteProvisionStateChanged(true)
507 
508             // THEN the value is reflected in the repo
509             assertThat(provisioned).isTrue()
510         }
511 
512     @Test
satelliteProvisioned_supported_tracksCallback_reRegistersOnCrashnull513     fun satelliteProvisioned_supported_tracksCallback_reRegistersOnCrash() =
514         testScope.runTest {
515             // GIVEN satellite is supported
516             setUpRepo(
517                 uptime = MIN_UPTIME,
518                 satMan = satelliteManager,
519                 satelliteSupported = true,
520             )
521 
522             val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
523 
524             runCurrent()
525 
526             val callback =
527                 withArgCaptor<SatelliteProvisionStateCallback> {
528                     verify(satelliteManager).registerForProvisionStateChanged(any(), capture())
529                 }
530             val telephonyCallback =
531                 MobileTelephonyHelpers.getTelephonyCallbackForType<
532                     TelephonyCallback.RadioPowerStateListener
533                 >(
534                     telephonyManager
535                 )
536 
537             // GIVEN satellite is currently provisioned
538             callback.onSatelliteProvisionStateChanged(true)
539 
540             assertThat(provisioned).isTrue()
541 
542             // WHEN a crash event happens (detected by radio state change)
543             telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_ON)
544             runCurrent()
545             telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_OFF)
546             runCurrent()
547 
548             // THEN listeners are re-registered
549             verify(satelliteManager, times(2)).registerForProvisionStateChanged(any(), any())
550             // AND the state is queried again
551             verify(satelliteManager, times(2)).requestIsProvisioned(any(), any())
552         }
553 
554     @Test
satelliteNotSupported_listenersAreNotRegisterednull555     fun satelliteNotSupported_listenersAreNotRegistered() =
556         testScope.runTest {
557             // GIVEN satellite is not supported
558             setUpRepo(
559                 uptime = MIN_UPTIME,
560                 satMan = satelliteManager,
561                 satelliteSupported = false,
562             )
563 
564             // WHEN data is requested from the repo
565             val connectionState by collectLastValue(underTest.connectionState)
566             val signalStrength by collectLastValue(underTest.signalStrength)
567 
568             // THEN the manager is not asked for the information, and default values are returned
569             verify(satelliteManager, never()).registerForModemStateChanged(any(), any())
570             verify(satelliteManager, never()).registerForNtnSignalStrengthChanged(any(), any())
571         }
572 
573     @Test
satelliteSupported_registersCallbackForStateChangesnull574     fun satelliteSupported_registersCallbackForStateChanges() =
575         testScope.runTest {
576             // GIVEN a supported satellite manager.
577             setupDefaultRepo()
578             runCurrent()
579 
580             // THEN the repo registers for state changes of satellite support
581             verify(satelliteManager, times(1)).registerForSupportedStateChanged(any(), any())
582         }
583 
584     @Test
satelliteNotSupported_registersCallbackForStateChangesnull585     fun satelliteNotSupported_registersCallbackForStateChanges() =
586         testScope.runTest {
587             // GIVEN satellite is not supported
588             setUpRepo(
589                 uptime = MIN_UPTIME,
590                 satMan = satelliteManager,
591                 satelliteSupported = false,
592             )
593 
594             runCurrent()
595             // THEN the repo registers for state changes of satellite support
596             verify(satelliteManager, times(1)).registerForSupportedStateChanged(any(), any())
597         }
598 
599     @Test
satelliteSupportedStateChangedCallbackThrows_doesNotCrashnull600     fun satelliteSupportedStateChangedCallbackThrows_doesNotCrash() =
601         testScope.runTest {
602             // GIVEN, satellite manager throws when registering for supported state changes
603             whenever(satelliteManager.registerForSupportedStateChanged(any(), any()))
604                 .thenThrow(IllegalStateException())
605 
606             // GIVEN a supported satellite manager.
607             setupDefaultRepo()
608             runCurrent()
609 
610             // THEN a listener for satellite supported changed can attempt to register,
611             // with no crash
612             verify(satelliteManager).registerForSupportedStateChanged(any(), any())
613         }
614 
615     @Test
satelliteSupported_supportIsLost_unregistersListenersnull616     fun satelliteSupported_supportIsLost_unregistersListeners() =
617         testScope.runTest {
618             // GIVEN a supported satellite manager.
619             setupDefaultRepo()
620             runCurrent()
621 
622             val callback =
623                 withArgCaptor<SatelliteSupportedStateCallback> {
624                     verify(satelliteManager).registerForSupportedStateChanged(any(), capture())
625                 }
626 
627             // WHEN data is requested from the repo
628             val connectionState by collectLastValue(underTest.connectionState)
629             val signalStrength by collectLastValue(underTest.signalStrength)
630 
631             // THEN the listeners are registered
632             verify(satelliteManager, times(1)).registerForModemStateChanged(any(), any())
633             verify(satelliteManager, times(1)).registerForNtnSignalStrengthChanged(any(), any())
634 
635             // WHEN satellite support turns off
636             callback.onSatelliteSupportedStateChanged(false)
637             runCurrent()
638 
639             // THEN listeners are unregistered
640             verify(satelliteManager, times(1)).unregisterForModemStateChanged(any())
641             verify(satelliteManager, times(1)).unregisterForNtnSignalStrengthChanged(any())
642         }
643 
644     @Test
satelliteNotSupported_supportShowsUp_registersListenersnull645     fun satelliteNotSupported_supportShowsUp_registersListeners() =
646         testScope.runTest {
647             // GIVEN satellite is not supported
648             setUpRepo(
649                 uptime = MIN_UPTIME,
650                 satMan = satelliteManager,
651                 satelliteSupported = false,
652             )
653             runCurrent()
654 
655             val callback =
656                 withArgCaptor<SatelliteSupportedStateCallback> {
657                     verify(satelliteManager).registerForSupportedStateChanged(any(), capture())
658                 }
659 
660             // WHEN data is requested from the repo
661             val connectionState by collectLastValue(underTest.connectionState)
662             val signalStrength by collectLastValue(underTest.signalStrength)
663 
664             // THEN the listeners are not yet registered
665             verify(satelliteManager, times(0)).registerForModemStateChanged(any(), any())
666             verify(satelliteManager, times(0)).registerForNtnSignalStrengthChanged(any(), any())
667 
668             // WHEN satellite support turns on
669             callback.onSatelliteSupportedStateChanged(true)
670             runCurrent()
671 
672             // THEN listeners are registered
673             verify(satelliteManager, times(1)).registerForModemStateChanged(any(), any())
674             verify(satelliteManager, times(1)).registerForNtnSignalStrengthChanged(any(), any())
675         }
676 
677     @Test
repoDoesNotCheckForSupportUntilMinUptimenull678     fun repoDoesNotCheckForSupportUntilMinUptime() =
679         testScope.runTest {
680             // GIVEN we init 100ms after sysui starts up
681             setUpRepo(
682                 uptime = 100,
683                 satMan = satelliteManager,
684                 satelliteSupported = true,
685             )
686 
687             // WHEN data is requested
688             val connectionState by collectLastValue(underTest.connectionState)
689             val signalStrength by collectLastValue(underTest.signalStrength)
690 
691             // THEN we have not yet talked to satellite manager, since we are well before MIN_UPTIME
692             Mockito.verifyZeroInteractions(satelliteManager)
693 
694             // WHEN enough time has passed
695             systemClock.advanceTime(MIN_UPTIME)
696             runCurrent()
697 
698             // THEN we finally register with the satellite manager
699             verify(satelliteManager).registerForModemStateChanged(any(), any())
700         }
701 
702     @Test
telephonyCrash_repoReregistersConnectionStateListenernull703     fun telephonyCrash_repoReregistersConnectionStateListener() =
704         testScope.runTest {
705             setupDefaultRepo()
706 
707             // GIVEN connection state is requested
708             val connectionState by collectLastValue(underTest.connectionState)
709 
710             runCurrent()
711 
712             val telephonyCallback =
713                 MobileTelephonyHelpers.getTelephonyCallbackForType<
714                     TelephonyCallback.RadioPowerStateListener
715                 >(
716                     telephonyManager
717                 )
718 
719             // THEN listener is registered once
720             verify(satelliteManager, times(1)).registerForModemStateChanged(any(), any())
721 
722             // WHEN a crash event happens (detected by radio state change)
723             telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_ON)
724             runCurrent()
725             telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_OFF)
726             runCurrent()
727 
728             // THEN listeners are unregistered and re-registered
729             verify(satelliteManager, times(1)).unregisterForModemStateChanged(any())
730             verify(satelliteManager, times(2)).registerForModemStateChanged(any(), any())
731         }
732 
733     @Test
telephonyCrash_repoReregistersSignalStrengthListenernull734     fun telephonyCrash_repoReregistersSignalStrengthListener() =
735         testScope.runTest {
736             setupDefaultRepo()
737 
738             // GIVEN signal strength is requested
739             val signalStrength by collectLastValue(underTest.signalStrength)
740 
741             runCurrent()
742 
743             val telephonyCallback =
744                 MobileTelephonyHelpers.getTelephonyCallbackForType<
745                     TelephonyCallback.RadioPowerStateListener
746                 >(
747                     telephonyManager
748                 )
749 
750             // THEN listeners are registered the first time
751             verify(satelliteManager, times(1)).registerForNtnSignalStrengthChanged(any(), any())
752 
753             // WHEN a crash event happens (detected by radio state change)
754             telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_ON)
755             runCurrent()
756             telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_OFF)
757             runCurrent()
758 
759             // THEN listeners are unregistered and re-registered
760             verify(satelliteManager, times(1)).unregisterForNtnSignalStrengthChanged(any())
761             verify(satelliteManager, times(2)).registerForNtnSignalStrengthChanged(any(), any())
762         }
763 
setUpReponull764     private fun setUpRepo(
765         uptime: Long = MIN_UPTIME,
766         satMan: SatelliteManager? = satelliteManager,
767         satelliteSupported: Boolean = true,
768         initialSatelliteIsProvisioned: Boolean = true,
769     ) {
770         doAnswer {
771                 val callback: OutcomeReceiver<Boolean, SatelliteException> =
772                     it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
773                 callback.onResult(satelliteSupported)
774             }
775             .whenever(satelliteManager)
776             .requestIsSupported(any(), any())
777 
778         doAnswer {
779             val callback: OutcomeReceiver<Boolean, SatelliteException> =
780                 it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
781             callback.onResult(initialSatelliteIsProvisioned)
782         }
783             .whenever(satelliteManager)
784             .requestIsProvisioned(any(), any())
785 
786         systemClock.setUptimeMillis(Process.getStartUptimeMillis() + uptime)
787 
788         underTest =
789             DeviceBasedSatelliteRepositoryImpl(
790                 if (satMan != null) Optional.of(satMan) else Optional.empty(),
791                 telephonyManager,
792                 dispatcher,
793                 testScope.backgroundScope,
794                 logBuffer = FakeLogBuffer.Factory.create(),
795                 verboseLogBuffer = FakeLogBuffer.Factory.create(),
796                 systemClock,
797             )
798     }
799 
800     // Set system time to MIN_UPTIME and create a repo with satellite supported
setupDefaultReponull801     private fun setupDefaultRepo() {
802         setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
803     }
804 }
805