Refactor AutomaticDataSwitchingPreference

Split into AutomaticDataSwitchingPreference.kt, and use
isMobileDataPolicyEnabledFlow.

Bug: 329061940
Test: manual - Set Automatic data switching
Test: unit test
Change-Id: I878ed70328307c0a5dba6dfb461ff5a85efbcf88
diff --git a/src/com/android/settings/network/SimOnboardingService.kt b/src/com/android/settings/network/SimOnboardingService.kt
index 2ec1ad3..f99a2b9 100644
--- a/src/com/android/settings/network/SimOnboardingService.kt
+++ b/src/com/android/settings/network/SimOnboardingService.kt
@@ -24,6 +24,7 @@
 import android.telephony.UiccSlotInfo
 import android.util.Log
 import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
+import com.android.settings.network.telephony.TelephonyRepository
 import com.android.settings.sim.SimActivationNotifier
 import com.android.settings.spa.network.setAutomaticData
 import com.android.settings.spa.network.setDefaultData
@@ -31,6 +32,7 @@
 import com.android.settings.spa.network.setDefaultVoice
 import com.android.settingslib.utils.ThreadUtils
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.withContext
 
 class SimOnboardingService {
@@ -46,7 +48,7 @@
     var targetPrimarySimCalls: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
     var targetPrimarySimTexts: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
     var targetPrimarySimMobileData: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
-    var targetPrimarySimAutoDataSwitch: Boolean = false
+    val targetPrimarySimAutoDataSwitch = MutableStateFlow(false)
     var targetNonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
         get() {
             if(targetPrimarySimMobileData == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
@@ -349,19 +351,10 @@
                     null,
                     targetPrimarySimMobileData
                 )
-
-                var nonDds = targetNonDds
-                Log.d(
-                    TAG,
-                    "setAutomaticData: targetNonDds: $nonDds," +
-                            " targetPrimarySimAutoDataSwitch: $targetPrimarySimAutoDataSwitch"
+                TelephonyRepository(context).setAutomaticData(
+                    targetNonDds,
+                    targetPrimarySimAutoDataSwitch.value
                 )
-                if (nonDds != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                    val telephonyManagerForNonDds: TelephonyManager? =
-                        context.getSystemService(TelephonyManager::class.java)
-                            ?.createForSubscriptionId(nonDds)
-                    setAutomaticData(telephonyManagerForNonDds, targetPrimarySimAutoDataSwitch)
-                }
             }
             // no next action, send finish
             callback(CallbackType.CALLBACK_FINISH)
diff --git a/src/com/android/settings/network/telephony/TelephonyRepository.kt b/src/com/android/settings/network/telephony/TelephonyRepository.kt
index 678aaac..18af621 100644
--- a/src/com/android/settings/network/telephony/TelephonyRepository.kt
+++ b/src/com/android/settings/network/telephony/TelephonyRepository.kt
@@ -17,8 +17,10 @@
 package com.android.settings.network.telephony
 
 import android.content.Context
+import android.telephony.SubscriptionManager
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
+import android.util.Log
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.ProducerScope
@@ -26,15 +28,51 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+class TelephonyRepository(
+    private val context: Context,
+    private val subscriptionsChangedFlow: Flow<Unit> = context.subscriptionsChangedFlow(),
+) {
+    fun isMobileDataPolicyEnabledFlow(
+        subId: Int,
+        @TelephonyManager.MobileDataPolicy policy: Int,
+    ): Flow<Boolean> {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
+
+        val telephonyManager = context.telephonyManager(subId)
+
+        return subscriptionsChangedFlow.map {
+            telephonyManager.isMobileDataPolicyEnabled(policy)
+                .also { Log.d(TAG, "[$subId] isMobileDataPolicyEnabled($policy): $it") }
+        }.conflate().flowOn(Dispatchers.Default)
+    }
+
+    fun setMobileDataPolicyEnabled(
+        subId: Int,
+        @TelephonyManager.MobileDataPolicy policy: Int,
+        enabled: Boolean,
+    ) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) return
+
+        val telephonyManager = context.telephonyManager(subId)
+        Log.d(TAG, "[$subId] setMobileDataPolicyEnabled($policy): $enabled")
+        telephonyManager.setMobileDataPolicyEnabled(policy, enabled)
+    }
+
+    private companion object {
+        private const val TAG = "TelephonyRepository"
+    }
+}
 
 /** Creates an instance of a cold Flow for Telephony callback of given [subId]. */
 fun <T> Context.telephonyCallbackFlow(
     subId: Int,
     block: ProducerScope<T>.() -> TelephonyCallback,
 ): Flow<T> = callbackFlow {
-    val telephonyManager = getSystemService(TelephonyManager::class.java)!!
-        .createForSubscriptionId(subId)
+    val telephonyManager = telephonyManager(subId)
 
     val callback = block()
 
@@ -42,3 +80,7 @@
 
     awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
 }.conflate().flowOn(Dispatchers.Default)
+
+fun Context.telephonyManager(subId: Int): TelephonyManager =
+    getSystemService(TelephonyManager::class.java)!!
+        .createForSubscriptionId(subId)
diff --git a/src/com/android/settings/spa/network/AutomaticDataSwitchingPreference.kt b/src/com/android/settings/spa/network/AutomaticDataSwitchingPreference.kt
new file mode 100644
index 0000000..824a935
--- /dev/null
+++ b/src/com/android/settings/spa/network/AutomaticDataSwitchingPreference.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.telephony.TelephonyManager
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.network.telephony.TelephonyRepository
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+@Composable
+fun AutomaticDataSwitchingPreference(
+    isAutoDataEnabled: () -> Boolean?,
+    setAutoDataEnabled: (newEnabled: Boolean) -> Unit,
+) {
+    val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg)
+    val coroutineScope = rememberCoroutineScope()
+    SwitchPreference(
+        object : SwitchPreferenceModel {
+            override val title = stringResource(id = R.string.primary_sim_automatic_data_title)
+            override val summary = { autoDataSummary }
+            override val checked = { isAutoDataEnabled() }
+            override val onCheckedChange: (Boolean) -> Unit = { newEnabled ->
+                coroutineScope.launch(Dispatchers.Default) {
+                    setAutoDataEnabled(newEnabled)
+                }
+            }
+        }
+    )
+}
+
+fun TelephonyRepository.setAutomaticData(subId: Int, newEnabled: Boolean) {
+    setMobileDataPolicyEnabled(
+        subId = subId,
+        policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
+        enabled = newEnabled,
+    )
+    //TODO: setup backup calling
+}
diff --git a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
index 5a2a394..bc5a4b7 100644
--- a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
+++ b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
@@ -30,7 +30,6 @@
 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
@@ -44,6 +43,7 @@
 import com.android.settings.R
 import com.android.settings.network.SubscriptionInfoListViewModel
 import com.android.settings.network.telephony.MobileNetworkUtils
+import com.android.settings.network.telephony.TelephonyRepository
 import com.android.settings.spa.network.PrimarySimRepository.PrimarySimInfo
 import com.android.settings.wifi.WifiPickerTrackerHelper
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -53,8 +53,6 @@
 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
 import com.android.settingslib.spa.widget.preference.Preference
 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.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.Category
 import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
@@ -193,7 +191,6 @@
     callsSelectedId: MutableIntState,
     textsSelectedId: MutableIntState,
     mobileDataSelectedId: MutableIntState,
-    nonDds: MutableIntState,
     subscriptionManager: SubscriptionManager? =
         LocalContext.current.getSystemService(SubscriptionManager::class.java),
     coroutineScope: CoroutineScope = rememberCoroutineScope(),
@@ -223,23 +220,9 @@
             )
         }
     },
-    actionSetAutoDataSwitch: (Boolean) -> Unit = { newState ->
-        coroutineScope.launch {
-            val telephonyManagerForNonDds: TelephonyManager? =
-                context.getSystemService(TelephonyManager::class.java)
-                    ?.createForSubscriptionId(nonDds.intValue)
-            Log.d(NetworkCellularGroupProvider.name, "NonDds:${nonDds.intValue} setAutomaticData")
-            setAutomaticData(telephonyManagerForNonDds, newState)
-        }
-    },
+    isAutoDataEnabled: () -> Boolean?,
+    setAutoDataEnabled: (newEnabled: Boolean) -> Unit,
 ) {
-    val telephonyManagerForNonDds: TelephonyManager? =
-            context.getSystemService(TelephonyManager::class.java)
-                    ?.createForSubscriptionId(nonDds.intValue)
-    val automaticDataChecked = rememberSaveable() {
-        mutableStateOf(false)
-    }
-
     CreatePrimarySimListPreference(
         stringResource(id = R.string.primary_sim_calls_title),
         primarySimInfo.callsAndSmsList,
@@ -262,31 +245,7 @@
         actionSetMobileData
     )
 
-    val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title)
-    val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg)
-    SwitchPreference(
-        object : SwitchPreferenceModel {
-            override val title = autoDataTitle
-            override val summary = { autoDataSummary }
-            override val checked = {
-                if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                    coroutineScope.launch {
-                        automaticDataChecked.value = getAutomaticData(telephonyManagerForNonDds)
-                        Log.d(
-                            NetworkCellularGroupProvider.name,
-                            "NonDds:${nonDds.intValue}" +
-                                "getAutomaticData:${automaticDataChecked.value}"
-                        )
-                    }
-                }
-                automaticDataChecked.value
-            }
-            override val onCheckedChange: ((Boolean) -> Unit)? = {
-                automaticDataChecked.value = it
-                actionSetAutoDataSwitch(it)
-            }
-        }
-    )
+    AutomaticDataSwitchingPreference(isAutoDataEnabled, setAutoDataEnabled)
 }
 
 @Composable
@@ -308,12 +267,21 @@
     }.collectAsStateWithLifecycle(initialValue = null).value ?: return
 
     Category(title = stringResource(id = R.string.primary_sim_title)) {
+        val isAutoDataEnabled by remember(nonDds.intValue) {
+            TelephonyRepository(context).isMobileDataPolicyEnabledFlow(
+                subId = nonDds.intValue,
+                policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH
+            )
+        }.collectAsStateWithLifecycle(initialValue = null)
         PrimarySimImpl(
             primarySimInfo,
             callsSelectedId,
             textsSelectedId,
             mobileDataSelectedId,
-            nonDds
+            isAutoDataEnabled = { isAutoDataEnabled },
+            setAutoDataEnabled = { newEnabled ->
+                TelephonyRepository(context).setAutomaticData(nonDds.intValue, newEnabled)
+            },
         )
     }
 }
@@ -381,23 +349,3 @@
             wifiPickerTrackerHelper.setCarrierNetworkEnabled(true)
         }
     }
-
-suspend fun getAutomaticData(telephonyManagerForNonDds: TelephonyManager?): Boolean =
-    withContext(Dispatchers.Default) {
-        telephonyManagerForNonDds != null
-            && telephonyManagerForNonDds.isMobileDataPolicyEnabled(
-            TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
-    }
-
-suspend fun setAutomaticData(telephonyManager: TelephonyManager?, newState: Boolean): Unit =
-    withContext(Dispatchers.Default) {
-        Log.d(
-            NetworkCellularGroupProvider.name,
-            "setAutomaticData: MOBILE_DATA_POLICY_AUTO_DATA_SWITCH as $newState"
-        )
-        telephonyManager?.setMobileDataPolicyEnabled(
-            TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
-            newState
-        )
-        //TODO: setup backup calling
-    }
\ No newline at end of file
diff --git a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
index a8c0575..4fad332 100644
--- a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
+++ b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
@@ -23,6 +23,7 @@
 import androidx.compose.material.icons.outlined.SignalCellularAlt
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableIntState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
@@ -75,9 +76,6 @@
         val mobileDataSelectedId = rememberSaveable {
             mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
         }
-        val nonDdsRemember = rememberSaveable {
-            mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
-        }
 
         Column(Modifier.padding(SettingsDimension.itemPadding)) {
             SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg))
@@ -94,12 +92,14 @@
         callsSelectedId.intValue = onboardingService.targetPrimarySimCalls
         textsSelectedId.intValue = onboardingService.targetPrimarySimTexts
         mobileDataSelectedId.intValue = onboardingService.targetPrimarySimMobileData
+        val isAutoDataEnabled by
+            onboardingService.targetPrimarySimAutoDataSwitch
+                .collectAsStateWithLifecycle(initialValue = null)
         PrimarySimImpl(
             primarySimInfo = primarySimInfo,
             callsSelectedId = callsSelectedId,
             textsSelectedId = textsSelectedId,
             mobileDataSelectedId = mobileDataSelectedId,
-            nonDds = nonDdsRemember,
             actionSetCalls = {
                 callsSelectedId.intValue = it
                 onboardingService.targetPrimarySimCalls = it},
@@ -109,8 +109,10 @@
             actionSetMobileData = {
                 mobileDataSelectedId.intValue = it
                 onboardingService.targetPrimarySimMobileData = it},
-            actionSetAutoDataSwitch = {
-                onboardingService.targetPrimarySimAutoDataSwitch = it},
+            isAutoDataEnabled = { isAutoDataEnabled },
+            setAutoDataEnabled = { newEnabled ->
+                onboardingService.targetPrimarySimAutoDataSwitch.value = newEnabled
+            },
         )
     }
 }
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt
index b7e1dcc..ce27ed4 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt
@@ -17,12 +17,14 @@
 package com.android.settings.network.telephony
 
 import android.content.Context
+import android.telephony.SubscriptionManager
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -31,6 +33,7 @@
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
 import org.mockito.kotlin.verify
 
 @RunWith(AndroidJUnit4::class)
@@ -48,6 +51,46 @@
         on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
     }
 
+    private val repository = TelephonyRepository(context, flowOf(Unit))
+
+    @Test
+    fun isMobileDataPolicyEnabledFlow_invalidSub_returnFalse() = runBlocking {
+        val flow = repository.isMobileDataPolicyEnabledFlow(
+            subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+            policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
+        )
+
+        assertThat(flow.firstWithTimeoutOrNull()).isFalse()
+    }
+
+    @Test
+    fun isMobileDataPolicyEnabledFlow_validSub_returnPolicyState() = runBlocking {
+        mockTelephonyManager.stub {
+            on {
+                isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
+            } doReturn true
+        }
+
+        val flow = repository.isMobileDataPolicyEnabledFlow(
+            subId = SUB_ID,
+            policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
+        )
+
+        assertThat(flow.firstWithTimeoutOrNull()).isTrue()
+    }
+
+    @Test
+    fun setMobileDataPolicyEnabled() = runBlocking {
+        repository.setMobileDataPolicyEnabled(
+            subId = SUB_ID,
+            policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
+            enabled = true
+        )
+
+        verify(mockTelephonyManager)
+            .setMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true)
+    }
+
     @Test
     fun telephonyCallbackFlow_callbackRegistered() = runBlocking {
         val flow = context.telephonyCallbackFlow<Unit>(SUB_ID) {