SIM Onboarding flow completed

- Add the setup flow for switching sim and rename and setup primary sim
  items
- Add the bottom sheet and progress dialog.
Bug: 318310357
Bug: 298898436
Bug: 298891941
Test: build pass. Will upload another cl for testing

Change-Id: Ie9680f0a67afe453c1449c0f2b59e98fd627e215
This commit is contained in:
songferngwang
2024-02-05 23:46:13 +00:00
parent 5fa73d14ab
commit f1ea484425
14 changed files with 810 additions and 193 deletions

View File

@@ -802,6 +802,11 @@
</intent-filter>
</activity>
<activity android:name=".network.SimOnboardingActivity"
android:exported="false"
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
android:theme="@style/Theme.SpaLib.BottomSheetDialog"/>
<activity android:name=".network.telephony.ToggleSubscriptionDialogActivity"
android:exported="false"
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"

View File

@@ -0,0 +1,430 @@
/*
* 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.network
import android.app.ProgressDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.telephony.SubscriptionManager
import android.util.Log
import android.view.MotionEvent
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.SignalCellularAlt
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import com.android.settings.R
import com.android.settings.SidecarFragment
import com.android.settings.network.telephony.SubscriptionActionDialogActivity
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.network.SimOnboardingPageProvider.getRoute
import com.android.settingslib.spa.SpaBaseDialogActivity
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spa.widget.ui.SettingsTitle
import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.launch
class SimOnboardingActivity : SpaBaseDialogActivity() {
lateinit var scope: CoroutineScope
lateinit var showBottomSheet: MutableState<Boolean>
lateinit var showError: MutableState<Boolean>
lateinit var showDialog: MutableState<Boolean>
private var switchToEuiccSubscriptionSidecar: SwitchToEuiccSubscriptionSidecar? = null
private var switchToRemovableSlotSidecar: SwitchToRemovableSlotSidecar? = null
private var enableMultiSimSidecar: EnableMultiSimSidecar? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!this.userManager.isAdminUser) {
Log.e(TAG, "It is not the admin user. Unable to toggle subscription.")
finish()
return
}
var targetSubId = intent.getIntExtra(SUB_ID,SubscriptionManager.INVALID_SUBSCRIPTION_ID)
initServiceData(this, targetSubId, callbackListener)
if (!onboardingService.isUsableTargetSubscriptionId) {
Log.e(TAG, "The subscription id is not usable.")
finish()
return
}
switchToEuiccSubscriptionSidecar = SwitchToEuiccSubscriptionSidecar.get(fragmentManager)
switchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(fragmentManager)
enableMultiSimSidecar = EnableMultiSimSidecar.get(fragmentManager)
setContent {
Content()
}
}
override fun finish() {
setProgressDialog(false)
onboardingService.clear()
super.finish()
}
var callbackListener: (Int) -> Unit = {
Log.d(TAG, "Receive the CALLBACK: $it")
when (it) {
CALLBACK_ERROR -> {
setProgressDialog(false)
showError.value = true
}
CALLBACK_ONBOARDING_COMPLETE -> {
showBottomSheet.value = false
setProgressDialog(true)
scope.launch {
// TODO: refactor the Sidecar
// start to activate the sim
startSimSwitching()
}
}
CALLBACK_SETUP_NAME -> {
scope.launch {
onboardingService.startSetupName()
}
}
CALLBACK_SETUP_PRIMARY_SIM -> {
scope.launch {
onboardingService.startSetupPrimarySim(this@SimOnboardingActivity)
}
}
CALLBACK_FINISH -> {
finish()
}
}
}
fun setProgressDialog(enable: Boolean) {
showDialog.value = enable
val progressState = if (enable) {
SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
} else {
SubscriptionActionDialogActivity.PROGRESS_IS_NOT_SHOWING
}
setProgressState(progressState)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
showBottomSheet = remember { mutableStateOf(true) }
showError = remember { mutableStateOf(false) }
showDialog = remember { mutableStateOf(false) }
scope = rememberCoroutineScope()
registerSidecarReceiverFlow()
if(showError.value){
// show error
return
}
if (showBottomSheet.value) {
var sheetState = rememberModalBottomSheetState()
BottomSheetImpl(
sheetState = sheetState,
nextAction = {
// TODO: if the phone is SS mode and the isDsdsConditionSatisfied is true, then
// enable the DSDS mode.
// case#1: the device need the reboot after enabling DSDS. Showing the confirm
// dialog to user whether reboot device or not.
// case#2: The device don't need the reboot. Enabling DSDS and then showing
// the SIM onboarding UI.
// case#2
val route = getRoute(onboardingService.targetSubId)
startSpaActivity(route)
},
cancelAction = { finish() },
)
} else {
ProgressDialogImpl()
}
}
@Composable
fun ProgressDialogImpl() {
// TODO: 1. Create the SPA's ProgressDialog and using SPA's widget
val dialog: ProgressDialog = object : ProgressDialog(this) {
override fun onTouchEvent(event: MotionEvent): Boolean {
return true
}
}
dialog.setMessage(
stringResource(
R.string.sim_onboarding_progressbar_turning_sim_on,
onboardingService.targetSubInfo?.displayName ?: ""
)
)
dialog.setCancelable(false)
if(showDialog.value) {
dialog.show()
}
}
@Composable
fun registerSidecarReceiverFlow(){
switchToEuiccSubscriptionSidecar?.sidecarReceiverFlow()
?.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
onStateChange(it)
}
switchToRemovableSlotSidecar?.sidecarReceiverFlow()
?.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
onStateChange(it)
}
enableMultiSimSidecar?.sidecarReceiverFlow()
?.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
onStateChange(it)
}
}
fun SidecarFragment.sidecarReceiverFlow(): Flow<SidecarFragment> = callbackFlow {
val broadcastReceiver = SidecarFragment.Listener {
Log.d(TAG, "onReceive: $it")
trySend(it)
}
addListener(broadcastReceiver)
awaitClose { removeListener(broadcastReceiver) }
}.catch { e ->
Log.e(TAG, "Error while sidecarReceiverFlow", e)
}.conflate()
fun startSimSwitching(){
Log.d(TAG, "startSimSwitching:")
var targetSubInfo = onboardingService.targetSubInfo
targetSubInfo?.let {
var removedSubInfo = onboardingService.getRemovedSim()
if (targetSubInfo.isEmbedded) {
switchToEuiccSubscriptionSidecar!!.run(
targetSubInfo.subscriptionId,
UiccSlotUtil.INVALID_PORT_ID,
removedSubInfo
)
return@let
}
switchToRemovableSlotSidecar!!.run(
UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID,
removedSubInfo
)
} ?: run {
Log.e(TAG, "no target subInfo in onboardingService")
finish()
}
}
fun onStateChange(fragment: SidecarFragment?) {
if (fragment === switchToEuiccSubscriptionSidecar) {
handleSwitchToEuiccSubscriptionSidecarStateChange()
} else if (fragment === switchToRemovableSlotSidecar) {
handleSwitchToRemovableSlotSidecarStateChange()
} else if (fragment === enableMultiSimSidecar) {
handleEnableMultiSimSidecarStateChange()
}
}
fun handleSwitchToEuiccSubscriptionSidecarStateChange() {
when (switchToEuiccSubscriptionSidecar!!.state) {
SidecarFragment.State.SUCCESS -> {
Log.i(TAG, "Successfully enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset()
callbackListener(CALLBACK_SETUP_NAME)
}
SidecarFragment.State.ERROR -> {
Log.i(TAG, "Failed to enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset()
callbackListener(CALLBACK_ERROR)
// TODO: showErrorDialog and using privileged_action_disable_fail_title and
// privileged_action_disable_fail_text
}
}
}
fun handleSwitchToRemovableSlotSidecarStateChange() {
when (switchToRemovableSlotSidecar!!.state) {
SidecarFragment.State.SUCCESS -> {
Log.i(TAG, "Successfully switched to removable slot.")
switchToRemovableSlotSidecar!!.reset()
onboardingService.handleTogglePsimAction()
callbackListener(CALLBACK_SETUP_NAME)
}
SidecarFragment.State.ERROR -> {
Log.e(TAG, "Failed switching to removable slot.")
switchToRemovableSlotSidecar!!.reset()
callbackListener(CALLBACK_ERROR)
// TODO: showErrorDialog and using sim_action_enable_sim_fail_title and
// sim_action_enable_sim_fail_text
}
}
}
fun handleEnableMultiSimSidecarStateChange() {
when (enableMultiSimSidecar!!.state) {
SidecarFragment.State.SUCCESS -> {
enableMultiSimSidecar!!.reset()
Log.i(TAG, "Successfully switched to DSDS without reboot.")
handleEnableSubscriptionAfterEnablingDsds()
}
SidecarFragment.State.ERROR -> {
enableMultiSimSidecar!!.reset()
Log.i(TAG, "Failed to switch to DSDS without rebooting.")
callbackListener(CALLBACK_ERROR)
// TODO: showErrorDialog and using dsds_activation_failure_title and
// dsds_activation_failure_body_msg2
}
}
}
fun handleEnableSubscriptionAfterEnablingDsds() {
var targetSubInfo = onboardingService.targetSubInfo
if (targetSubInfo?.isEmbedded == true) {
Log.i(TAG,
"DSDS enabled, start to enable profile: " + targetSubInfo.getSubscriptionId()
)
// For eSIM operations, we simply switch to the selected eSIM profile.
switchToEuiccSubscriptionSidecar!!.run(
targetSubInfo.subscriptionId,
UiccSlotUtil.INVALID_PORT_ID,
null
)
return
}
Log.i(TAG, "DSDS enabled, start to enable pSIM profile.")
onboardingService.handleTogglePsimAction()
callbackListener(CALLBACK_FINISH)
}
@Composable
fun BottomSheetBody(nextAction: () -> Unit) {
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(bottom = SettingsDimension.itemPaddingVertical)) {
Icon(
imageVector = Icons.Outlined.SignalCellularAlt,
contentDescription = null,
modifier = Modifier
.size(SettingsDimension.iconLarge),
tint = MaterialTheme.colorScheme.primary,
)
SettingsTitle(stringResource(R.string.sim_onboarding_bottomsheets_title))
Column(Modifier.padding(SettingsDimension.itemPadding)) {
Text(
text = stringResource(R.string.sim_onboarding_bottomsheets_msg),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center
)
}
Button(onClick = nextAction) {
Text(stringResource(R.string.sim_onboarding_setup))
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomSheetImpl(
sheetState: SheetState,
nextAction: () -> Unit,
cancelAction: () -> Unit,
) {
ModalBottomSheet(
onDismissRequest = cancelAction,
sheetState = sheetState,
) {
BottomSheetBody(nextAction = nextAction)
}
LaunchedEffect(Unit) {
sheetState.show()
}
}
fun setProgressState(state: Int) {
val prefs = getSharedPreferences(
SubscriptionActionDialogActivity.SIM_ACTION_DIALOG_PREFS,
MODE_PRIVATE
)
prefs.edit().putInt(SubscriptionActionDialogActivity.KEY_PROGRESS_STATE, state).apply()
Log.i(TAG, "setProgressState:$state")
}
fun initServiceData(context: Context,targetSubId: Int, callback:(Int)->Unit) {
onboardingService.initData(targetSubId, context,callback)
}
companion object {
@JvmStatic
fun startSimOnboardingActivity(
context: Context,
subId: Int,
) {
val intent = Intent(context, SimOnboardingActivity::class.java).apply {
putExtra(SUB_ID, subId)
}
context.startActivity(intent)
}
var onboardingService:SimOnboardingService = SimOnboardingService()
const val TAG = "SimOnboardingActivity"
const val SUB_ID = "sub_id"
const val CALLBACK_ERROR = -1
const val CALLBACK_ONBOARDING_COMPLETE = 1
const val CALLBACK_SETUP_NAME = 2
const val CALLBACK_SETUP_PRIMARY_SIM = 3
const val CALLBACK_FINISH = 4
}
}

View File

@@ -23,11 +23,17 @@ import android.telephony.TelephonyManager
import android.telephony.UiccCardInfo
import android.telephony.UiccSlotInfo
import android.util.Log
import com.android.settings.spa.network.setAutomaticData
import com.android.settings.spa.network.setDefaultData
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.withContext
private const val TAG = "SimOnboardingService"
private const val INVALID = -1
private const val INVALID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
class SimOnboardingService {
var subscriptionManager:SubscriptionManager? = null
@@ -40,19 +46,72 @@ class SimOnboardingService {
var slotInfoList: List<UiccSlotInfo> = listOf()
var uiccCardInfoList: List<UiccCardInfo> = listOf()
var selectedSubInfoList: MutableList<SubscriptionInfo> = mutableListOf()
var targetPrimarySimCalls: Int = -1
var targetPrimarySimTexts: Int = -1
var targetPrimarySimMobileData: Int = -1
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
var targetNonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
get() {
if(targetPrimarySimMobileData == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
Log.w(TAG, "No DDS")
return SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
return selectedSubInfoList
.filter { info ->
(info.simSlotIndex != -1) && (info.subscriptionId != targetPrimarySimMobileData)
}
.map { it.subscriptionId }
.firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
var callback: (Int) -> Unit = {}
var isMultipleEnabledProfilesSupported: Boolean = false
get() {
if (uiccCardInfoList.isEmpty()) {
Log.w(TAG, "UICC cards info list is empty.")
return false
}
return uiccCardInfoList.stream()
.anyMatch { cardInfo: UiccCardInfo -> cardInfo.isMultipleEnabledProfilesSupported }
return uiccCardInfoList.any { it.isMultipleEnabledProfilesSupported }
}
var isRemovableSimEnabled: Boolean = false
get() {
if(slotInfoList.isEmpty()) {
Log.w(TAG, "UICC Slot info list is empty.")
return false
}
return UiccSlotUtil.isRemovableSimEnabled(slotInfoList)
}
var doesTargetSimHaveEsimOperation = false
get() {
return targetSubInfo?.isEmbedded ?: false
}
var isUsableTargetSubscriptionId = false
get() {
return SubscriptionManager.isUsableSubscriptionId(targetSubId)
}
var getActiveModemCount = 0
get() {
return telephonyManager?.getActiveModemCount() ?: 0
}
var renameMutableMap : MutableMap<Int, String> = mutableMapOf()
var userSelectedSubInfoList : MutableList<SubscriptionInfo> = mutableListOf()
var isSimSelectionFinished = false
get() {
return getActiveModemCount != 0 && userSelectedSubInfoList.size == getActiveModemCount
}
var isAllOfSlotAssigned = false
get() {
if(getActiveModemCount == 0){
Log.e(TAG, "isAllOfSlotAssigned: getActiveModemCount is 0")
return true
}
return getActiveModemCount != 0 && activeSubInfoList.size == getActiveModemCount
}
fun isValid(): Boolean {
return targetSubId != INVALID
@@ -73,18 +132,27 @@ class SimOnboardingService {
targetPrimarySimCalls = -1
targetPrimarySimTexts = -1
targetPrimarySimMobileData = -1
renameMutableMap.clear()
clearUserRecord()
}
fun initData(inputTargetSubId:Int,context: Context) {
fun clearUserRecord(){
renameMutableMap.clear()
userSelectedSubInfoList.clear()
}
fun initData(inputTargetSubId:Int,context: Context, callback: (Int) -> Unit) {
this.callback = callback
targetSubId = inputTargetSubId
subscriptionManager = context.getSystemService(SubscriptionManager::class.java)
telephonyManager = context.getSystemService(TelephonyManager::class.java)
Log.d(
TAG, "startInit: targetSubId:$targetSubId"
)
ThreadUtils.postOnBackgroundThread {
activeSubInfoList = SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
availableSubInfoList = SubscriptionUtil.getAvailableSubscriptions(context)
targetSubInfo = availableSubInfoList.find { subInfo -> subInfo.subscriptionId == targetSubId }
targetSubInfo?.let { userSelectedSubInfoList.add(it) }
Log.d(
TAG, "targetSubId: $targetSubId" + ", targetSubInfo: $targetSubInfo" +
". activeSubInfoList: $activeSubInfoList"
@@ -94,11 +162,24 @@ class SimOnboardingService {
uiccCardInfoList = telephonyManager?.uiccCardsInfo!!
Log.d(TAG, "uiccCardInfoList: $uiccCardInfoList")
Log.d(TAG, "isMultipleEnabledProfilesSupported: $isMultipleEnabledProfilesSupported")
targetPrimarySimCalls = SubscriptionManager.getDefaultVoiceSubscriptionId()
targetPrimarySimTexts = SubscriptionManager.getDefaultSmsSubscriptionId()
targetPrimarySimMobileData = SubscriptionManager.getDefaultDataSubscriptionId()
Log.d(
TAG,"doesTargetSimHaveEsimOperation: $doesTargetSimHaveEsimOperation" +
", isRemovableSimEnabled: $isRemovableSimEnabled" +
", isMultipleEnabledProfilesSupported: $isMultipleEnabledProfilesSupported" +
", targetPrimarySimCalls: $targetPrimarySimCalls" +
", targetPrimarySimTexts: $targetPrimarySimTexts" +
", targetPrimarySimMobileData: $targetPrimarySimMobileData")
}
}
fun getSelectableSubscriptionInfo(): List<SubscriptionInfo> {
/**
* Return the subscriptionInfo list which has
* the target subscriptionInfo + active subscriptionInfo.
*/
fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
var list: MutableList<SubscriptionInfo> = mutableListOf()
list.addAll(activeSubInfoList)
if (!list.contains(targetSubInfo)) {
@@ -109,18 +190,102 @@ class SimOnboardingService {
return list.toList()
}
/**
* Return the user selected SubscriptionInfo list.
*/
fun getSelectedSubscriptionInfoList(): List<SubscriptionInfo> {
if (userSelectedSubInfoList.isEmpty()){
Log.d(TAG, "userSelectedSubInfoList is empty")
return activeSubInfoList
}
return userSelectedSubInfoList.toList()
}
fun addItemForRenaming(subInfo: SubscriptionInfo, newName: String) {
if (subInfo.displayName == newName) {
return
}
renameMutableMap[subInfo.subscriptionId] = newName
Log.d(TAG, "renameMutableMap add ${subInfo.subscriptionId} & $newName into: $renameMutableMap")
}
fun getSubscriptionInfoDisplayName(subInfo: SubscriptionInfo): String {
return renameMutableMap[subInfo.subscriptionId] ?: subInfo.displayName.toString()
}
fun startActivatingSim(callback:() -> Unit){
fun addCurrentItemForSelectedSim(){
userSelectedSubInfoList.addAll(activeSubInfoList)
}
fun addItemForSelectedSim(selectedSubInfo: SubscriptionInfo) {
userSelectedSubInfoList.add(selectedSubInfo)
}
fun removeItemForSelectedSim(selectedSubInfo: SubscriptionInfo) {
if (userSelectedSubInfoList.contains(selectedSubInfo)) {
userSelectedSubInfoList.remove(selectedSubInfo)
}
}
/**
* Return the subscriptionInfo which will be removed in the slot during the sim onboarding.
* If return Null, then no subscriptionInfo will be removed in the slot.
*/
fun getRemovedSim():SubscriptionInfo?{
return activeSubInfoList.find { !userSelectedSubInfoList.contains(it) }
}
fun handleTogglePsimAction() {
val canDisablePhysicalSubscription =
subscriptionManager?.canDisablePhysicalSubscription() == true
if (targetSubInfo != null && canDisablePhysicalSubscription) {
// TODO: to support disable case.
subscriptionManager?.setUiccApplicationsEnabled(
targetSubInfo!!.subscriptionId, /*enabled=*/true)
} else {
Log.i(TAG, "The device does not support toggling pSIM. It is enough to just "
+ "enable the removable slot."
)
}
}
fun startActivatingSim(){
// TODO: start to activate sim
callback(SimOnboardingActivity.CALLBACK_FINISH)
}
suspend fun startSetupName() {
withContext(Dispatchers.Default) {
renameMutableMap.forEach {
subscriptionManager?.setDisplayName(
it.value, it.key,
SubscriptionManager.NAME_SOURCE_USER_INPUT
)
}
// next action is SETUP_PRIMARY_SIM
callback(SimOnboardingActivity.CALLBACK_SETUP_PRIMARY_SIM)
}
}
suspend fun startSetupPrimarySim(context: Context) {
withContext(Dispatchers.Default) {
setDefaultVoice(subscriptionManager,targetPrimarySimCalls)
setDefaultSms(subscriptionManager,targetPrimarySimTexts)
setDefaultData(
context,
subscriptionManager,
null,
targetPrimarySimMobileData
)
val telephonyManagerForNonDds: TelephonyManager? =
context.getSystemService(TelephonyManager::class.java)
?.createForSubscriptionId(targetNonDds)
setAutomaticData(telephonyManagerForNonDds, targetPrimarySimAutoDataSwitch)
// no next action, send finish
callback(SimOnboardingActivity.CALLBACK_FINISH)
}
}
}

View File

@@ -48,8 +48,6 @@ import com.android.settings.network.helper.SelectableSubscriptions;
import com.android.settings.network.helper.SubscriptionAnnotation;
import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
import com.android.settings.spa.SpaActivity;
import com.android.settings.spa.network.SimOnboardingPageProvider;
import java.util.ArrayList;
import java.util.Collections;
@@ -546,8 +544,7 @@ public class SubscriptionUtil {
return;
}
if (enable && Flags.isDualSimOnboardingEnabled()) {
String route = SimOnboardingPageProvider.INSTANCE.getRoute(subId);
SpaActivity.startSpaActivity(context, route);
SimOnboardingActivity.startSimOnboardingActivity(context, subId);
return;
}
context.startActivity(ToggleSubscriptionDialogActivity.getIntent(context, subId, enable));

View File

@@ -463,15 +463,25 @@ public class UiccSlotUtil {
if (telMgr == null) {
return false;
}
ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(telMgr);
List<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(telMgr);
return isRemovableSimEnabled(slotInfos);
}
/**
* Return whether the removable psim is enabled.
*
* @param slotInfos is a List of UiccSlotInfo.
* @return whether the removable psim is enabled.
*/
public static boolean isRemovableSimEnabled(List<UiccSlotInfo> slotInfos) {
boolean isRemovableSimEnabled =
slotInfos.stream()
.anyMatch(
slot -> slot != null
&& slot.isRemovable()
&& !slot.getIsEuicc()
&& slot.getPorts().stream().anyMatch(
port -> port.isActive())
&& slot.getPorts().stream()
.anyMatch(port -> port.isActive())
&& slot.getCardStateInfo()
== UiccSlotInfo.CARD_STATE_INFO_PRESENT);
Log.i(TAG, "isRemovableSimEnabled: " + isRemovableSimEnabled);

View File

@@ -41,6 +41,7 @@ import androidx.fragment.app.FragmentManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.flags.Flags;
import com.android.settings.network.CarrierConfigCache;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.ims.WifiCallingQueryImsState;
@@ -134,6 +135,17 @@ public class SimDialogActivity extends FragmentActivity {
return;
}
if (Flags.isDualSimOnboardingEnabled()
&& getProgressState() == SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
&& (dialogType == PREFERRED_PICK
|| dialogType == DATA_PICK
|| dialogType == CALLS_PICK
|| dialogType == SMS_PICK)) {
Log.d(TAG, "Finish the sim dialog since the sim onboarding is shown");
finish();
return;
}
final String tag = Integer.toString(dialogType);
final FragmentManager fragmentManager = getSupportFragmentManager();
SimDialogFragment fragment = (SimDialogFragment) fragmentManager.findFragmentByTag(tag);

View File

@@ -67,6 +67,7 @@ import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverF
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.combine
@@ -171,6 +172,8 @@ object NetworkCellularGroupProvider : SettingsPageProvider {
.map { it.subscriptionId }
.firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
Log.d(name, "defaultDataSubId: $defaultDataSubId, nonDds: $nonDds")
}
}
@@ -195,7 +198,6 @@ fun PageImpl(selectableSubscriptionInfoList: List<SubscriptionInfo>,
selectableSubscriptionInfoList
)
PrimarySimSectionImpl(
subscriptionManager,
activeSubscriptionInfoList,
defaultVoiceSubId,
defaultSmsSubId,
@@ -257,12 +259,49 @@ fun SimsSectionImpl(
@Composable
fun PrimarySimSectionImpl(
subscriptionManager: SubscriptionManager?,
activeSubscriptionInfoList: List<SubscriptionInfo>,
subscriptionInfoList: List<SubscriptionInfo>,
callsSelectedId: MutableIntState,
textsSelectedId: MutableIntState,
mobileDataSelectedId: MutableIntState,
nonDds: MutableIntState
nonDds: MutableIntState,
subscriptionManager: SubscriptionManager? =
LocalContext.current.getSystemService(SubscriptionManager::class.java),
coroutineScope: CoroutineScope = rememberCoroutineScope(),
context: Context = LocalContext.current,
actionSetCalls: (Int) -> Unit = {
callsSelectedId.intValue = it
coroutineScope.launch {
setDefaultVoice(subscriptionManager, it)
}
},
actionSetTexts: (Int) -> Unit = {
textsSelectedId.intValue = it
coroutineScope.launch {
setDefaultSms(subscriptionManager, it)
}
},
actionSetMobileData: (Int) -> Unit = {
mobileDataSelectedId.intValue = it
coroutineScope.launch {
// TODO: to fix the WifiPickerTracker crash when create
// the wifiPickerTrackerHelper
setDefaultData(
context,
subscriptionManager,
null/*wifiPickerTrackerHelper*/,
it
)
}
},
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)
}
},
) {
var state = rememberSaveable { mutableStateOf(false) }
var callsAndSmsList = remember {
@@ -272,11 +311,11 @@ fun PrimarySimSectionImpl(
mutableListOf(ListPreferenceOption(id = -1, text = "Loading"))
}
if (activeSubscriptionInfoList.size >= 2) {
if (subscriptionInfoList.size >= 2) {
state.value = true
callsAndSmsList.clear()
dataList.clear()
for (info in activeSubscriptionInfoList) {
for (info in subscriptionInfoList) {
var item = ListPreferenceOption(
id = info.subscriptionId,
text = "${info.displayName}"
@@ -291,12 +330,10 @@ fun PrimarySimSectionImpl(
} else {
// hide the primary sim
state.value = false
Log.d("NetworkCellularGroupProvider", "Hide primary sim")
Log.d(NetworkCellularGroupProvider.name, "Hide primary sim")
}
if (state.value) {
val coroutineScope = rememberCoroutineScope()
var context = LocalContext.current
val telephonyManagerForNonDds: TelephonyManager? =
context.getSystemService(TelephonyManager::class.java)
?.createForSubscriptionId(nonDds.intValue)
@@ -305,44 +342,27 @@ fun PrimarySimSectionImpl(
}
Category(title = stringResource(id = R.string.primary_sim_title)) {
createPrimarySimListPreference(
CreatePrimarySimListPreference(
stringResource(id = R.string.primary_sim_calls_title),
callsAndSmsList,
callsSelectedId,
ImageVector.vectorResource(R.drawable.ic_phone),
) {
callsSelectedId.intValue = it
coroutineScope.launch {
setDefaultVoice(subscriptionManager, it)
}
}
createPrimarySimListPreference(
actionSetCalls
)
CreatePrimarySimListPreference(
stringResource(id = R.string.primary_sim_texts_title),
callsAndSmsList,
textsSelectedId,
Icons.AutoMirrored.Outlined.Message,
) {
textsSelectedId.intValue = it
coroutineScope.launch {
setDefaultSms(subscriptionManager, it)
}
}
createPrimarySimListPreference(
actionSetTexts
)
CreatePrimarySimListPreference(
stringResource(id = R.string.mobile_data_settings_title),
dataList,
mobileDataSelectedId,
Icons.Outlined.DataUsage,
) {
mobileDataSelectedId.intValue = it
coroutineScope.launch {
// TODO: to fix the WifiPickerTracker crash when create
// the wifiPickerTrackerHelper
setDefaultData(context,
subscriptionManager,
null/*wifiPickerTrackerHelper*/,
it)
}
}
actionSetMobileData
)
}
val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title)
@@ -351,24 +371,17 @@ fun PrimarySimSectionImpl(
object : SwitchPreferenceModel {
override val title = autoDataTitle
override val summary = { autoDataSummary }
override val changeable: () -> Boolean = {
nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
override val checked = {
if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
coroutineScope.launch {
withContext(Dispatchers.Default) {
automaticDataChecked.value = telephonyManagerForNonDds != null
&& telephonyManagerForNonDds.isMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
automaticDataChecked.value = getAutomaticData(telephonyManagerForNonDds)
}
}
automaticDataChecked.value
}
override val onCheckedChange: ((Boolean) -> Unit)? =
{ newChecked: Boolean ->
coroutineScope.launch {
setAutomaticData(telephonyManagerForNonDds, newChecked)
}
override val onCheckedChange: ((Boolean) -> Unit)? = {
automaticDataChecked.value = it
actionSetAutoDataSwitch(it)
}
}
})
@@ -428,19 +441,19 @@ private fun showEuiccSettings(context: Context): Boolean {
return MobileNetworkUtils.showEuiccSettings(context)
}
private suspend fun setDefaultVoice(
suspend fun setDefaultVoice(
subscriptionManager: SubscriptionManager?,
subId: Int): Unit = withContext(Dispatchers.Default) {
subscriptionManager?.setDefaultVoiceSubscriptionId(subId)
}
private suspend fun setDefaultSms(
suspend fun setDefaultSms(
subscriptionManager: SubscriptionManager?,
subId: Int): Unit = withContext(Dispatchers.Default) {
subscriptionManager?.setDefaultSmsSubId(subId)
}
private suspend fun setDefaultData(context: Context,
suspend fun setDefaultData(context: Context,
subscriptionManager: SubscriptionManager?,
wifiPickerTrackerHelper: WifiPickerTrackerHelper?,
subId: Int): Unit = withContext(Dispatchers.Default) {
@@ -455,11 +468,22 @@ private suspend fun setDefaultData(context: Context,
wifiPickerTrackerHelper.setCarrierNetworkEnabled(true)
}
}
private suspend fun setAutomaticData(telephonyManager: TelephonyManager?, newState: Boolean): Unit =
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",
"setAutomaticData: MOBILE_DATA_POLICY_AUTO_DATA_SWITCH as $newState"
)
telephonyManager?.setMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
newState)
newState
)
//TODO: setup backup calling
}

View File

@@ -27,11 +27,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settings.network.SimOnboardingService
import com.android.settings.network.SubscriptionUtil
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
@@ -74,7 +72,7 @@ private fun labelSimBody(onboardingService: SimOnboardingService) {
SettingsBody(stringResource(R.string.sim_onboarding_label_sim_msg))
}
for (subInfo in onboardingService.getSelectableSubscriptionInfo()) {
for (subInfo in onboardingService.getSelectableSubscriptionInfoList()) {
var titleSimName by remember {
mutableStateOf(
onboardingService.getSubscriptionInfoDisplayName(subInfo)

View File

@@ -21,6 +21,7 @@ import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.os.Bundle
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
@@ -31,12 +32,12 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.android.settings.R
import com.android.settings.network.SimOnboardingActivity
import com.android.settings.network.SimOnboardingService
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -59,7 +60,7 @@ object SimOnboardingPageProvider : SettingsPageProvider {
private val owner = createSettingsPage()
@VisibleForTesting
var onboardingService: SimOnboardingService = SimOnboardingService()
var onboardingService: SimOnboardingService = SimOnboardingActivity.onboardingService
fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
.setUiLayoutFn {
@@ -72,18 +73,12 @@ object SimOnboardingPageProvider : SettingsPageProvider {
@Composable
override fun Page(arguments: Bundle?) {
initServiceData(arguments!!.getInt(SUB_ID))
PageImpl(onboardingService,rememberNavController())
}
fun getRoute(
subId: Int
): String = "${name}/$subId"
@Composable
fun initServiceData(targetSubId: Int) {
onboardingService.initData(targetSubId, LocalContext.current)
}
}
private fun Context.getActivity(): Activity? = when (this) {
@@ -95,7 +90,10 @@ private fun Context.getActivity(): Activity? = when (this) {
@Composable
fun PageImpl(onboardingService:SimOnboardingService,navHostController: NavHostController) {
val context = LocalContext.current
var previousPageOfOnboarding: () -> Unit = { context.getActivity()?.finish() }
var finishOnboarding: () -> Unit = {
context.getActivity()?.finish()
onboardingService.callback(SimOnboardingActivity.CALLBACK_FINISH)
}
NavHost(
navController = navHostController,
@@ -103,31 +101,32 @@ fun PageImpl(onboardingService:SimOnboardingService,navHostController: NavHostCo
) {
composable(route = SimOnboardingScreen.LabelSim.name) {
val nextPage =
// Adding more conditions
if (onboardingService.isMultipleEnabledProfilesSupported) {
if (onboardingService.isMultipleEnabledProfilesSupported && onboardingService.isAllOfSlotAssigned) {
SimOnboardingScreen.SelectSim.name
} else {
onboardingService.addCurrentItemForSelectedSim()
SimOnboardingScreen.PrimarySim.name
}
SimOnboardingLabelSimImpl(
nextAction = { navHostController.navigate(nextPage) },
cancelAction = previousPageOfOnboarding,
cancelAction = finishOnboarding,
onboardingService = onboardingService
)
}
composable(route = SimOnboardingScreen.PrimarySim.name) {
SimOnboardingPrimarySimImpl(
nextAction = {
//go back and activate sim
onboardingService.callback(SimOnboardingActivity.CALLBACK_ONBOARDING_COMPLETE)
context.getActivity()?.finish()
},
cancelAction = previousPageOfOnboarding,
cancelAction = finishOnboarding,
onboardingService = onboardingService
)
}
composable(route = SimOnboardingScreen.SelectSim.name) {
SimOnboardingSelectSimImpl(
nextAction = { navHostController.navigate(SimOnboardingScreen.PrimarySim.name) },
cancelAction = previousPageOfOnboarding,
cancelAction = finishOnboarding,
onboardingService = onboardingService
)
}

View File

@@ -16,32 +16,25 @@
package com.android.settings.spa.network
import android.telephony.SubscriptionManager
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Message
import androidx.compose.material.icons.outlined.DataUsage
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.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import com.android.settings.R
import com.android.settings.network.SimOnboardingService
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.preference.ListPreference
import com.android.settingslib.spa.widget.preference.ListPreferenceModel
import com.android.settingslib.spa.widget.preference.ListPreferenceOption
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton
import com.android.settingslib.spa.widget.scaffold.SuwScaffold
import com.android.settingslib.spa.widget.ui.SettingsBody
@@ -68,84 +61,54 @@ fun SimOnboardingPrimarySimImpl(
cancelAction
),
) {
primarySimBody(onboardingService)
val callsSelectedId = rememberSaveable {
mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
val textsSelectedId = rememberSaveable {
mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
val mobileDataSelectedId = rememberSaveable {
mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
val nonDdsRemember = rememberSaveable {
mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
}
@Composable
private fun primarySimBody(onboardingService: SimOnboardingService) {
//TODO: Load the status from the frameworks
var callsSelectedId = rememberSaveable { mutableIntStateOf(1) }
var textsSelectedId = rememberSaveable { mutableIntStateOf(1) }
var mobileDataSelectedId = rememberSaveable { mutableIntStateOf(1) }
var automaticDataChecked by rememberSaveable { mutableStateOf(true) }
Column(Modifier.padding(SettingsDimension.itemPadding)) {
SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg))
}
var selectableSubscriptionInfo = onboardingService.getSelectableSubscriptionInfo()
var list = listOf(ListPreferenceOption(id = -1, text = "Loading"))
if (selectableSubscriptionInfo.size >= 2) {
list = listOf(
ListPreferenceOption(
id = selectableSubscriptionInfo[0].subscriptionId,
text = "${selectableSubscriptionInfo[0].displayName}"
),
ListPreferenceOption(
id = selectableSubscriptionInfo[1].subscriptionId,
text = "${selectableSubscriptionInfo[1].displayName}"
),
ListPreferenceOption(
id = -1,
text = stringResource(id = R.string.sim_calls_ask_first_prefs_title)
),
)
} else {
// set all of primary sim items' enable as false and showing that sim.
}
createPrimarySimListPreference(
stringResource(id = R.string.primary_sim_calls_title),
list,
callsSelectedId,
ImageVector.vectorResource(R.drawable.ic_phone),
onIdSelected = { callsSelectedId.intValue = it }
)
createPrimarySimListPreference(
stringResource(id = R.string.primary_sim_texts_title),
list,
textsSelectedId,
Icons.AutoMirrored.Outlined.Message,
onIdSelected = { textsSelectedId.intValue = it }
)
createPrimarySimListPreference(
stringResource(id = R.string.mobile_data_settings_title),
list,
mobileDataSelectedId,
Icons.Outlined.DataUsage,
onIdSelected = { mobileDataSelectedId.intValue = it }
var selectedSubscriptionInfoList = onboardingService.getSelectedSubscriptionInfoList()
callsSelectedId.intValue = onboardingService.targetPrimarySimCalls
textsSelectedId.intValue = onboardingService.targetPrimarySimTexts
mobileDataSelectedId.intValue = onboardingService.targetPrimarySimMobileData
PrimarySimSectionImpl(
subscriptionInfoList = selectedSubscriptionInfoList,
callsSelectedId = callsSelectedId,
textsSelectedId = textsSelectedId,
mobileDataSelectedId = mobileDataSelectedId,
nonDds = nonDdsRemember,
actionSetCalls = {
callsSelectedId.intValue = it
onboardingService.targetPrimarySimCalls = it},
actionSetTexts = {
textsSelectedId.intValue = it
onboardingService.targetPrimarySimTexts = it},
actionSetMobileData = {
mobileDataSelectedId.intValue = it
onboardingService.targetPrimarySimMobileData = it},
actionSetAutoDataSwitch = {
onboardingService.targetPrimarySimAutoDataSwitch = it},
)
val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title)
val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg)
SwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = autoDataTitle
override val summary = { autoDataSummary }
override val checked = { automaticDataChecked }
override val onCheckedChange =
{ newChecked: Boolean -> automaticDataChecked = newChecked }
}
})
}
@Composable
fun createPrimarySimListPreference(
fun CreatePrimarySimListPreference(
title: String,
list: List<ListPreferenceOption>,
selectedId: MutableIntState,
icon: ImageVector,
enable: Boolean = true,
onIdSelected: (id: Int) -> Unit
) = ListPreference(remember {
object : ListPreferenceModel {
@@ -156,7 +119,5 @@ fun createPrimarySimListPreference(
override val icon = @Composable {
SettingsIcon(icon)
}
override val enabled: () -> Boolean
get() = { enable }
}
})

View File

@@ -16,24 +16,23 @@
package com.android.settings.spa.network
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.SignalCellularAlt
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settings.network.SimOnboardingService
import com.android.settings.sim.SimDialogActivity
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.preference.CheckboxPreference
import com.android.settingslib.spa.widget.preference.CheckboxPreferenceModel
import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton
import com.android.settingslib.spa.widget.scaffold.SuwScaffold
import com.android.settingslib.spa.widget.ui.SettingsBody
@@ -68,21 +67,37 @@ private fun selectSimBody(onboardingService: SimOnboardingService) {
Column(Modifier.padding(SettingsDimension.itemPadding)) {
SettingsBody(stringResource(id = R.string.sim_onboarding_select_sim_msg))
}
for (subInfo in onboardingService.getSelectableSubscriptionInfo()) {
var isFinished = rememberSaveable { mutableStateOf(false) }
isFinished.value = onboardingService.isSimSelectionFinished
for (subInfo in onboardingService.getSelectableSubscriptionInfoList()) {
var title = onboardingService.getSubscriptionInfoDisplayName(subInfo)
var summaryNumber =
subInfo.number // TODO using the SubscriptionUtil.getFormattedPhoneNumber
var changeable = subInfo.isActive
var checked by rememberSaveable { mutableStateOf(!subInfo.isActive) }
var checked = rememberSaveable {
mutableStateOf(
onboardingService.getSelectedSubscriptionInfoList().contains(subInfo)
)
}
CheckboxPreference(remember {
object : CheckboxPreferenceModel {
override val title = title
override val summary: () -> String
get() = { summaryNumber }
override val checked = { checked }
override val changeable = { changeable }
override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
override val checked = { checked.value }
override val onCheckedChange = { newChecked: Boolean ->
checked.value = newChecked
if (newChecked) {
onboardingService.addItemForSelectedSim(subInfo)
} else {
onboardingService.removeItemForSelectedSim(subInfo)
}
isFinished.value = onboardingService.isSimSelectionFinished
}
override val changeable = {
subInfo.isActive
&& (!isFinished.value || (isFinished.value && checked.value))
}
}
})
}

View File

@@ -108,7 +108,7 @@ class SimOnboardingLabelSimTest {
on { targetSubInfo }.doReturn(SUB_INFO_1)
on { availableSubInfoList }.doReturn(listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_3))
on { activeSubInfoList }.doReturn(listOf(SUB_INFO_2, SUB_INFO_3))
on { getSelectableSubscriptionInfo() }.doReturn(
on { getSelectableSubscriptionInfoList() }.doReturn(
listOf(
SUB_INFO_1,
SUB_INFO_2,
@@ -139,7 +139,7 @@ class SimOnboardingLabelSimTest {
on { targetSubInfo }.doReturn(SUB_INFO_1)
on { availableSubInfoList }.doReturn(listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_3))
on { activeSubInfoList }.doReturn(listOf(SUB_INFO_2, SUB_INFO_3))
on { getSelectableSubscriptionInfo() }.doReturn(
on { getSelectableSubscriptionInfoList() }.doReturn(
listOf(
SUB_INFO_1,
SUB_INFO_2,

View File

@@ -91,6 +91,7 @@ class SimOnboardingPageProviderTest {
fun simOnboardingPage_nextAction_fromLabelSimToSelectSim() {
mockSimOnboardingService.stub {
on { isMultipleEnabledProfilesSupported }.thenReturn(true)
on { isAllOfSlotAssigned }.thenReturn(true)
}
composeTestRule.setContent {

View File

@@ -108,7 +108,7 @@ class SimOnboardingSelectSimTest {
on { targetSubInfo }.doReturn(SUB_INFO_1)
on { availableSubInfoList }.doReturn(listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_3))
on { activeSubInfoList }.doReturn(listOf(SUB_INFO_2, SUB_INFO_3))
on { getSelectableSubscriptionInfo() }.doReturn(
on { getSelectableSubscriptionInfoList() }.doReturn(
listOf(
SUB_INFO_1,
SUB_INFO_2,