Create isSubscriptionEnabledFlow

To better display the isEnable checked state.

Bug: 318310357
Test: manual - on Mobile Settings
Test: unit test
Change-Id: Ia595e7445650ad67883f1e7c1a0662cb826565ea
This commit is contained in:
Chaohui Wang
2024-02-29 14:52:36 +08:00
parent 29e984b13a
commit bec4c9573e
4 changed files with 161 additions and 132 deletions

View File

@@ -25,10 +25,17 @@ import kotlinx.coroutines.channels.awaitClose
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)!!

View File

@@ -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.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.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 @@ import kotlinx.coroutines.withContext
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 @@ object NetworkCellularGroupProvider : SettingsPageProvider {
@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 @@ object NetworkCellularGroupProvider : SettingsPageProvider {
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 @@ object NetworkCellularGroupProvider : SettingsPageProvider {
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 @@ object NetworkCellularGroupProvider : SettingsPageProvider {
}
@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,
@@ -207,56 +191,6 @@ fun PageImpl(selectableSubscriptionInfoList: List<SubscriptionInfo>,
}
}
@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>,
@@ -440,32 +374,6 @@ private fun Context.defaultDefaultDataSubscriptionFlow(): Flow<Int> =
).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

View File

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

View File

@@ -33,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
@RunWith(AndroidJUnit4::class)
class SubscriptionRepositoryTest {
@@ -49,6 +50,17 @@ class SubscriptionRepositoryTest {
on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager
}
@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 @@ class SubscriptionRepositoryTest {
assertThat(listDeferred.await()).hasSize(2)
}
private companion object {
const val SUB_ID = 1
}
}