Disable SIM On/Off operation when device is in a Satellite session

Bug: 330585109
Test: SatelliteManagerTestOnMockService SatelliteSessionControllerTest SatelliteControllerTest
Manual test with demo and real mode

Change-Id: Iade6426981f76a0b9b71828e0c86d3088c3e974e
This commit is contained in:
Thomas Nguyen
2024-04-23 11:26:58 -07:00
parent 58b70959a6
commit c6005fb22a
3 changed files with 113 additions and 14 deletions

View File

@@ -25,7 +25,6 @@ import androidx.annotation.VisibleForTesting
import androidx.concurrent.futures.CallbackToFutureAdapter import androidx.concurrent.futures.CallbackToFutureAdapter
import com.google.common.util.concurrent.Futures.immediateFuture import com.google.common.util.concurrent.Futures.immediateFuture
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import java.util.concurrent.Executor
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor import kotlinx.coroutines.asExecutor
@@ -33,6 +32,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import java.util.concurrent.Executor
/** /**
* A repository class for interacting with the SatelliteManager API. * A repository class for interacting with the SatelliteManager API.
@@ -74,6 +74,41 @@ class SatelliteRepository(
} }
} }
/**
* Checks if a satellite session has started.
*
* @param executor The executor to run the asynchronous operation on
* @return A ListenableFuture that will resolve to `true` if a satellite session has started,
* `false` otherwise.
*/
fun requestIsSessionStarted(executor: Executor): ListenableFuture<Boolean> {
val satelliteManager: SatelliteManager? =
context.getSystemService(SatelliteManager::class.java)
if (satelliteManager == null) {
Log.w(TAG, "SatelliteManager is null")
return immediateFuture(false)
}
return CallbackToFutureAdapter.getFuture { completer ->
val callback = object : SatelliteModemStateCallback {
override fun onSatelliteModemStateChanged(state: Int) {
val isSessionStarted = isSatelliteSessionStarted(state)
Log.i(TAG, "Satellite modem state changed: state=$state"
+ ", isSessionStarted=$isSessionStarted")
completer.set(isSessionStarted)
satelliteManager.unregisterForModemStateChanged(this)
}
}
val registerResult = satelliteManager.registerForModemStateChanged(executor, callback)
if (registerResult != SatelliteManager.SATELLITE_RESULT_SUCCESS) {
Log.w(TAG, "Failed to register for satellite modem state change: $registerResult")
completer.set(false)
}
"requestIsSessionStarted"
}
}
/** /**
* Provides a Flow that emits the enabled state of the satellite modem. Updates are triggered * Provides a Flow that emits the enabled state of the satellite modem. Updates are triggered
* when the modem state changes. * when the modem state changes.
@@ -134,5 +169,20 @@ class SatelliteRepository(
companion object { companion object {
private const val TAG: String = "SatelliteRepository" private const val TAG: String = "SatelliteRepository"
} }
/**
* Check if the modem is in a satellite session.
*
* @param state The SatelliteModemState provided by the SatelliteManager.
* @return `true` if the modem is in a satellite session, `false` otherwise.
*/
fun isSatelliteSessionStarted(@SatelliteManager.SatelliteModemState state: Int): Boolean {
return when (state) {
SatelliteManager.SATELLITE_MODEM_STATE_OFF,
SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE,
SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN -> false
else -> true
}
}
} }

View File

@@ -56,22 +56,23 @@ public class SimSlotChangeReceiver extends BroadcastReceiver {
public static void runOnBackgroundThread(Context context) { public static void runOnBackgroundThread(Context context) {
if (shouldHandleSlotChange(context)) { if (shouldHandleSlotChange(context)) {
Log.d(TAG, "Checking satellite enabled status"); Log.d(TAG, "Checking satellite session status");
Executor executor = Executors.newSingleThreadExecutor(); Executor executor = Executors.newSingleThreadExecutor();
ListenableFuture<Boolean> satelliteEnabledFuture = new SatelliteRepository(context) ListenableFuture<Boolean> isSatelliteSessionStartedFuture =
.requestIsEnabled(executor); new SatelliteRepository(context).requestIsSessionStarted(executor);
satelliteEnabledFuture.addListener(() -> { isSatelliteSessionStartedFuture.addListener(() -> {
boolean isSatelliteEnabled = false; boolean isSatelliteSessionStarted = false;
try { try {
isSatelliteEnabled = satelliteEnabledFuture.get(); isSatelliteSessionStarted = isSatelliteSessionStartedFuture.get();
} catch (ExecutionException | InterruptedException e) { } catch (ExecutionException | InterruptedException e) {
Log.w(TAG, "Can't get satellite enabled status", e); Log.w(TAG, "Can't get satellite session status", e);
} }
if (isSatelliteEnabled) { if (isSatelliteSessionStarted) {
Log.i(TAG, "Satellite is enabled. Unable to handle SIM slot changes"); Log.i(TAG, "Device is in a satellite session. Unable to handle SIM slot"
+ " changes");
} else { } else {
Log.i(TAG, "Satellite is disabled. Handle slot changes"); Log.i(TAG, "Not in a satellite session. Handle slot changes");
SimSlotChangeHandler.get().onSlotsStatusChange(context.getApplicationContext()); SimSlotChangeHandler.get().onSlotsStatusChange(context.getApplicationContext());
} }
}, executor); }, executor);

View File

@@ -24,7 +24,6 @@ import android.telephony.satellite.SatelliteModemStateCallback
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import java.util.concurrent.Executor
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
@@ -35,12 +34,12 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.any import org.mockito.Mockito.*
import org.mockito.Mockito.`when`
import org.mockito.Spy import org.mockito.Spy
import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule import org.mockito.junit.MockitoRule
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
import java.util.concurrent.Executor
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@@ -90,6 +89,55 @@ class SatelliteRepositoryTest {
assertTrue(result.get()) assertTrue(result.get())
} }
@Test
fun requestIsSessionStarted_resultIsTrue() = runBlocking {
`when`(mockSatelliteManager.registerForModemStateChanged(any(), any())
).thenAnswer { invocation ->
val callback = invocation.getArgument<SatelliteModemStateCallback>(1)
callback.onSatelliteModemStateChanged(SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED)
SatelliteManager.SATELLITE_RESULT_SUCCESS
}
val result: ListenableFuture<Boolean> = repository.requestIsSessionStarted(mockExecutor)
assertTrue(result.get())
verify(mockSatelliteManager).unregisterForModemStateChanged(any())
}
@Test
fun requestIsSessionStarted_resultIsFalse() = runBlocking {
`when`(mockSatelliteManager.registerForModemStateChanged(any(), any())
).thenAnswer { invocation ->
val callback = invocation.getArgument<SatelliteModemStateCallback>(1)
callback.onSatelliteModemStateChanged(SatelliteManager.SATELLITE_MODEM_STATE_OFF)
SatelliteManager.SATELLITE_RESULT_SUCCESS
}
val result: ListenableFuture<Boolean> = repository.requestIsSessionStarted(mockExecutor)
assertFalse(result.get())
verify(mockSatelliteManager).unregisterForModemStateChanged(any())
}
@Test
fun requestIsSessionStarted_registerFailed() = runBlocking {
`when`(mockSatelliteManager.registerForModemStateChanged(any(), any())
).thenAnswer { invocation ->
SatelliteManager.SATELLITE_RESULT_ERROR
}
val result: ListenableFuture<Boolean> = repository.requestIsSessionStarted(mockExecutor)
assertFalse(result.get())
verify(mockSatelliteManager, never()).unregisterForModemStateChanged(any())
}
@Test
fun requestIsSessionStarted_nullSatelliteManager() = runBlocking {
`when`(spyContext.getSystemService(SatelliteManager::class.java)).thenReturn(null)
val result: ListenableFuture<Boolean> = repository.requestIsSessionStarted(mockExecutor)
assertFalse(result.get())
verifyNoInteractions(mockSatelliteManager)
}
@Test @Test
fun requestIsEnabled_resultIsFalse() = runBlocking { fun requestIsEnabled_resultIsFalse() = runBlocking {
`when`( `when`(