Disable SIM On/Off operation when device is in Satellite Enabled Mode

Cherry-picking ag/26965536 into the 24D1-dev branch caused conflicts. Therefore, manually create this CL to migrate the MobileNetworkSwitchController to Kotlin and utilize Compose.

Bug: 315928920
Test: atest, manual
Change-Id: I215b5a4615a3b3da6fc160f76c85c814210cc3ef
Merged-In: I7aaaf43b4c449129197e7cc92565d274ffdd2d8c
This commit is contained in:
Samuel Huang
2024-04-16 08:42:08 +00:00
parent 67df73a1fd
commit 316e7bf3e6
13 changed files with 603 additions and 543 deletions

View File

@@ -20,10 +20,12 @@ import android.content.Context
import android.os.OutcomeReceiver
import android.telephony.satellite.SatelliteManager
import android.telephony.satellite.SatelliteManager.SatelliteException
import android.telephony.satellite.SatelliteModemStateCallback
import androidx.test.core.app.ApplicationProvider
import com.android.settings.network.SatelliteManagerUtil.requestIsEnabled
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.ListenableFuture
import java.util.concurrent.Executor
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -42,7 +44,7 @@ import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class SatelliteManagerUtilTest {
class SatelliteRepositoryTest {
@JvmField
@Rule
@@ -57,10 +59,15 @@ class SatelliteManagerUtilTest {
@Mock
private lateinit var mockExecutor: Executor
private lateinit var repository: SatelliteRepository
@Before
fun setUp() {
`when`(this.spyContext.getSystemService(SatelliteManager::class.java))
.thenReturn(mockSatelliteManager)
repository = SatelliteRepository(spyContext)
}
@Test
@@ -78,7 +85,7 @@ class SatelliteManagerUtilTest {
}
val result: ListenableFuture<Boolean> =
requestIsEnabled(spyContext, mockExecutor)
repository.requestIsEnabled(mockExecutor)
assertTrue(result.get())
}
@@ -98,7 +105,7 @@ class SatelliteManagerUtilTest {
}
val result: ListenableFuture<Boolean> =
requestIsEnabled(spyContext, mockExecutor)
repository.requestIsEnabled(mockExecutor)
assertFalse(result.get())
}
@@ -117,7 +124,7 @@ class SatelliteManagerUtilTest {
null
}
val result = requestIsEnabled(spyContext, mockExecutor)
val result = repository.requestIsEnabled(mockExecutor)
assertFalse(result.get())
}
@@ -126,8 +133,52 @@ class SatelliteManagerUtilTest {
fun requestIsEnabled_nullSatelliteManager() = runBlocking {
`when`(spyContext.getSystemService(SatelliteManager::class.java)).thenReturn(null)
val result: ListenableFuture<Boolean> = requestIsEnabled(spyContext, mockExecutor)
val result: ListenableFuture<Boolean> = repository.requestIsEnabled(mockExecutor)
assertFalse(result.get())
}
}
@Test
fun getIsModemEnabledFlow_isSatelliteEnabledState() = 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 flow = repository.getIsModemEnabledFlow()
assertThat(flow.first()).isTrue()
}
@Test
fun getIsModemEnabledFlow_isSatelliteDisabledState() = 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 flow = repository.getIsModemEnabledFlow()
assertThat(flow.first()).isFalse()
}
@Test
fun getIsModemEnabledFlow_nullSatelliteManager() = runBlocking {
`when`(spyContext.getSystemService(SatelliteManager::class.java)).thenReturn(null)
val flow = repository.getIsModemEnabledFlow()
assertThat(flow.first()).isFalse()
}
}

View File

@@ -0,0 +1,169 @@
/*
* 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.telephony
import android.content.Context
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isOff
import androidx.compose.ui.test.isOn
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settingslib.spa.testutils.waitUntilExists
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class MobileNetworkSwitchControllerTest {
@get:Rule
val composeTestRule = createComposeRule()
private val mockSubscriptionManager = mock<SubscriptionManager> {
on { isSubscriptionEnabled(SUB_ID) } doReturn true
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { subscriptionManager } doReturn mockSubscriptionManager
doNothing().whenever(mock).startActivity(any())
}
private val mockSubscriptionRepository = mock<SubscriptionRepository> {
on { getSelectableSubscriptionInfoList() } doReturn listOf(SubInfo)
on { isSubscriptionEnabledFlow(SUB_ID) } doReturn flowOf(false)
}
private val controller = MobileNetworkSwitchController(
context = context,
preferenceKey = TEST_KEY,
subscriptionRepository = mockSubscriptionRepository,
).apply { init(SUB_ID) }
@Test
fun isVisible_pSimAndCanDisablePhysicalSubscription_returnTrue() {
val pSimSubInfo = SubscriptionInfo.Builder().apply {
setId(SUB_ID)
setEmbedded(false)
}.build()
mockSubscriptionManager.stub {
on { canDisablePhysicalSubscription() } doReturn true
}
mockSubscriptionRepository.stub {
on { getSelectableSubscriptionInfoList() } doReturn listOf(pSimSubInfo)
}
setContent()
composeTestRule.onNodeWithText(context.getString(R.string.mobile_network_use_sim_on))
.assertIsDisplayed()
}
@Test
fun isVisible_pSimAndCannotDisablePhysicalSubscription_returnFalse() {
val pSimSubInfo = SubscriptionInfo.Builder().apply {
setId(SUB_ID)
setEmbedded(false)
}.build()
mockSubscriptionManager.stub {
on { canDisablePhysicalSubscription() } doReturn false
}
mockSubscriptionRepository.stub {
on { getSelectableSubscriptionInfoList() } doReturn listOf(pSimSubInfo)
}
setContent()
composeTestRule.onNodeWithText(context.getString(R.string.mobile_network_use_sim_on))
.assertDoesNotExist()
}
@Test
fun isVisible_eSim_returnTrue() {
val eSimSubInfo = SubscriptionInfo.Builder().apply {
setId(SUB_ID)
setEmbedded(true)
}.build()
mockSubscriptionRepository.stub {
on { getSelectableSubscriptionInfoList() } doReturn listOf(eSimSubInfo)
}
setContent()
composeTestRule.onNodeWithText(context.getString(R.string.mobile_network_use_sim_on))
.assertIsDisplayed()
}
@Test
fun isChecked_subscriptionEnabled_switchIsOn() {
mockSubscriptionRepository.stub {
on { isSubscriptionEnabledFlow(SUB_ID) } doReturn flowOf(true)
}
setContent()
composeTestRule.waitUntilExists(
hasText(context.getString(R.string.mobile_network_use_sim_on)) and isOn()
)
}
@Test
fun isChecked_subscriptionNotEnabled_switchIsOff() {
mockSubscriptionRepository.stub {
on { isSubscriptionEnabledFlow(SUB_ID) } doReturn flowOf(false)
}
setContent()
composeTestRule.waitUntilExists(
hasText(context.getString(R.string.mobile_network_use_sim_on)) and isOff()
)
}
private fun setContent() {
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
controller.Content()
}
}
}
private companion object {
const val TEST_KEY = "test_key"
const val SUB_ID = 123
val SubInfo: SubscriptionInfo = SubscriptionInfo.Builder().apply {
setId(SUB_ID)
setEmbedded(true)
}.build()
}
}

View File

@@ -17,12 +17,14 @@
package com.android.settings.network.telephony
import android.content.Context
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.android.settingslib.spa.testutils.toListWithTimeout
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
@@ -47,16 +49,16 @@ class SubscriptionRepositoryTest {
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager
on { subscriptionManager } doReturn mockSubscriptionManager
}
@Test
fun isSubscriptionEnabledFlow() = runBlocking {
mockSubscriptionManager.stub {
on { isSubscriptionEnabled(SUB_ID) } doReturn true
on { isSubscriptionEnabled(SUB_ID_1) } doReturn true
}
val isEnabled = context.isSubscriptionEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
val isEnabled = context.isSubscriptionEnabledFlow(SUB_ID_1).firstWithTimeoutOrNull()
assertThat(isEnabled).isTrue()
}
@@ -80,7 +82,87 @@ class SubscriptionRepositoryTest {
assertThat(listDeferred.await()).hasSize(2)
}
@Test
fun getSelectableSubscriptionInfoList_sortedBySubId() {
mockSubscriptionManager.stub {
on { getAvailableSubscriptionInfoList() } doReturn listOf(
SubscriptionInfo.Builder().apply {
setId(SUB_ID_2)
}.build(),
SubscriptionInfo.Builder().apply {
setId(SUB_ID_1)
}.build(),
)
}
val subInfos = context.getSelectableSubscriptionInfoList()
assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_1, SUB_ID_2).inOrder()
}
@Test
fun getSelectableSubscriptionInfoList_sameGroupAndOneHasSlot_returnTheOneWithSimSlotIndex() {
mockSubscriptionManager.stub {
on { getAvailableSubscriptionInfoList() } doReturn listOf(
SubscriptionInfo.Builder().apply {
setId(SUB_ID_1)
setGroupUuid(GROUP_UUID)
}.build(),
SubscriptionInfo.Builder().apply {
setId(SUB_ID_2)
setGroupUuid(GROUP_UUID)
setSimSlotIndex(SIM_SLOT_INDEX)
}.build(),
)
}
val subInfos = context.getSelectableSubscriptionInfoList()
assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_2)
}
@Test
fun getSelectableSubscriptionInfoList_sameGroupAndNonHasSlot_returnTheOneWithMinimumSubId() {
mockSubscriptionManager.stub {
on { getAvailableSubscriptionInfoList() } doReturn listOf(
SubscriptionInfo.Builder().apply {
setId(SUB_ID_2)
setGroupUuid(GROUP_UUID)
}.build(),
SubscriptionInfo.Builder().apply {
setId(SUB_ID_1)
setGroupUuid(GROUP_UUID)
}.build(),
)
}
val subInfos = context.getSelectableSubscriptionInfoList()
assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_1)
}
@Test
fun phoneNumberFlow() = runBlocking {
mockSubscriptionManager.stub {
on { getPhoneNumber(SUB_ID_1) } doReturn NUMBER_1
}
val subInfo = SubscriptionInfo.Builder().apply {
setId(SUB_ID_1)
setMcc(MCC)
}.build()
val phoneNumber = context.phoneNumberFlow(subInfo).firstWithTimeoutOrNull()
assertThat(phoneNumber).isEqualTo(NUMBER_1)
}
private companion object {
const val SUB_ID = 1
const val SUB_ID_1 = 1
const val SUB_ID_2 = 2
val GROUP_UUID = UUID.randomUUID().toString()
const val SIM_SLOT_INDEX = 1
const val NUMBER_1 = "000000001"
const val MCC = "310"
}
}

View File

@@ -1,269 +0,0 @@
/*
* Copyright (C) 2020 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.telephony;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.widget.SettingsMainSwitchPreference;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.Arrays;
import java.util.concurrent.Executor;
public class MobileNetworkSwitchControllerTest {
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock
private SubscriptionManager mSubscriptionManager;
@Mock
private SubscriptionInfo mSubscription;
@Mock
private TelephonyManager mTelephonyManager;
private PreferenceScreen mScreen;
private PreferenceManager mPreferenceManager;
private SettingsMainSwitchPreference mSwitchBar;
private Context mContext;
private MobileNetworkSwitchController mController;
private int mSubId = 123;
@Before
public void setUp() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
when(mSubscriptionManager.setSubscriptionEnabled(eq(mSubId), anyBoolean()))
.thenReturn(true);
when(mSubscription.isEmbedded()).thenReturn(true);
when(mSubscription.getSubscriptionId()).thenReturn(mSubId);
// Most tests want to have 2 available subscriptions so that the switch bar will show.
final SubscriptionInfo sub2 = mock(SubscriptionInfo.class);
when(sub2.getSubscriptionId()).thenReturn(456);
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription, sub2));
when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
when(mTelephonyManager.createForSubscriptionId(mSubId))
.thenReturn(mTelephonyManager);
final String key = "prefKey";
mController = new MobileNetworkSwitchController(mContext, key);
mController.init(mSubscription.getSubscriptionId());
mPreferenceManager = new PreferenceManager(mContext);
mScreen = mPreferenceManager.createPreferenceScreen(mContext);
mSwitchBar = new SettingsMainSwitchPreference(mContext);
mSwitchBar.setKey(key);
mSwitchBar.setTitle("123");
mScreen.addPreference(mSwitchBar);
final LayoutInflater inflater = LayoutInflater.from(mContext);
final View view = inflater.inflate(mSwitchBar.getLayoutResource(),
new LinearLayout(mContext), false);
final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view);
mSwitchBar.onBindViewHolder(holder);
}
@After
public void cleanUp() {
SubscriptionUtil.setAvailableSubscriptionsForTesting(null);
}
@Test
@UiThreadTest
public void isAvailable_pSIM_isNotAvailable() {
when(mSubscription.isEmbedded()).thenReturn(false);
mController.displayPreference(mScreen);
assertThat(mSwitchBar.isShowing()).isFalse();
when(mSubscriptionManager.canDisablePhysicalSubscription()).thenReturn(true);
mController.displayPreference(mScreen);
assertThat(mSwitchBar.isShowing()).isTrue();
}
@Test
@UiThreadTest
public void displayPreference_oneEnabledSubscription_switchBarNotHidden() {
doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(mSubId);
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription));
mController.displayPreference(mScreen);
assertThat(mSwitchBar.isShowing()).isTrue();
}
@Test
@UiThreadTest
public void displayPreference_oneDisabledSubscription_switchBarNotHidden() {
doReturn(false).when(mSubscriptionManager).isActiveSubscriptionId(mSubId);
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription));
mController.displayPreference(mScreen);
assertThat(mSwitchBar.isShowing()).isTrue();
}
@Test
@UiThreadTest
public void displayPreference_subscriptionEnabled_switchIsOn() {
when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(true);
mController.displayPreference(mScreen);
assertThat(mSwitchBar.isShowing()).isTrue();
assertThat(mSwitchBar.isChecked()).isTrue();
}
@Test
@UiThreadTest
public void displayPreference_subscriptionDisabled_switchIsOff() {
when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(false);
mController.displayPreference(mScreen);
assertThat(mSwitchBar.isShowing()).isTrue();
assertThat(mSwitchBar.isChecked()).isFalse();
}
@Test
@UiThreadTest
public void switchChangeListener_fromEnabledToDisabled_setSubscriptionEnabledCalledCorrectly() {
when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(true);
mController.displayPreference(mScreen);
assertThat(mSwitchBar.isShowing()).isTrue();
assertThat(mSwitchBar.isChecked()).isTrue();
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
doNothing().when(mContext).startActivity(intentCaptor.capture());
// set switch off then should start a Activity.
mSwitchBar.setChecked(false);
when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(false);
// Simulate action of back from previous activity.
mController.displayPreference(mScreen);
Bundle extra = intentCaptor.getValue().getExtras();
verify(mContext, times(1)).startActivity(any());
assertThat(extra.getInt(ToggleSubscriptionDialogActivity.ARG_SUB_ID)).isEqualTo(mSubId);
assertThat(extra.getBoolean(ToggleSubscriptionDialogActivity.ARG_enable))
.isEqualTo(false);
assertThat(mSwitchBar.isChecked()).isFalse();
}
@Test
@UiThreadTest
public void switchChangeListener_fromEnabledToDisabled_setSubscriptionEnabledFailed() {
when(mSubscriptionManager.setSubscriptionEnabled(eq(mSubId), anyBoolean()))
.thenReturn(false);
when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(true);
mController.displayPreference(mScreen);
assertThat(mSwitchBar.isShowing()).isTrue();
assertThat(mSwitchBar.isChecked()).isTrue();
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
doNothing().when(mContext).startActivity(intentCaptor.capture());
// set switch off then should start a Activity.
mSwitchBar.setChecked(false);
// Simulate action of back from previous activity.
mController.displayPreference(mScreen);
Bundle extra = intentCaptor.getValue().getExtras();
verify(mContext, times(1)).startActivity(any());
assertThat(extra.getInt(ToggleSubscriptionDialogActivity.ARG_SUB_ID)).isEqualTo(mSubId);
assertThat(extra.getBoolean(ToggleSubscriptionDialogActivity.ARG_enable))
.isEqualTo(false);
assertThat(mSwitchBar.isChecked()).isTrue();
}
@Test
@UiThreadTest
public void switchChangeListener_fromDisabledToEnabled_setSubscriptionEnabledCalledCorrectly() {
when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(false);
mController.displayPreference(mScreen);
assertThat(mSwitchBar.isShowing()).isTrue();
assertThat(mSwitchBar.isChecked()).isFalse();
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
doNothing().when(mContext).startActivity(intentCaptor.capture());
mSwitchBar.setChecked(true);
Bundle extra = intentCaptor.getValue().getExtras();
verify(mContext, times(1)).startActivity(any());
assertThat(extra.getInt(ToggleSubscriptionDialogActivity.ARG_SUB_ID)).isEqualTo(mSubId);
assertThat(extra.getBoolean(ToggleSubscriptionDialogActivity.ARG_enable)).isEqualTo(true);
}
@Test
@UiThreadTest
public void onResumeAndonPause_registerAndUnregisterTelephonyCallback() {
mController.onResume();
verify(mTelephonyManager)
.registerTelephonyCallback(any(Executor.class), any(TelephonyCallback.class));
mController.onPause();
verify(mTelephonyManager)
.unregisterTelephonyCallback(any(TelephonyCallback.class));
}
@Test
@UiThreadTest
public void onPause_doNotRegisterAndUnregisterTelephonyCallback() {
mController.onPause();
verify(mTelephonyManager, times(0))
.unregisterTelephonyCallback(any(TelephonyCallback.class));
}
}