Create isSubscriptionEnabledFlow

To better display the isEnable checked state.

Bug: 318310357
Test: manual - on Mobile Settings
Test: unit test
Change-Id: Ia595e7445650ad67883f1e7c1a0662cb826565ea
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index ee4ac1e..9a46272 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -25,10 +25,17 @@
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.conflate
 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)
+
+    subscriptionManager?.isSubscriptionEnabled(subId) ?: false
+}.flowOn(Dispatchers.Default)
+
 fun Context.subscriptionsChangedFlow() = callbackFlow {
     val subscriptionManager = getSystemService(SubscriptionManager::class.java)!!
 
diff --git a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
index 351ac77..a0c363a 100644
--- a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
+++ b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
@@ -16,37 +16,33 @@
 
 package com.android.settings.spa.network
 
-import android.app.Application
 import android.content.Context
-import android.content.Intent
 import android.content.IntentFilter
 import android.os.Bundle
-import android.os.UserManager
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
-import android.telephony.euicc.EuiccManager
 import android.util.Log
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.outlined.Message
-import androidx.compose.material.icons.outlined.Add
 import androidx.compose.material.icons.outlined.DataUsage
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableIntState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.toMutableStateList
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.vectorResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.viewmodel.compose.viewModel
 import com.android.settings.R
 import com.android.settings.network.SubscriptionInfoListViewModel
-import com.android.settings.network.SubscriptionUtil
 import com.android.settings.network.telephony.MobileNetworkUtils
 import com.android.settings.wifi.WifiPickerTrackerHelper
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -59,17 +55,13 @@
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.Category
-import com.android.settingslib.spa.widget.ui.SettingsIcon
 import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
-
-import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
-import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.flowOf
@@ -85,10 +77,8 @@
 object NetworkCellularGroupProvider : SettingsPageProvider {
     override val name = "NetworkCellularGroupProvider"
 
-    private lateinit var subscriptionViewModel: SubscriptionInfoListViewModel
     private val owner = createSettingsPage()
 
-    var selectableSubscriptionInfoList: List<SubscriptionInfo> = listOf()
     var defaultVoiceSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
     var defaultSmsSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
     var defaultDataSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -106,9 +96,6 @@
     @Composable
     override fun Page(arguments: Bundle?) {
         val context = LocalContext.current
-        var selectableSubscriptionInfoListRemember = remember {
-            mutableListOf<SubscriptionInfo>().toMutableStateList()
-        }
         var callsSelectedId = rememberSaveable {
             mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
         }
@@ -122,24 +109,24 @@
             mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
         }
 
-        subscriptionViewModel = SubscriptionInfoListViewModel(
-                context.applicationContext as Application)
+        val subscriptionViewModel = viewModel<SubscriptionInfoListViewModel>()
 
-        allOfFlows(context, subscriptionViewModel.selectableSubscriptionInfoListFlow)
-                .collectLatestWithLifecycle(LocalLifecycleOwner.current) {
-                    selectableSubscriptionInfoListRemember.clear()
-                    selectableSubscriptionInfoListRemember.addAll(selectableSubscriptionInfoList)
-                    callsSelectedId.intValue = defaultVoiceSubId
-                    textsSelectedId.intValue = defaultSmsSubId
-                    mobileDataSelectedId.intValue = defaultDataSubId
-                    nonDdsRemember.intValue = nonDds
-                }
+        remember {
+            allOfFlows(context, subscriptionViewModel.selectableSubscriptionInfoListFlow)
+        }.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
+            callsSelectedId.intValue = defaultVoiceSubId
+            textsSelectedId.intValue = defaultSmsSubId
+            mobileDataSelectedId.intValue = defaultDataSubId
+            nonDdsRemember.intValue = nonDds
+        }
 
-        PageImpl(selectableSubscriptionInfoListRemember,
-                callsSelectedId,
-                textsSelectedId,
-                mobileDataSelectedId,
-                nonDdsRemember)
+        PageImpl(
+            subscriptionViewModel.selectableSubscriptionInfoListFlow,
+            callsSelectedId,
+            textsSelectedId,
+            mobileDataSelectedId,
+            nonDdsRemember
+        )
     }
 
     private fun allOfFlows(context: Context,
@@ -152,13 +139,12 @@
                     NetworkCellularGroupProvider::refreshUiStates,
             ).flowOn(Dispatchers.Default)
 
-    fun refreshUiStates(
-            inputSelectableSubscriptionInfoList: List<SubscriptionInfo>,
-            inputDefaultVoiceSubId: Int,
-            inputDefaultSmsSubId: Int,
-            inputDefaultDateSubId: Int
-    ): Unit {
-        selectableSubscriptionInfoList = inputSelectableSubscriptionInfoList
+    private fun refreshUiStates(
+        selectableSubscriptionInfoList: List<SubscriptionInfo>,
+        inputDefaultVoiceSubId: Int,
+        inputDefaultSmsSubId: Int,
+        inputDefaultDateSubId: Int
+    ) {
         defaultVoiceSubId = inputDefaultVoiceSubId
         defaultSmsSubId = inputDefaultSmsSubId
         defaultDataSubId = inputDefaultDateSubId
@@ -178,25 +164,23 @@
 }
 
 @Composable
-fun PageImpl(selectableSubscriptionInfoList: List<SubscriptionInfo>,
-             defaultVoiceSubId: MutableIntState,
-             defaultSmsSubId: MutableIntState,
-             defaultDataSubId: MutableIntState,
-             nonDds: MutableIntState) {
-    val context = LocalContext.current
-    var activeSubscriptionInfoList: List<SubscriptionInfo> =
-            selectableSubscriptionInfoList.filter { subscriptionInfo ->
-                subscriptionInfo.simSlotIndex != -1
-            }
-    var subscriptionManager = context.getSystemService(SubscriptionManager::class.java)
+fun PageImpl(
+    selectableSubscriptionInfoListFlow: StateFlow<List<SubscriptionInfo>>,
+    defaultVoiceSubId: MutableIntState,
+    defaultSmsSubId: MutableIntState,
+    defaultDataSubId: MutableIntState,
+    nonDds: MutableIntState
+) {
+    val selectableSubscriptionInfoList by selectableSubscriptionInfoListFlow
+        .collectAsStateWithLifecycle(initialValue = emptyList())
+    val activeSubscriptionInfoList: List<SubscriptionInfo> =
+        selectableSubscriptionInfoList.filter { subscriptionInfo ->
+            subscriptionInfo.simSlotIndex != -1
+        }
 
     val stringSims = stringResource(R.string.provider_network_settings_title)
     RegularScaffold(title = stringSims) {
-        SimsSectionImpl(
-                context,
-                subscriptionManager,
-                selectableSubscriptionInfoList
-        )
+        SimsSection(selectableSubscriptionInfoList)
         PrimarySimSectionImpl(
                 activeSubscriptionInfoList,
                 defaultVoiceSubId,
@@ -208,56 +192,6 @@
 }
 
 @Composable
-fun SimsSectionImpl(
-        context: Context,
-        subscriptionManager: SubscriptionManager?,
-        subscriptionInfoList: List<SubscriptionInfo>
-) {
-    val coroutineScope = rememberCoroutineScope()
-    for (subInfo in subscriptionInfoList) {
-        val checked = rememberSaveable() {
-            mutableStateOf(false)
-        }
-        //TODO: Add the Restricted TwoTargetSwitchPreference in SPA
-        TwoTargetSwitchPreference(
-                object : SwitchPreferenceModel {
-                    override val title = subInfo.displayName.toString()
-                    override val summary = { subInfo.number }
-                    override val checked = {
-                        coroutineScope.launch {
-                            withContext(Dispatchers.Default) {
-                                checked.value = subscriptionManager?.isSubscriptionEnabled(
-                                    subInfo.subscriptionId)?:false
-                            }
-                        }
-                        checked.value
-                    }
-                    override val onCheckedChange = { newChecked: Boolean ->
-                        startToggleSubscriptionDialog(context, subInfo, newChecked)
-                    }
-                }
-        ) {
-            startMobileNetworkSettings(context, subInfo)
-        }
-    }
-
-    // + add sim
-    if (showEuiccSettings(context)) {
-        RestrictedPreference(
-                model = object : PreferenceModel {
-                    override val title = stringResource(id = R.string.mobile_network_list_add_more)
-                    override val icon = @Composable { SettingsIcon(Icons.Outlined.Add) }
-                    override val onClick = {
-                        startAddSimFlow(context)
-                    }
-                },
-                restrictions = Restrictions(keys =
-                        listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)),
-        )
-    }
-}
-
-@Composable
 fun PrimarySimImpl(
     subscriptionInfoList: List<SubscriptionInfo>,
     callsSelectedId: MutableIntState,
@@ -440,32 +374,6 @@
         ).map { SubscriptionManager.getDefaultDataSubscriptionId() }
                 .conflate().flowOn(Dispatchers.Default)
 
-private fun startToggleSubscriptionDialog(
-        context: Context,
-        subInfo: SubscriptionInfo,
-        newStatus: Boolean
-) {
-    SubscriptionUtil.startToggleSubscriptionDialogActivity(
-            context,
-            subInfo.subscriptionId,
-            newStatus
-    )
-}
-
-private fun startMobileNetworkSettings(context: Context, subInfo: SubscriptionInfo) {
-    MobileNetworkUtils.launchMobileNetworkSettings(context, subInfo)
-}
-
-private fun startAddSimFlow(context: Context) {
-    val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION)
-    intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true)
-    context.startActivity(intent)
-}
-
-private fun showEuiccSettings(context: Context): Boolean {
-    return MobileNetworkUtils.showEuiccSettings(context)
-}
-
 suspend fun setDefaultVoice(
     subscriptionManager: SubscriptionManager?,
     subId: Int
diff --git a/src/com/android/settings/spa/network/SimsSection.kt b/src/com/android/settings/spa/network/SimsSection.kt
new file mode 100644
index 0000000..cc8a5d1
--- /dev/null
+++ b/src/com/android/settings/spa/network/SimsSection.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.spa.network
+
+import android.content.Context
+import android.content.Intent
+import android.os.UserManager
+import android.telephony.SubscriptionInfo
+import android.telephony.euicc.EuiccManager
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Add
+import androidx.compose.runtime.Composable
+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.SubscriptionUtil
+import com.android.settings.network.telephony.MobileNetworkUtils
+import com.android.settings.network.telephony.isSubscriptionEnabledFlow
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
+
+@Composable
+fun SimsSection(subscriptionInfoList: List<SubscriptionInfo>) {
+    Column {
+        for (subInfo in subscriptionInfoList) {
+            SimPreference(subInfo)
+        }
+
+        AddSim()
+    }
+}
+
+@Composable
+private fun SimPreference(subInfo: SubscriptionInfo) {
+    val context = LocalContext.current
+    val checked = remember(subInfo.subscriptionId) {
+        context.isSubscriptionEnabledFlow(subInfo.subscriptionId)
+    }.collectAsStateWithLifecycle(initialValue = false)
+    //TODO: Add the Restricted TwoTargetSwitchPreference in SPA
+    TwoTargetSwitchPreference(
+        object : SwitchPreferenceModel {
+            override val title = subInfo.displayName.toString()
+            override val summary = { subInfo.number }
+            override val checked = { checked.value }
+            override val onCheckedChange = { newChecked: Boolean ->
+                SubscriptionUtil.startToggleSubscriptionDialogActivity(
+                    context,
+                    subInfo.subscriptionId,
+                    newChecked,
+                )
+            }
+        }
+    ) {
+        MobileNetworkUtils.launchMobileNetworkSettings(context, subInfo)
+    }
+}
+
+@Composable
+private fun AddSim() {
+    val context = LocalContext.current
+    if (remember { MobileNetworkUtils.showEuiccSettings(context) }) {
+        RestrictedPreference(
+            model = object : PreferenceModel {
+                override val title = stringResource(id = R.string.mobile_network_list_add_more)
+                override val icon = @Composable { SettingsIcon(Icons.Outlined.Add) }
+                override val onClick = { startAddSimFlow(context) }
+            },
+            restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)),
+        )
+    }
+}
+
+private fun startAddSimFlow(context: Context) {
+    val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION)
+    intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true)
+    context.startActivity(intent)
+}
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 2887134..a59bf93 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
@@ -33,6 +33,7 @@
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
 
 @RunWith(AndroidJUnit4::class)
 class SubscriptionRepositoryTest {
@@ -50,6 +51,17 @@
     }
 
     @Test
+    fun isSubscriptionEnabledFlow() = runBlocking {
+        mockSubscriptionManager.stub {
+            on { isSubscriptionEnabled(SUB_ID) } doReturn true
+        }
+
+        val isEnabled = context.isSubscriptionEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
+
+        assertThat(isEnabled).isTrue()
+    }
+
+    @Test
     fun subscriptionsChangedFlow_hasInitialValue() = runBlocking {
         val initialValue = context.subscriptionsChangedFlow().firstWithTimeoutOrNull()
 
@@ -67,4 +79,8 @@
 
         assertThat(listDeferred.await()).hasSize(2)
     }
+
+    private companion object {
+        const val SUB_ID = 1
+    }
 }