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));
-    }
-}