Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bec4c957 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Create isSubscriptionEnabledFlow

To better display the isEnable checked state.

Bug: 318310357
Test: manual - on Mobile Settings
Test: unit test
Change-Id: Ia595e7445650ad67883f1e7c1a0662cb826565ea
parent 29e984b1
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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)!!

+40 −132
Original line number Diff line number Diff line
@@ -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>()

        remember {
            allOfFlows(context, subscriptionViewModel.selectableSubscriptionInfoListFlow)
                .collectLatestWithLifecycle(LocalLifecycleOwner.current) {
                    selectableSubscriptionInfoListRemember.clear()
                    selectableSubscriptionInfoListRemember.addAll(selectableSubscriptionInfoList)
        }.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
            callsSelectedId.intValue = defaultVoiceSubId
            textsSelectedId.intValue = defaultSmsSubId
            mobileDataSelectedId.intValue = defaultDataSubId
            nonDdsRemember.intValue = nonDds
        }

        PageImpl(selectableSubscriptionInfoListRemember,
        PageImpl(
            subscriptionViewModel.selectableSubscriptionInfoListFlow,
            callsSelectedId,
            textsSelectedId,
            mobileDataSelectedId,
                nonDdsRemember)
            nonDdsRemember
        )
    }

    private fun allOfFlows(context: Context,
@@ -152,13 +139,12 @@ object NetworkCellularGroupProvider : SettingsPageProvider {
                    NetworkCellularGroupProvider::refreshUiStates,
            ).flowOn(Dispatchers.Default)

    fun refreshUiStates(
            inputSelectableSubscriptionInfoList: List<SubscriptionInfo>,
    private fun refreshUiStates(
        selectableSubscriptionInfoList: List<SubscriptionInfo>,
        inputDefaultVoiceSubId: Int,
        inputDefaultSmsSubId: Int,
        inputDefaultDateSubId: Int
    ): Unit {
        selectableSubscriptionInfoList = inputSelectableSubscriptionInfoList
    ) {
        defaultVoiceSubId = inputDefaultVoiceSubId
        defaultSmsSubId = inputDefaultSmsSubId
        defaultDataSubId = inputDefaultDateSubId
@@ -178,25 +164,23 @@ object NetworkCellularGroupProvider : SettingsPageProvider {
}

@Composable
fun PageImpl(selectableSubscriptionInfoList: List<SubscriptionInfo>,
fun PageImpl(
    selectableSubscriptionInfoListFlow: StateFlow<List<SubscriptionInfo>>,
    defaultVoiceSubId: MutableIntState,
    defaultSmsSubId: MutableIntState,
    defaultDataSubId: MutableIntState,
             nonDds: MutableIntState) {
    val context = LocalContext.current
    var activeSubscriptionInfoList: List<SubscriptionInfo> =
    nonDds: MutableIntState
) {
    val selectableSubscriptionInfoList by selectableSubscriptionInfoListFlow
        .collectAsStateWithLifecycle(initialValue = emptyList())
    val activeSubscriptionInfoList: List<SubscriptionInfo> =
        selectableSubscriptionInfoList.filter { subscriptionInfo ->
            subscriptionInfo.simSlotIndex != -1
        }
    var subscriptionManager = context.getSystemService(SubscriptionManager::class.java)

    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
+98 −0
Original line number Diff line number Diff line
/*
 * 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)
}
+16 −0
Original line number Diff line number Diff line
@@ -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
    }
}