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
diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml
index 1e43ef0..adb84b6 100644
--- a/res/xml/mobile_network_settings.xml
+++ b/res/xml/mobile_network_settings.xml
@@ -18,9 +18,8 @@
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="mobile_network_pref_screen">
- <com.android.settings.widget.SettingsMainSwitchPreference
+ <com.android.settings.spa.preference.ComposePreference
android:key="use_sim_switch"
- android:title="@string/mobile_network_use_sim_on"
settings:controller="com.android.settings.network.telephony.MobileNetworkSwitchController"/>
<PreferenceCategory
diff --git a/src/com/android/settings/network/SatelliteManagerUtil.kt b/src/com/android/settings/network/SatelliteManagerUtil.kt
deleted file mode 100644
index 5dc1a84..0000000
--- a/src/com/android/settings/network/SatelliteManagerUtil.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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<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 ->
- satelliteManager.requestIsEnabled(executor,
- object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
- 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/network/SatelliteRepository.kt b/src/com/android/settings/network/SatelliteRepository.kt
new file mode 100644
index 0000000..4145e01
--- /dev/null
+++ b/src/com/android/settings/network/SatelliteRepository.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.SatelliteModemStateCallback
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+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
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * A repository class for interacting with the SatelliteManager API.
+ */
+class SatelliteRepository(
+ private val context: Context,
+) {
+
+ /**
+ * Checks if the satellite modem is enabled.
+ *
+ * @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.
+ */
+ fun requestIsEnabled(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 ->
+ satelliteManager.requestIsEnabled(executor,
+ object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
+ 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"
+ }
+ }
+
+ /**
+ * Provides a Flow that emits the enabled state of the satellite modem. Updates are triggered
+ * when the modem state changes.
+ *
+ * @param defaultDispatcher The CoroutineDispatcher to use (Defaults to `Dispatchers.Default`).
+ * @return A Flow emitting `true` when the modem is enabled and `false` otherwise.
+ */
+ fun getIsModemEnabledFlow(
+ defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
+ ): Flow<Boolean> {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return flowOf(false)
+ }
+
+ return callbackFlow {
+ val callback = SatelliteModemStateCallback { state ->
+ val isEnabled = convertSatelliteModemStateToEnabledState(state)
+ Log.i(TAG, "Satellite modem state changed: state=$state, isEnabled=$isEnabled")
+ trySend(isEnabled)
+ }
+
+ val result = satelliteManager.registerForModemStateChanged(
+ defaultDispatcher.asExecutor(),
+ callback
+ )
+ Log.i(TAG, "Call registerForModemStateChanged: result=$result")
+
+ awaitClose { satelliteManager.unregisterForModemStateChanged(callback) }
+ }
+ }
+
+ /**
+ * Converts a [SatelliteManager.SatelliteModemState] to a boolean representing whether the modem
+ * is enabled.
+ *
+ * @param state The SatelliteModemState provided by the SatelliteManager.
+ * @return `true` if the modem is enabled, `false` otherwise.
+ */
+ @VisibleForTesting
+ fun convertSatelliteModemStateToEnabledState(
+ @SatelliteManager.SatelliteModemState state: Int,
+ ): Boolean {
+ // Mapping table based on logic from b/315928920#comment24
+ return when (state) {
+ SatelliteManager.SATELLITE_MODEM_STATE_IDLE,
+ SatelliteManager.SATELLITE_MODEM_STATE_LISTENING,
+ SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING,
+ SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING,
+ SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED,
+ SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED -> true
+ else -> false
+ }
+ }
+
+ companion object {
+ private const val TAG: String = "SatelliteRepository"
+ }
+}
+
diff --git a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
index f682002..df3b8ba 100644
--- a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
+++ b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
@@ -20,6 +20,7 @@
import android.telephony.SubscriptionManager
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
+import com.android.settings.network.telephony.getSelectableSubscriptionInfoList
import com.android.settings.network.telephony.subscriptionsChangedFlow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
@@ -41,10 +42,10 @@
}.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
/**
- * Getting the Selectable SubscriptionInfo List from the SubscriptionManager's
+ * Getting the Selectable SubscriptionInfo List from the SubscriptionRepository's
* getAvailableSubscriptionInfoList
*/
val selectableSubscriptionInfoListFlow = application.subscriptionsChangedFlow().map {
- SubscriptionUtil.getSelectableSubscriptionInfoList(application)
+ application.getSelectableSubscriptionInfoList()
}.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
}
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index 3632ca3..56381e2 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -50,12 +50,12 @@
import com.android.settings.network.helper.SubscriptionAnnotation;
import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
+import com.android.settings.network.telephony.SubscriptionRepositoryKt;
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -505,40 +505,7 @@
* @return list of user selectable subscriptions.
*/
public static List<SubscriptionInfo> getSelectableSubscriptionInfoList(Context context) {
- SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
- List<SubscriptionInfo> availableList = subManager.getAvailableSubscriptionInfoList();
- if (availableList == null) {
- return null;
- } else {
- // Multiple subscriptions in a group should only have one representative.
- // It should be the current active primary subscription if any, or any
- // primary subscription.
- List<SubscriptionInfo> selectableList = new ArrayList<>();
- Map<ParcelUuid, SubscriptionInfo> groupMap = new HashMap<>();
-
- for (SubscriptionInfo info : availableList) {
- // Opportunistic subscriptions are considered invisible
- // to users so they should never be returned.
- if (!isSubscriptionVisible(subManager, context, info)) continue;
-
- ParcelUuid groupUuid = info.getGroupUuid();
- if (groupUuid == null) {
- // Doesn't belong to any group. Add in the list.
- selectableList.add(info);
- } else if (!groupMap.containsKey(groupUuid)
- || (groupMap.get(groupUuid).getSimSlotIndex() == INVALID_SIM_SLOT_INDEX
- && info.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX)) {
- // If it belongs to a group that has never been recorded or it's the current
- // active subscription, add it in the list.
- selectableList.remove(groupMap.get(groupUuid));
- selectableList.add(info);
- groupMap.put(groupUuid, info);
- }
-
- }
- Log.d(TAG, "getSelectableSubscriptionInfoList: " + selectableList);
- return selectableList;
- }
+ return SubscriptionRepositoryKt.getSelectableSubscriptionInfoList(context);
}
/**
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java
deleted file mode 100644
index 20a3d89..0000000
--- a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.telephony.TelephonyManager.CALL_STATE_IDLE;
-
-import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
-
-import android.content.Context;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyCallback;
-import android.telephony.TelephonyManager;
-
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.OnLifecycleEvent;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.network.SubscriptionUtil;
-import com.android.settings.network.SubscriptionsChangeListener;
-import com.android.settings.widget.SettingsMainSwitchPreference;
-
-/** This controls a switch to allow enabling/disabling a mobile network */
-public class MobileNetworkSwitchController extends BasePreferenceController implements
- SubscriptionsChangeListener.SubscriptionsChangeListenerClient, LifecycleObserver {
- private static final String TAG = "MobileNetworkSwitchCtrl";
- private SettingsMainSwitchPreference mSwitchBar;
- private int mSubId;
- private SubscriptionsChangeListener mChangeListener;
- private SubscriptionManager mSubscriptionManager;
- private TelephonyManager mTelephonyManager;
- private CallStateTelephonyCallback mCallStateCallback;
-
- public MobileNetworkSwitchController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
- mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
- mChangeListener = new SubscriptionsChangeListener(context, this);
- }
-
- void init(int subId) {
- mSubId = subId;
- mTelephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
- }
-
- @OnLifecycleEvent(ON_RESUME)
- public void onResume() {
- mChangeListener.start();
-
- if (mCallStateCallback == null) {
- mCallStateCallback = new CallStateTelephonyCallback();
- mTelephonyManager.registerTelephonyCallback(
- mContext.getMainExecutor(), mCallStateCallback);
- }
- update();
- }
-
- @OnLifecycleEvent(ON_PAUSE)
- public void onPause() {
- if (mCallStateCallback != null) {
- mTelephonyManager.unregisterTelephonyCallback(mCallStateCallback);
- mCallStateCallback = null;
- }
- mChangeListener.stop();
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mSwitchBar = (SettingsMainSwitchPreference) screen.findPreference(mPreferenceKey);
-
- mSwitchBar.setOnBeforeCheckedChangeListener((isChecked) -> {
- // TODO b/135222940: re-evaluate whether to use
- // mSubscriptionManager#isSubscriptionEnabled
- if (mSubscriptionManager.isActiveSubscriptionId(mSubId) != isChecked) {
- SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, mSubId, isChecked);
- return true;
- }
- return false;
- });
- update();
- }
-
- private void update() {
- if (mSwitchBar == null) {
- return;
- }
-
- SubscriptionInfo subInfo = null;
- for (SubscriptionInfo info : SubscriptionUtil.getAvailableSubscriptions(mContext)) {
- if (info.getSubscriptionId() == mSubId) {
- subInfo = info;
- break;
- }
- }
-
- // For eSIM, we always want the toggle. If telephony stack support disabling a pSIM
- // directly, we show the toggle.
- if (subInfo == null || (!subInfo.isEmbedded() && !SubscriptionUtil.showToggleForPhysicalSim(
- mSubscriptionManager))) {
- mSwitchBar.hide();
- } else {
- mSwitchBar.show();
- mSwitchBar.setCheckedInternal(mSubscriptionManager.isActiveSubscriptionId(mSubId));
- }
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE_UNSEARCHABLE;
-
- }
-
- @Override
- public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
- }
-
- @Override
- public void onSubscriptionsChanged() {
- update();
- }
-
- private class CallStateTelephonyCallback extends TelephonyCallback implements
- TelephonyCallback.CallStateListener {
- @Override
- public void onCallStateChanged(int state) {
- mSwitchBar.setSwitchBarEnabled(state == CALL_STATE_IDLE);
- }
- }
-}
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt
new file mode 100644
index 0000000..3e1aab8
--- /dev/null
+++ b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.SubscriptionManager
+import android.telephony.TelephonyManager
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.settings.R
+import com.android.settings.network.SatelliteRepository
+import com.android.settings.network.SubscriptionUtil
+import com.android.settings.spa.preference.ComposePreferenceController
+import com.android.settingslib.spa.widget.preference.MainSwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+class MobileNetworkSwitchController @JvmOverloads constructor(
+ context: Context,
+ preferenceKey: String,
+ private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
+ private val satelliteRepository: SatelliteRepository = SatelliteRepository(context)
+) : ComposePreferenceController(context, preferenceKey) {
+
+ private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+
+ override fun getAvailabilityStatus() = AVAILABLE_UNSEARCHABLE
+
+ fun init(subId: Int) {
+ this.subId = subId
+ }
+
+ @Composable
+ override fun Content() {
+ val context = LocalContext.current
+ if (remember { !context.isVisible() }) return
+ val checked by remember {
+ subscriptionRepository.isSubscriptionEnabledFlow(subId)
+ }.collectAsStateWithLifecycle(initialValue = null)
+ val changeable by remember {
+ combine(
+ context.callStateFlow(subId).map { it == TelephonyManager.CALL_STATE_IDLE },
+ satelliteRepository.getIsModemEnabledFlow()
+ ) { isCallStateIdle, isSatelliteModemEnabled ->
+ isCallStateIdle && !isSatelliteModemEnabled
+ }
+ }.collectAsStateWithLifecycle(initialValue = true)
+ MainSwitchPreference(model = object : SwitchPreferenceModel {
+ override val title = stringResource(R.string.mobile_network_use_sim_on)
+ override val changeable = { changeable }
+ override val checked = { checked }
+ override val onCheckedChange = { newChecked: Boolean ->
+ SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, newChecked)
+ }
+ })
+ }
+
+ private fun Context.isVisible(): Boolean {
+ val subInfo = subscriptionRepository.getSelectableSubscriptionInfoList()
+ .firstOrNull { it.subscriptionId == subId }
+ ?: return false
+ // For eSIM, we always want the toggle. If telephony stack support disabling a pSIM
+ // directly, we show the toggle.
+ return subInfo.isEmbedded || requireSubscriptionManager().canDisablePhysicalSubscription()
+ }
+}
+
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index e44b577..8aee297 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -20,30 +20,49 @@
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.util.Log
+import androidx.lifecycle.LifecycleOwner
import com.android.settings.network.SubscriptionUtil
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
private const val TAG = "SubscriptionRepository"
-fun Context.isSubscriptionEnabledFlow(subId: Int) = subscriptionsChangedFlow().map {
- val subscriptionManager = getSystemService(SubscriptionManager::class.java)
+class SubscriptionRepository(private val context: Context) {
+ /**
+ * Return a list of subscriptions that are available and visible to the user.
+ *
+ * @return list of user selectable subscriptions.
+ */
+ fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> =
+ context.getSelectableSubscriptionInfoList()
+ fun isSubscriptionEnabledFlow(subId: Int) = context.isSubscriptionEnabledFlow(subId)
+}
+
+val Context.subscriptionManager: SubscriptionManager?
+ get() = getSystemService(SubscriptionManager::class.java)
+
+fun Context.requireSubscriptionManager(): SubscriptionManager = subscriptionManager!!
+
+fun Context.isSubscriptionEnabledFlow(subId: Int) = subscriptionsChangedFlow().map {
subscriptionManager?.isSubscriptionEnabled(subId) ?: false
-}.flowOn(Dispatchers.Default)
+}.conflate().onEach { Log.d(TAG, "[$subId] isSubscriptionEnabledFlow: $it") }
+ .flowOn(Dispatchers.Default)
fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map {
SubscriptionUtil.getFormattedPhoneNumber(this, subscriptionInfo)
-}.flowOn(Dispatchers.Default)
+}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
fun Context.subscriptionsChangedFlow() = callbackFlow {
- val subscriptionManager = getSystemService(SubscriptionManager::class.java)!!
+ val subscriptionManager = requireSubscriptionManager()
val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
override fun onSubscriptionsChanged() {
@@ -58,3 +77,36 @@
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
}.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
+
+/**
+ * Return a list of subscriptions that are available and visible to the user.
+ *
+ * @return list of user selectable subscriptions.
+ */
+fun Context.getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
+ val subscriptionManager = requireSubscriptionManager()
+ val availableList = subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList()
+ val visibleList = availableList.filter { subInfo ->
+ // Opportunistic subscriptions are considered invisible
+ // to users so they should never be returned.
+ SubscriptionUtil.isSubscriptionVisible(subscriptionManager, this, subInfo)
+ }
+ // Multiple subscriptions in a group should only have one representative.
+ // It should be the current active primary subscription if any, or any primary subscription.
+ val groupUuidToSelectedIdMap = visibleList
+ .groupBy { it.groupUuid }
+ .mapValues { (_, subInfos) ->
+ subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX }
+ .ifEmpty { subInfos }
+ .minOf { it.subscriptionId }
+ }
+
+ return visibleList
+ .filter { subInfo ->
+ val groupUuid = subInfo.groupUuid ?: return@filter true
+ groupUuidToSelectedIdMap[groupUuid] == subInfo.subscriptionId
+ }
+ .sortedBy { it.subscriptionId }
+ .also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") }
+}
+
diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java
index 9bba217..4920bb8 100644
--- a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java
+++ b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java
@@ -29,7 +29,7 @@
import androidx.annotation.Nullable;
import com.android.settings.R;
-import com.android.settings.network.SatelliteManagerUtil;
+import com.android.settings.network.SatelliteRepository;
import com.google.common.util.concurrent.ListenableFuture;
@@ -58,8 +58,8 @@
if (shouldHandleSlotChange(context)) {
Log.d(TAG, "Checking satellite enabled status");
Executor executor = Executors.newSingleThreadExecutor();
- ListenableFuture<Boolean> satelliteEnabledFuture = SatelliteManagerUtil
- .requestIsEnabled(context, executor);
+ ListenableFuture<Boolean> satelliteEnabledFuture = new SatelliteRepository(context)
+ .requestIsEnabled(executor);
satelliteEnabledFuture.addListener(() -> {
boolean isSatelliteEnabled = false;
try {
diff --git a/tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt b/tests/robotests/src/com/android/settings/network/SatelliteRepositoryTest.kt
similarity index 65%
rename from tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt
rename to tests/robotests/src/com/android/settings/network/SatelliteRepositoryTest.kt
index 50d7897..432048c 100644
--- a/tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt
+++ b/tests/robotests/src/com/android/settings/network/SatelliteRepositoryTest.kt
@@ -20,10 +20,12 @@
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 @@
@RunWith(RobolectricTestRunner::class)
-class SatelliteManagerUtilTest {
+class SatelliteRepositoryTest {
@JvmField
@Rule
@@ -57,10 +59,15 @@
@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 @@
}
val result: ListenableFuture<Boolean> =
- requestIsEnabled(spyContext, mockExecutor)
+ repository.requestIsEnabled(mockExecutor)
assertTrue(result.get())
}
@@ -98,7 +105,7 @@
}
val result: ListenableFuture<Boolean> =
- requestIsEnabled(spyContext, mockExecutor)
+ repository.requestIsEnabled(mockExecutor)
assertFalse(result.get())
}
@@ -117,7 +124,7 @@
null
}
- val result = requestIsEnabled(spyContext, mockExecutor)
+ val result = repository.requestIsEnabled(mockExecutor)
assertFalse(result.get())
}
@@ -126,8 +133,52 @@
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())
}
-}
\ No newline at end of file
+
+ @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()
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt
new file mode 100644
index 0000000..e731333
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt
@@ -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()
+ }
+}
+
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
index a59bf93..3f69155 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
@@ -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 @@
}
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 @@
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"
}
}
+
diff --git a/tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java
deleted file mode 100644
index 3cdd23a..0000000
--- a/tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java
+++ /dev/null
@@ -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));
- }
-}