• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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.content.res.Resources
20 import android.os.OutcomeReceiver
21 import android.telephony.TelephonyCallback
22 import android.telephony.TelephonyManager
23 import android.telephony.satellite.NtnSignalStrengthCallback
24 import android.telephony.satellite.SatelliteCommunicationAccessStateCallback
25 import android.telephony.satellite.SatelliteManager
26 import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS
27 import android.telephony.satellite.SatelliteModemStateCallback
28 import android.telephony.satellite.SatelliteProvisionStateCallback
29 import androidx.annotation.VisibleForTesting
30 import com.android.app.tracing.coroutines.launchTraced as launch
31 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
32 import com.android.systemui.dagger.SysUISingleton
33 import com.android.systemui.dagger.qualifiers.Background
34 import com.android.systemui.dagger.qualifiers.Main
35 import com.android.systemui.log.LogBuffer
36 import com.android.systemui.log.core.LogLevel
37 import com.android.systemui.log.core.MessageInitializer
38 import com.android.systemui.log.core.MessagePrinter
39 import com.android.systemui.res.R
40 import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
41 import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog
42 import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
43 import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported
44 import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported
45 import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Supported
46 import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Unknown
47 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
48 import com.android.systemui.util.kotlin.getOrNull
49 import com.android.systemui.util.kotlin.pairwise
50 import com.android.systemui.util.time.SystemClock
51 import java.util.Optional
52 import java.util.function.Consumer
53 import javax.inject.Inject
54 import kotlin.coroutines.resume
55 import kotlinx.coroutines.CoroutineDispatcher
56 import kotlinx.coroutines.CoroutineScope
57 import kotlinx.coroutines.asExecutor
58 import kotlinx.coroutines.channels.awaitClose
59 import kotlinx.coroutines.delay
60 import kotlinx.coroutines.flow.Flow
61 import kotlinx.coroutines.flow.MutableStateFlow
62 import kotlinx.coroutines.flow.SharingStarted
63 import kotlinx.coroutines.flow.StateFlow
64 import kotlinx.coroutines.flow.collectLatest
65 import kotlinx.coroutines.flow.flatMapLatest
66 import kotlinx.coroutines.flow.flowOf
67 import kotlinx.coroutines.flow.flowOn
68 import kotlinx.coroutines.flow.mapNotNull
69 import kotlinx.coroutines.flow.onStart
70 import kotlinx.coroutines.flow.stateIn
71 import kotlinx.coroutines.suspendCancellableCoroutine
72 import kotlinx.coroutines.withContext
73 
74 /**
75  * A SatelliteManager that has responded that it has satellite support. Use [SatelliteSupport] to
76  * get one
77  */
78 private typealias SupportedSatelliteManager = SatelliteManager
79 
80 /**
81  * "Supported" here means supported by the device. The value of this should be stable during the
82  * process lifetime.
83  *
84  * @VisibleForTesting
85  */
86 sealed interface SatelliteSupport {
87     /** Not yet fetched */
88     data object Unknown : SatelliteSupport
89 
90     /**
91      * SatelliteManager says that this mode is supported. Note that satellite manager can never be
92      * null now
93      */
94     data class Supported(val satelliteManager: SupportedSatelliteManager) : SatelliteSupport
95 
96     /**
97      * Either we were told that there is no support for this feature, or the manager is null, or
98      * some other exception occurred while querying for support.
99      */
100     data object NotSupported : SatelliteSupport
101 
102     companion object {
103         /**
104          * Convenience function to switch to the supported flow. [retrySignal] is a flow that emits
105          * [Unit] whenever the [supported] flow needs to be restarted
106          */
107         fun <T> Flow<SatelliteSupport>.whenSupported(
108             supported: (SatelliteManager) -> Flow<T>,
109             orElse: Flow<T>,
110             retrySignal: Flow<Unit>,
111         ): Flow<T> = flatMapLatest { satelliteSupport ->
112             when (satelliteSupport) {
113                 is Supported -> {
114                     retrySignal.flatMapLatest { supported(satelliteSupport.satelliteManager) }
115                 }
116                 else -> orElse
117             }
118         }
119     }
120 }
121 
122 /**
123  * Basically your everyday run-of-the-mill system service listener, with two notable exceptions.
124  *
125  * First, there are cases when simply requesting information from SatelliteManager can fail. See
126  * [SatelliteSupport] for details on how we track the state. What's worth noting here is that
127  * SUPPORTED is a stronger guarantee than [satelliteManager] being null. Therefore, the fundamental
128  * data flows here ([connectionState], [signalStrength],...) are wrapped in the convenience method
129  * [SatelliteSupport.whenSupported]. By defining flows as simple functions based on a
130  * [SupportedSatelliteManager], we can guarantee that the manager is non-null AND that it has told
131  * us that satellite is supported. Therefore, we don't expect exceptions to be thrown.
132  *
133  * Second, this class is designed to wait a full minute of process uptime before making any requests
134  * to the satellite manager. The hope is that by waiting we don't have to retry due to a modem that
135  * is still booting up or anything like that. We can tune or remove this behavior in the future if
136  * necessary.
137  */
138 @SysUISingleton
139 class DeviceBasedSatelliteRepositoryImpl
140 @Inject
141 constructor(
142     satelliteManagerOpt: Optional<SatelliteManager>,
143     telephonyManager: TelephonyManager,
144     @Background private val bgDispatcher: CoroutineDispatcher,
145     @Background private val scope: CoroutineScope,
146     @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
147     @VerboseDeviceBasedSatelliteInputLog private val verboseLogBuffer: LogBuffer,
148     private val systemClock: SystemClock,
149     @Main resources: Resources,
150 ) : RealDeviceBasedSatelliteRepository {
151 
152     private val satelliteManager: SatelliteManager?
153 
154     override val isOpportunisticSatelliteIconEnabled: Boolean =
155         resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
156 
157     // Some calls into satellite manager will throw exceptions if it is not supported.
158     // This is never expected to change after boot, but may need to be retried in some cases
159     @get:VisibleForTesting
160     val satelliteSupport: MutableStateFlow<SatelliteSupport> = MutableStateFlow(Unknown)
161 
162     /**
163      * Note that we are given an "unbound" [TelephonyManager] (meaning it was not created with a
164      * specific `subscriptionId`). Therefore this is the radio power state of the
165      * DEFAULT_SUBSCRIPTION_ID subscription. This subscription, I am led to believe, is the one that
166      * would be used for the SatelliteManager subscription.
167      *
168      * By watching power state changes, we can detect if the telephony process crashes.
169      *
170      * See b/337258696 for details
171      */
172     private val radioPowerState: StateFlow<Int> =
<lambda>null173         conflatedCallbackFlow {
174                 val cb =
175                     object : TelephonyCallback(), TelephonyCallback.RadioPowerStateListener {
176                         override fun onRadioPowerStateChanged(powerState: Int) {
177                             trySend(powerState)
178                         }
179                     }
180 
181                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), cb)
182 
183                 awaitClose { telephonyManager.unregisterTelephonyCallback(cb) }
184             }
185             .flowOn(bgDispatcher)
186             .stateIn(
187                 scope,
188                 SharingStarted.WhileSubscribed(),
189                 TelephonyManager.RADIO_POWER_UNAVAILABLE,
190             )
191 
192     /**
193      * In the event that a telephony phone process has crashed, we expect to see a radio power state
194      * change from ON to something else. This trigger can be used to re-start a flow via
195      * [whenSupported]
196      *
197      * This flow emits [Unit] when started so that newly-started collectors always run, and only
198      * restart when the state goes from ON -> !ON
199      */
200     private val telephonyProcessCrashedEvent: Flow<Unit> =
201         radioPowerState
202             .pairwise()
newnull203             .mapNotNull { (prev: Int, new: Int) ->
204                 if (
205                     prev == TelephonyManager.RADIO_POWER_ON &&
206                         new != TelephonyManager.RADIO_POWER_ON
207                 ) {
208                     Unit
209                 } else {
210                     null
211                 }
212             }
<lambda>null213             .onStart { emit(Unit) }
214 
215     init {
216         satelliteManager = satelliteManagerOpt.getOrNull()
217 
218         if (satelliteManager != null) {
219             // Outer scope launch allows us to delay until MIN_UPTIME
<lambda>null220             scope.launch(context = bgDispatcher) {
221                 // First, check that satellite is supported on this device
222                 satelliteSupport.value = checkSatelliteSupportAfterMinUptime(satelliteManager)
223                 logBuffer.i(
224                     { str1 = satelliteSupport.value.toString() },
225                     { "Checked for system support. support=$str1" },
226                 )
227 
228                 // Second, register a listener to let us know if there are changes to support
229                 scope.launch(context = bgDispatcher) {
230                     listenForChangesToSatelliteSupport(satelliteManager)
231                 }
232             }
233         } else {
<lambda>null234             logBuffer.i { "Satellite manager is null" }
235             satelliteSupport.value = NotSupported
236         }
237     }
238 
checkSatelliteSupportAfterMinUptimenull239     private suspend fun checkSatelliteSupportAfterMinUptime(
240         sm: SatelliteManager
241     ): SatelliteSupport {
242         val waitTime = ensureMinUptime(systemClock, MIN_UPTIME)
243         if (waitTime > 0) {
244             logBuffer.i({ long1 = waitTime }) {
245                 "Waiting $long1 ms before checking for satellite support"
246             }
247             delay(waitTime)
248         }
249 
250         return sm.checkSatelliteSupported()
251     }
252 
253     override val isSatelliteAllowedForCurrentLocation =
254         satelliteSupport
255             .whenSupported(
256                 supported = ::isSatelliteAvailableFlow,
257                 orElse = flowOf(false),
258                 retrySignal = telephonyProcessCrashedEvent,
259             )
260             .stateIn(scope, SharingStarted.Lazily, false)
261 
isSatelliteAvailableFlownull262     private fun isSatelliteAvailableFlow(sm: SupportedSatelliteManager): Flow<Boolean> =
263         conflatedCallbackFlow {
264                 val callback = SatelliteCommunicationAccessStateCallback { allowed ->
265                     logBuffer.i({ bool1 = allowed }) {
266                         "onSatelliteCommunicationAccessAllowedStateChanged: $bool1"
267                     }
268 
269                     trySend(allowed)
270                 }
271 
272                 var registered = false
273                 try {
274                     logBuffer.i { "registerForCommunicationAccessStateChanged" }
275                     sm.registerForCommunicationAccessStateChanged(
276                         bgDispatcher.asExecutor(),
277                         callback,
278                     )
279                     registered = true
280                 } catch (e: Exception) {
281                     logBuffer.e("Error calling registerForCommunicationAccessStateChanged", e)
282                 }
283 
284                 awaitClose {
285                     if (registered) {
286                         logBuffer.i { "unRegisterForCommunicationAccessStateChanged" }
287                         sm.unregisterForCommunicationAccessStateChanged(callback)
288                     }
289                 }
290             }
291             .flowOn(bgDispatcher)
292 
293     /**
294      * Register a callback with [SatelliteManager] to let us know if there is a change in satellite
295      * support. This job restarts if there is a crash event detected.
296      *
297      * Note that the structure of this method looks similar to [whenSupported], but since we want
298      * this callback registered even when it is [NotSupported], we just mimic the structure here.
299      */
listenForChangesToSatelliteSupportnull300     private suspend fun listenForChangesToSatelliteSupport(sm: SatelliteManager) {
301         telephonyProcessCrashedEvent.collectLatest {
302             satelliteIsSupportedCallback.collect { supported ->
303                 if (supported) {
304                     satelliteSupport.value = Supported(sm)
305                 } else {
306                     satelliteSupport.value = NotSupported
307                 }
308             }
309         }
310     }
311 
312     /**
313      * Callback version of [checkSatelliteSupported]. This flow should be retried on the same
314      * [telephonyProcessCrashedEvent] signal, but does not require a [SupportedSatelliteManager],
315      * since it specifically watches for satellite support.
316      */
317     private val satelliteIsSupportedCallback: Flow<Boolean> =
318         if (satelliteManager == null) {
319             flowOf(false)
320         } else {
<lambda>null321             conflatedCallbackFlow {
322                 val callback =
323                     Consumer<Boolean> { supported ->
324                         logBuffer.i {
325                             "onSatelliteSupportedStateChanged: " +
326                                 "${if (supported) "supported" else "not supported"}"
327                         }
328                         trySend(supported)
329                     }
330 
331                 var registered = false
332                 try {
333                     logBuffer.i { "registerForSupportedStateChanged" }
334                     satelliteManager.registerForSupportedStateChanged(
335                         bgDispatcher.asExecutor(),
336                         callback,
337                     )
338                     registered = true
339                 } catch (e: Exception) {
340                     logBuffer.e("error registering for supported state change", e)
341                 }
342 
343                 awaitClose {
344                     if (registered) {
345                         logBuffer.i { "unregisterForSupportedStateChanged" }
346                         satelliteManager.unregisterForSupportedStateChanged(callback)
347                     }
348                 }
349             }
350         }
351 
352     override val isSatelliteProvisioned: StateFlow<Boolean> =
353         satelliteSupport
354             .whenSupported(
355                 supported = ::satelliteProvisioned,
356                 orElse = flowOf(false),
357                 retrySignal = telephonyProcessCrashedEvent,
358             )
359             .stateIn(scope, SharingStarted.Eagerly, false)
360 
satelliteProvisionednull361     private fun satelliteProvisioned(sm: SupportedSatelliteManager): Flow<Boolean> =
362         conflatedCallbackFlow {
363                 // TODO(b/347992038): SatelliteManager should be sending the current provisioned
364                 // status when we register a callback. Until then, we have to manually query here.
365 
366                 // First, check to see what the current status is, and send the result to the output
367                 trySend(queryIsSatelliteProvisioned(sm))
368 
369                 val callback = SatelliteProvisionStateCallback { provisioned ->
370                     logBuffer.i {
371                         "onSatelliteProvisionStateChanged: " +
372                             if (provisioned) "provisioned" else "not provisioned"
373                     }
374                     trySend(provisioned)
375                 }
376 
377                 var registered = false
378                 try {
379                     logBuffer.i { "registerForProvisionStateChanged" }
380                     sm.registerForProvisionStateChanged(bgDispatcher.asExecutor(), callback)
381                     registered = true
382                 } catch (e: Exception) {
383                     logBuffer.e("error registering for provisioning state callback", e)
384                 }
385 
386                 awaitClose {
387                     if (registered) {
388                         logBuffer.i { "unregisterForProvisionStateChanged" }
389                         sm.unregisterForProvisionStateChanged(callback)
390                     }
391                 }
392             }
393             .flowOn(bgDispatcher)
394 
395     /** Check the current satellite provisioning status. */
queryIsSatelliteProvisionednull396     private suspend fun queryIsSatelliteProvisioned(sm: SupportedSatelliteManager): Boolean =
397         withContext(bgDispatcher) {
398             suspendCancellableCoroutine { continuation ->
399                 val receiver =
400                     object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
401                         override fun onResult(result: Boolean) {
402                             logBuffer.i { "requestIsProvisioned.onResult: $result" }
403                             continuation.resume(result)
404                         }
405 
406                         override fun onError(exception: SatelliteManager.SatelliteException) {
407                             logBuffer.e("requestIsProvisioned.onError:", exception)
408                             continuation.resume(false)
409                         }
410                     }
411 
412                 logBuffer.i { "Query for current satellite provisioned state." }
413                 try {
414                     sm.requestIsProvisioned(bgDispatcher.asExecutor(), receiver)
415                 } catch (e: Exception) {
416                     logBuffer.e("Exception while calling SatelliteManager.requestIsProvisioned:", e)
417                     continuation.resume(false)
418                 }
419             }
420         }
421 
422     override val connectionState =
423         satelliteSupport
424             .whenSupported(
425                 supported = ::connectionStateFlow,
426                 orElse = flowOf(SatelliteConnectionState.Off),
427                 retrySignal = telephonyProcessCrashedEvent,
428             )
429             .stateIn(scope, SharingStarted.Eagerly, SatelliteConnectionState.Off)
430 
431     // By using the SupportedSatelliteManager here, we expect registration never to fail
connectionStateFlownull432     private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> =
433         conflatedCallbackFlow {
434                 val cb = SatelliteModemStateCallback { state ->
435                     logBuffer.i({ int1 = state }) { "onSatelliteModemStateChanged: state=$int1" }
436                     trySend(SatelliteConnectionState.fromModemState(state))
437                 }
438 
439                 var registered = false
440 
441                 try {
442                     val res = sm.registerForModemStateChanged(bgDispatcher.asExecutor(), cb)
443                     registered = res == SATELLITE_RESULT_SUCCESS
444                 } catch (e: Exception) {
445                     logBuffer.e("error registering for modem state", e)
446                 }
447 
448                 awaitClose { if (registered) sm.unregisterForModemStateChanged(cb) }
449             }
450             .flowOn(bgDispatcher)
451 
452     override val signalStrength =
453         satelliteSupport
454             .whenSupported(
455                 supported = ::signalStrengthFlow,
456                 orElse = flowOf(0),
457                 retrySignal = telephonyProcessCrashedEvent,
458             )
459             .stateIn(scope, SharingStarted.Eagerly, 0)
460 
461     // By using the SupportedSatelliteManager here, we expect registration never to fail
signalStrengthFlownull462     private fun signalStrengthFlow(sm: SupportedSatelliteManager) =
463         conflatedCallbackFlow {
464                 val cb = NtnSignalStrengthCallback { signalStrength ->
465                     verboseLogBuffer.i({ int1 = signalStrength.level }) {
466                         "onNtnSignalStrengthChanged: level=$int1"
467                     }
468                     trySend(signalStrength.level)
469                 }
470 
471                 var registered = false
472                 try {
473                     sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
474                     registered = true
475                     logBuffer.i { "Registered for signal strength successfully" }
476                 } catch (e: Exception) {
477                     logBuffer.e("error registering for signal strength", e)
478                 }
479 
480                 awaitClose {
481                     if (registered) {
482                         sm.unregisterForNtnSignalStrengthChanged(cb)
483                         logBuffer.i { "Unregistered for signal strength successfully" }
484                     }
485                 }
486             }
487             .flowOn(bgDispatcher)
488 
checkSatelliteSupportednull489     private suspend fun SatelliteManager.checkSatelliteSupported(): SatelliteSupport =
490         suspendCancellableCoroutine { continuation ->
491             val cb =
492                 object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
493                     override fun onResult(supported: Boolean) {
494                         continuation.resume(
495                             if (supported) {
496                                 Supported(satelliteManager = this@checkSatelliteSupported)
497                             } else {
498                                 NotSupported
499                             }
500                         )
501                     }
502 
503                     override fun onError(error: SatelliteManager.SatelliteException) {
504                         logBuffer.e(
505                             "Exception when checking for satellite support. " +
506                                 "Assuming it is not supported for this device.",
507                             error,
508                         )
509 
510                         // Assume that an error means it's not supported
511                         continuation.resume(NotSupported)
512                     }
513                 }
514 
515             try {
516                 requestIsSupported(bgDispatcher.asExecutor(), cb)
517             } catch (error: Exception) {
518                 logBuffer.e(
519                     "Exception when checking for satellite support. " +
520                         "Assuming it is not supported for this device.",
521                     error,
522                 )
523                 continuation.resume(NotSupported)
524             }
525         }
526 
527     companion object {
528         // Let the system boot up and stabilize before we check for system support
529         const val MIN_UPTIME: Long = 1000 * 60
530 
531         private const val TAG = "DeviceBasedSatelliteRepo"
532 
533         /** Calculates how long we have to wait to reach MIN_UPTIME */
ensureMinUptimenull534         private fun ensureMinUptime(clock: SystemClock, uptime: Long): Long =
535             uptime - (clock.uptimeMillis() - android.os.Process.getStartUptimeMillis())
536 
537         /** A couple of convenience logging methods rather than a whole class */
538         private fun LogBuffer.i(initializer: MessageInitializer = {}, printer: MessagePrinter) =
539             this.log(TAG, LogLevel.INFO, initializer, printer)
540 
LogBuffernull541         private fun LogBuffer.e(message: String, exception: Throwable? = null) =
542             this.log(tag = TAG, level = LogLevel.ERROR, message = message, exception = exception)
543     }
544 }
545