- Automatic Data switch can't set value to framework, since the nonDds is -1 - update the ProgressDialog as compose UI Bug: 318310357 Bug: 298898436 Bug: 298891941 Test: Build pass Change-Id: Ifeca38b42b3a51e8a967c0e744eeae06338f6ddc
454 lines
17 KiB
Kotlin
454 lines
17 KiB
Kotlin
/*
|
|
* 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 androidx.activity.compose.setContent
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.Row
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.layout.size
|
|
import androidx.compose.foundation.layout.width
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.outlined.SignalCellularAlt
|
|
import androidx.compose.material3.AlertDialogDefaults
|
|
import androidx.compose.material3.BasicAlertDialog
|
|
import androidx.compose.material3.Button
|
|
import androidx.compose.material3.CircularProgressIndicator
|
|
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.Surface
|
|
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.dialog.getDialogWidth
|
|
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()
|
|
}
|
|
}
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
@Composable
|
|
fun ProgressDialogImpl() {
|
|
if(showDialog.value) {
|
|
// TODO: Create the SPA's ProgressDialog and using SPA's widget
|
|
BasicAlertDialog(
|
|
onDismissRequest = {},
|
|
modifier = Modifier.width(
|
|
getDialogWidth()
|
|
),
|
|
) {
|
|
Surface(
|
|
color = AlertDialogDefaults.containerColor,
|
|
shape = AlertDialogDefaults.shape
|
|
) {
|
|
Row(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.padding(SettingsDimension.itemPaddingStart),
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
CircularProgressIndicator()
|
|
Column(modifier = Modifier
|
|
.padding(start = SettingsDimension.itemPaddingStart)) {
|
|
SettingsTitle(
|
|
stringResource(
|
|
R.string.sim_onboarding_progressbar_turning_sim_on,
|
|
onboardingService.targetSubInfo?.displayName ?: ""
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@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
|
|
}
|
|
} |