From bfd8a517be2298d2c4a83fc159ee23d99094a30b Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 18 Mar 2024 17:47:51 +0800 Subject: [PATCH] Refactor AutomaticDataSwitchingPreference Split into AutomaticDataSwitchingPreference.kt, and use isMobileDataPolicyEnabledFlow. Bug: 329061940 Test: manual - Set Automatic data switching Test: unit test Change-Id: I878ed70328307c0a5dba6dfb461ff5a85efbcf88 --- .../settings/network/SimOnboardingService.kt | 19 ++--- .../network/telephony/TelephonyRepository.kt | 46 ++++++++++- .../AutomaticDataSwitchingPreference.kt | 58 ++++++++++++++ .../network/NetworkCellularGroupProvider.kt | 80 ++++--------------- .../spa/network/SimOnboardingPrimarySim.kt | 14 ++-- .../telephony/TelephonyRepositoryTest.kt | 43 ++++++++++ 6 files changed, 173 insertions(+), 87 deletions(-) create mode 100644 src/com/android/settings/spa/network/AutomaticDataSwitchingPreference.kt diff --git a/src/com/android/settings/network/SimOnboardingService.kt b/src/com/android/settings/network/SimOnboardingService.kt index 2ec1ad3ae43..f99a2b982d4 100644 --- a/src/com/android/settings/network/SimOnboardingService.kt +++ b/src/com/android/settings/network/SimOnboardingService.kt @@ -24,6 +24,7 @@ import android.telephony.UiccCardInfo 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.setDefaultSms 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 @@ class SimOnboardingService { 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 @@ class SimOnboardingService { 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 678aaac0ee4..18af621564e 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.channels.awaitClose 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 = context.subscriptionsChangedFlow(), +) { + fun isMobileDataPolicyEnabledFlow( + subId: Int, + @TelephonyManager.MobileDataPolicy policy: Int, + ): Flow { + 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 Context.telephonyCallbackFlow( subId: Int, block: ProducerScope.() -> TelephonyCallback, ): Flow = callbackFlow { - val telephonyManager = getSystemService(TelephonyManager::class.java)!! - .createForSubscriptionId(subId) + val telephonyManager = telephonyManager(subId) val callback = block() @@ -42,3 +80,7 @@ fun Context.telephonyCallbackFlow( 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 00000000000..824a93532b2 --- /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 5a2a3947621..bc5a4b7fb88 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.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 @@ -44,6 +43,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel 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.compose.navigator 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 @@ fun PrimarySimImpl( callsSelectedId: MutableIntState, textsSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState, - nonDds: MutableIntState, subscriptionManager: SubscriptionManager? = LocalContext.current.getSystemService(SubscriptionManager::class.java), coroutineScope: CoroutineScope = rememberCoroutineScope(), @@ -223,23 +220,9 @@ fun PrimarySimImpl( ) } }, - 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 @@ fun PrimarySimImpl( 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 @@ fun PrimarySimSectionImpl( }.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 @@ suspend fun setDefaultData( 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 a8c0575e3e8..4fad3325b11 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.Icons 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 @@ fun SimOnboardingPrimarySimImpl( 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 @@ fun SimOnboardingPrimarySimImpl( 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 @@ fun SimOnboardingPrimarySimImpl( 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 b7e1dcc37b5..ce27ed4c4f8 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.doAnswer 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 @@ class TelephonyRepositoryTest { 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(SUB_ID) {