diff --git a/src/com/android/settings/network/SatelliteManagerUtil.kt b/src/com/android/settings/network/SatelliteManagerUtil.kt new file mode 100644 index 00000000000..5dc1a84fa49 --- /dev/null +++ b/src/com/android/settings/network/SatelliteManagerUtil.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network + +import android.content.Context +import android.os.OutcomeReceiver +import android.telephony.satellite.SatelliteManager +import android.util.Log +import androidx.concurrent.futures.CallbackToFutureAdapter +import com.google.common.util.concurrent.Futures.immediateFuture +import com.google.common.util.concurrent.ListenableFuture +import java.util.concurrent.Executor + +/** + * Utility class for interacting with the SatelliteManager API. + */ +object SatelliteManagerUtil { + + private const val TAG: String = "SatelliteManagerUtil" + + /** + * Checks if the satellite modem is enabled. + * + * @param context The application context + * @param executor The executor to run the asynchronous operation on + * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled, + * `false` otherwise. + */ + @JvmStatic + fun requestIsEnabled(context: Context, executor: Executor): ListenableFuture { + val satelliteManager: SatelliteManager? = + context.getSystemService(SatelliteManager::class.java) + if (satelliteManager == null) { + Log.w(TAG, "SatelliteManager is null") + return immediateFuture(false) + } + + return CallbackToFutureAdapter.getFuture { completer -> + satelliteManager.requestIsEnabled(executor, + object : OutcomeReceiver { + override fun onResult(result: Boolean) { + Log.i(TAG, "Satellite modem enabled status: $result") + completer.set(result) + } + + override fun onError(error: SatelliteManager.SatelliteException) { + super.onError(error) + Log.w(TAG, "Can't get satellite modem enabled status", error) + completer.set(false) + } + }) + "requestIsEnabled" + } + } +} diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java index af8b38c5cf2..9bba2177144 100644 --- a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java +++ b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java @@ -29,9 +29,14 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.settings.R; -import com.android.settingslib.utils.ThreadUtils; +import com.android.settings.network.SatelliteManagerUtil; + +import com.google.common.util.concurrent.ListenableFuture; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; /** The receiver when the slot status changes. */ public class SimSlotChangeReceiver extends BroadcastReceiver { @@ -51,7 +56,25 @@ public class SimSlotChangeReceiver extends BroadcastReceiver { public static void runOnBackgroundThread(Context context) { if (shouldHandleSlotChange(context)) { - SimSlotChangeHandler.get().onSlotsStatusChange(context.getApplicationContext()); + Log.d(TAG, "Checking satellite enabled status"); + Executor executor = Executors.newSingleThreadExecutor(); + ListenableFuture satelliteEnabledFuture = SatelliteManagerUtil + .requestIsEnabled(context, executor); + satelliteEnabledFuture.addListener(() -> { + boolean isSatelliteEnabled = false; + try { + isSatelliteEnabled = satelliteEnabledFuture.get(); + } catch (ExecutionException | InterruptedException e) { + Log.w(TAG, "Can't get satellite enabled status", e); + } + + if (isSatelliteEnabled) { + Log.i(TAG, "Satellite is enabled. Unable to handle SIM slot changes"); + } else { + Log.i(TAG, "Satellite is disabled. Handle slot changes"); + SimSlotChangeHandler.get().onSlotsStatusChange(context.getApplicationContext()); + } + }, executor); } } diff --git a/tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt b/tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt new file mode 100644 index 00000000000..50d78973c16 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network + +import android.content.Context +import android.os.OutcomeReceiver +import android.telephony.satellite.SatelliteManager +import android.telephony.satellite.SatelliteManager.SatelliteException +import androidx.test.core.app.ApplicationProvider +import com.android.settings.network.SatelliteManagerUtil.requestIsEnabled +import com.google.common.util.concurrent.ListenableFuture +import java.util.concurrent.Executor +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.`when` +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.robolectric.RobolectricTestRunner + + +@RunWith(RobolectricTestRunner::class) +class SatelliteManagerUtilTest { + + @JvmField + @Rule + val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Spy + var spyContext: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var mockSatelliteManager: SatelliteManager + + @Mock + private lateinit var mockExecutor: Executor + + @Before + fun setUp() { + `when`(this.spyContext.getSystemService(SatelliteManager::class.java)) + .thenReturn(mockSatelliteManager) + } + + @Test + fun requestIsEnabled_resultIsTrue() = runBlocking { + `when`( + mockSatelliteManager.requestIsEnabled( + eq(mockExecutor), any>() + ) + ) + .thenAnswer { invocation -> + val receiver = + invocation.getArgument>(1) + receiver.onResult(true) + null + } + + val result: ListenableFuture = + requestIsEnabled(spyContext, mockExecutor) + + assertTrue(result.get()) + } + + @Test + fun requestIsEnabled_resultIsFalse() = runBlocking { + `when`( + mockSatelliteManager.requestIsEnabled( + eq(mockExecutor), any>() + ) + ) + .thenAnswer { invocation -> + val receiver = + invocation.getArgument>(1) + receiver.onResult(false) + null + } + + val result: ListenableFuture = + requestIsEnabled(spyContext, mockExecutor) + assertFalse(result.get()) + } + + + @Test + fun requestIsEnabled_exceptionFailure() = runBlocking { + `when`( + mockSatelliteManager.requestIsEnabled( + eq(mockExecutor), any>() + ) + ) + .thenAnswer { invocation -> + val receiver = + invocation.getArgument>(1) + receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR)) + null + } + + val result = requestIsEnabled(spyContext, mockExecutor) + + assertFalse(result.get()) + } + + @Test + fun requestIsEnabled_nullSatelliteManager() = runBlocking { + `when`(spyContext.getSystemService(SatelliteManager::class.java)).thenReturn(null) + + val result: ListenableFuture = requestIsEnabled(spyContext, mockExecutor) + + assertFalse(result.get()) + } +} \ No newline at end of file