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