1 /* <lambda>null2 * Copyright (C) 2024 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.settings.network 18 19 import android.content.Context 20 import android.os.OutcomeReceiver 21 import android.telephony.satellite.SatelliteManager 22 import android.telephony.satellite.SatelliteModemStateCallback 23 import android.util.Log 24 import androidx.annotation.VisibleForTesting 25 import androidx.concurrent.futures.CallbackToFutureAdapter 26 import com.google.common.util.concurrent.Futures.immediateFuture 27 import com.google.common.util.concurrent.ListenableFuture 28 import kotlinx.coroutines.CoroutineDispatcher 29 import kotlinx.coroutines.Dispatchers 30 import kotlinx.coroutines.asExecutor 31 import kotlinx.coroutines.channels.awaitClose 32 import kotlinx.coroutines.flow.Flow 33 import kotlinx.coroutines.flow.callbackFlow 34 import kotlinx.coroutines.flow.flowOf 35 import java.util.concurrent.Executor 36 import kotlinx.coroutines.flow.flowOn 37 38 /** 39 * A repository class for interacting with the SatelliteManager API. 40 */ 41 class SatelliteRepository( 42 private val context: Context, 43 ) { 44 45 /** 46 * Checks if the satellite modem is enabled. 47 * 48 * @param executor The executor to run the asynchronous operation on 49 * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled, 50 * `false` otherwise. 51 */ 52 fun requestIsEnabled(executor: Executor): ListenableFuture<Boolean> { 53 val satelliteManager: SatelliteManager? = 54 context.getSystemService(SatelliteManager::class.java) 55 if (satelliteManager == null) { 56 Log.w(TAG, "SatelliteManager is null") 57 return immediateFuture(false) 58 } 59 60 return CallbackToFutureAdapter.getFuture { completer -> 61 satelliteManager.requestIsEnabled(executor, 62 object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> { 63 override fun onResult(result: Boolean) { 64 Log.i(TAG, "Satellite modem enabled status: $result") 65 completer.set(result) 66 } 67 68 override fun onError(error: SatelliteManager.SatelliteException) { 69 super.onError(error) 70 Log.w(TAG, "Can't get satellite modem enabled status", error) 71 completer.set(false) 72 } 73 }) 74 "requestIsEnabled" 75 } 76 } 77 78 /** 79 * Checks if a satellite session has started. 80 * 81 * @param executor The executor to run the asynchronous operation on 82 * @return A ListenableFuture that will resolve to `true` if a satellite session has started, 83 * `false` otherwise. 84 */ 85 fun requestIsSessionStarted(executor: Executor): ListenableFuture<Boolean> { 86 isSessionStarted?.let { return immediateFuture(it) } 87 88 val satelliteManager: SatelliteManager? = 89 context.getSystemService(SatelliteManager::class.java) 90 if (satelliteManager == null) { 91 Log.w(TAG, "SatelliteManager is null") 92 return immediateFuture(false) 93 } 94 95 return CallbackToFutureAdapter.getFuture { completer -> 96 val callback = object : SatelliteModemStateCallback { 97 override fun onSatelliteModemStateChanged(state: Int) { 98 val isSessionStarted = isSatelliteSessionStarted(state) 99 Log.i(TAG, "Satellite modem state changed: state=$state" 100 + ", isSessionStarted=$isSessionStarted") 101 completer.set(isSessionStarted) 102 satelliteManager.unregisterForModemStateChanged(this) 103 } 104 } 105 106 val registerResult = satelliteManager.registerForModemStateChanged(executor, callback) 107 if (registerResult != SatelliteManager.SATELLITE_RESULT_SUCCESS) { 108 Log.w(TAG, "Failed to register for satellite modem state change: $registerResult") 109 completer.set(false) 110 } 111 "requestIsSessionStarted" 112 } 113 } 114 115 /** 116 * Provides a Flow that emits the session state of the satellite modem. Updates are triggered 117 * when the modem state changes. 118 * 119 * @param defaultDispatcher The CoroutineDispatcher to use (Defaults to `Dispatchers.Default`). 120 * @return A Flow emitting `true` when the session is started and `false` otherwise. 121 */ 122 fun getIsSessionStartedFlow( 123 defaultDispatcher: CoroutineDispatcher = Dispatchers.Default, 124 ): Flow<Boolean> { 125 val satelliteManager: SatelliteManager? = 126 context.getSystemService(SatelliteManager::class.java) 127 if (satelliteManager == null) { 128 Log.w(TAG, "SatelliteManager is null") 129 return flowOf(false) 130 } 131 132 return callbackFlow { 133 val callback = SatelliteModemStateCallback { state -> 134 val isSessionStarted = isSatelliteSessionStarted(state) 135 Log.i(TAG, "Satellite modem state changed: state=$state" 136 + ", isSessionStarted=$isSessionStarted") 137 trySend(isSessionStarted) 138 } 139 140 val registerResult = satelliteManager.registerForModemStateChanged( 141 defaultDispatcher.asExecutor(), 142 callback 143 ) 144 145 if (registerResult != SatelliteManager.SATELLITE_RESULT_SUCCESS) { 146 // If the registration failed (e.g., device doesn't support satellite), 147 // SatelliteManager will not emit the current state by callback. 148 // We send `false` value by ourself to make sure the flow has initial value. 149 Log.w(TAG, "Failed to register for satellite modem state change: $registerResult") 150 trySend(false) 151 } 152 153 awaitClose { satelliteManager.unregisterForModemStateChanged(callback) } 154 }.flowOn(Dispatchers.Default) 155 } 156 157 /** 158 * Check if the modem is in a satellite session. 159 * 160 * @param state The SatelliteModemState provided by the SatelliteManager. 161 * @return `true` if the modem is in a satellite session, `false` otherwise. 162 */ 163 fun isSatelliteSessionStarted(@SatelliteManager.SatelliteModemState state: Int): Boolean { 164 return when (state) { 165 SatelliteManager.SATELLITE_MODEM_STATE_OFF, 166 SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE, 167 SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN -> false 168 else -> true 169 } 170 } 171 172 companion object { 173 private const val TAG: String = "SatelliteRepository" 174 175 private var isSessionStarted: Boolean? = null 176 177 @VisibleForTesting 178 fun setIsSessionStartedForTesting(isEnabled: Boolean) { 179 this.isSessionStarted = isEnabled 180 } 181 } 182 } 183