Merge "Add the mechanism of enable DSDS" into main
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8b0cd67..f26939e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11685,6 +11685,13 @@
     <!-- Body text of automatic data switching at dual sim onboarding's primary sim page or SIMs page. [CHAR LIMIT=NONE] -->
     <string name="primary_sim_automatic_data_msg">Use data from either SIM depending on coverage and availability</string>
 
+    <!-- Title of asking the user whether to restart device after enabling DSDS. [CHAR LIMIT=NONE] -->
+    <string name="sim_action_restart_dialog_title">Restart to use 2 SIMs</string>
+    <!-- Body text of asking the user whether to restart device after enabling DSDS. [CHAR LIMIT=NONE] -->
+    <string name="sim_action_restart_dialog_msg">To use 2 SIMs at once, restart your device, then turn on both SIMs</string>
+    <!-- Button text to cancel dialog and then enable the sim -->
+    <string name="sim_action_restart_dialog_cancel">Use <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g> only</string>
+
     <!-- Text of phone number item when the sim is data only. [CHAR LIMIT=NONE] -->
     <string name="sim_onboarding_phoneNumber_data_only">Data only</string>
 
diff --git a/src/com/android/settings/network/SimOnboardingActivity.kt b/src/com/android/settings/network/SimOnboardingActivity.kt
index 98bb5d7..350f5b8 100644
--- a/src/com/android/settings/network/SimOnboardingActivity.kt
+++ b/src/com/android/settings/network/SimOnboardingActivity.kt
@@ -21,7 +21,6 @@
 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
@@ -45,7 +44,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
@@ -67,7 +65,6 @@
 import com.android.settingslib.spa.widget.dialog.AlertDialogButton
 import com.android.settingslib.spa.widget.dialog.getDialogWidth
 import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
-import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
 import com.android.settingslib.spa.widget.ui.SettingsTitle
 import com.android.settingslib.spaprivileged.framework.common.userManager
 import kotlinx.coroutines.CoroutineScope
@@ -83,6 +80,8 @@
     lateinit var showBottomSheet: MutableState<Boolean>
     lateinit var showError: MutableState<ErrorType>
     lateinit var showProgressDialog: MutableState<Boolean>
+    lateinit var showDsdsProgressDialog: MutableState<Boolean>
+    lateinit var showRestartDialog: MutableState<Boolean>
 
     private var switchToEuiccSubscriptionSidecar: SwitchToEuiccSubscriptionSidecar? = null
     private var switchToRemovableSlotSidecar: SwitchToRemovableSlotSidecar? = null
@@ -132,6 +131,12 @@
                 setProgressDialog(false)
             }
 
+            CallbackType.CALLBACK_ENABLE_DSDS-> {
+                scope.launch {
+                    onboardingService.startEnableDsds(this@SimOnboardingActivity)
+                }
+            }
+
             CallbackType.CALLBACK_ONBOARDING_COMPLETE -> {
                 showBottomSheet.value = false
                 setProgressDialog(true)
@@ -179,12 +184,14 @@
         showBottomSheet = remember { mutableStateOf(false) }
         showError = remember { mutableStateOf(ErrorType.ERROR_NONE) }
         showProgressDialog = remember { mutableStateOf(false) }
+        showDsdsProgressDialog = remember { mutableStateOf(false) }
+        showRestartDialog = remember { mutableStateOf(false) }
         scope = rememberCoroutineScope()
 
         registerSidecarReceiverFlow()
 
         ErrorDialogImpl()
-
+        RestartDialogImpl()
         LaunchedEffect(Unit) {
             if (onboardingService.activeSubInfoList.isNotEmpty()) {
                 showBottomSheet.value = true
@@ -196,29 +203,76 @@
             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)
+                    if (onboardingService.isDsdsConditionSatisfied()) {
+                        // 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.
+                        if (onboardingService.doesSwitchMultiSimConfigTriggerReboot) {
+                            // case#1
+                            Log.d(TAG, "Device does not support reboot free DSDS.")
+                            showRestartDialog.value = true
+                        } else {
+                            // case#2
+                            Log.d(TAG, "Enable DSDS mode")
+                            showDsdsProgressDialog.value = true
+                            enableMultiSimSidecar?.run(SimOnboardingService.NUM_OF_SIMS_FOR_DSDS)
+                        }
+                    } else {
+                        startSimOnboardingProvider()
+                    }
                 },
                 cancelAction = { finish() },
             )
         }
 
-        if(showProgressDialog.value) {
-            ProgressDialogImpl()
+        if (showProgressDialog.value) {
+            ProgressDialogImpl(
+                stringResource(
+                    R.string.sim_onboarding_progressbar_turning_sim_on,
+                    onboardingService.targetSubInfo?.displayName ?: ""
+                )
+            )
+        }
+        if (showDsdsProgressDialog.value) {
+            ProgressDialogImpl(
+                stringResource(R.string.sim_action_enabling_sim_without_carrier_name)
+            )
+        }
+    }
+    @Composable
+    private fun RestartDialogImpl() {
+        val restartDialogPresenter = rememberAlertDialogPresenter(
+            confirmButton = AlertDialogButton(
+                stringResource(R.string.sim_action_reboot)
+            ) {
+                callbackListener(CallbackType.CALLBACK_ENABLE_DSDS)
+            },
+            dismissButton = AlertDialogButton(
+                stringResource(
+                    R.string.sim_action_restart_dialog_cancel,
+                    onboardingService.targetSubInfo?.displayName ?: "")
+            ) {
+                callbackListener(CallbackType.CALLBACK_ONBOARDING_COMPLETE)
+            },
+            title = stringResource(R.string.sim_action_restart_dialog_title),
+            text = {
+                Text(stringResource(R.string.sim_action_restart_dialog_msg))
+            },
+        )
+
+        if(showRestartDialog.value){
+            LaunchedEffect(Unit) {
+                restartDialogPresenter.open()
+            }
         }
     }
 
     @OptIn(ExperimentalMaterial3Api::class)
     @Composable
-    fun ProgressDialogImpl() {
+    fun ProgressDialogImpl(title: String) {
         // TODO: Create the SPA's ProgressDialog and using SPA's widget
         BasicAlertDialog(
             onDismissRequest = {},
@@ -232,19 +286,14 @@
             ) {
                 Row(
                     modifier = Modifier
-                            .fillMaxWidth()
-                            .padding(SettingsDimension.itemPaddingStart),
+                        .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 ?: ""
-                            )
-                        )
+                        SettingsTitle(title)
                     }
                 }
             }
@@ -329,7 +378,7 @@
         Log.e(TAG, "Error while sidecarReceiverFlow", e)
     }.conflate()
 
-    fun startSimSwitching(){
+    fun startSimSwitching() {
         Log.d(TAG, "startSimSwitching:")
 
         var targetSubInfo = onboardingService.targetSubInfo
@@ -376,8 +425,6 @@
                 switchToEuiccSubscriptionSidecar!!.reset()
                 showError.value = ErrorType.ERROR_EUICC_SLOT
                 callbackListener(CallbackType.CALLBACK_ERROR)
-                // TODO: showErrorDialog and using privileged_action_disable_fail_title and
-                //       privileged_action_disable_fail_text
             }
         }
     }
@@ -396,18 +443,19 @@
                 switchToRemovableSlotSidecar!!.reset()
                 showError.value = ErrorType.ERROR_REMOVABLE_SLOT
                 callbackListener(CallbackType.CALLBACK_ERROR)
-                // TODO: showErrorDialog and using sim_action_enable_sim_fail_title and
-                //       sim_action_enable_sim_fail_text
             }
         }
     }
 
     fun handleEnableMultiSimSidecarStateChange() {
+        showDsdsProgressDialog.value = false
         when (enableMultiSimSidecar!!.state) {
             SidecarFragment.State.SUCCESS -> {
                 enableMultiSimSidecar!!.reset()
                 Log.i(TAG, "Successfully switched to DSDS without reboot.")
-                handleEnableSubscriptionAfterEnablingDsds()
+                // refresh data
+                initServiceData(this, onboardingService.targetSubId, callbackListener)
+                startSimOnboardingProvider()
             }
 
             SidecarFragment.State.ERROR -> {
@@ -415,34 +463,14 @@
                 Log.i(TAG, "Failed to switch to DSDS without rebooting.")
                 showError.value = ErrorType.ERROR_ENABLE_DSDS
                 callbackListener(CallbackType.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(CallbackType.CALLBACK_FINISH)
-    }
-
     @Composable
     fun BottomSheetBody(nextAction: () -> Unit) {
-        Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(bottom = SettingsDimension.itemPaddingVertical)) {
+        Column(horizontalAlignment = Alignment.CenterHorizontally,
+            modifier = Modifier.padding(bottom = SettingsDimension.itemPaddingVertical)) {
             Icon(
                 imageVector = Icons.Outlined.SignalCellularAlt,
                 contentDescription = null,
@@ -497,6 +525,11 @@
         onboardingService.initData(targetSubId, context,callback)
     }
 
+    private fun startSimOnboardingProvider() {
+        val route = getRoute(onboardingService.targetSubId)
+        startSpaActivity(route)
+    }
+
     companion object {
         @JvmStatic
         fun startSimOnboardingActivity(
@@ -523,9 +556,10 @@
         enum class CallbackType(val value:Int){
             CALLBACK_ERROR(-1),
             CALLBACK_ONBOARDING_COMPLETE(1),
-            CALLBACK_SETUP_NAME(2),
-            CALLBACK_SETUP_PRIMARY_SIM(3),
-            CALLBACK_FINISH(4)
+            CALLBACK_ENABLE_DSDS(2),
+            CALLBACK_SETUP_NAME(3),
+            CALLBACK_SETUP_PRIMARY_SIM(4),
+            CALLBACK_FINISH(5)
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/network/SimOnboardingService.kt b/src/com/android/settings/network/SimOnboardingService.kt
index 962741f..2ec1ad3 100644
--- a/src/com/android/settings/network/SimOnboardingService.kt
+++ b/src/com/android/settings/network/SimOnboardingService.kt
@@ -24,6 +24,7 @@
 import android.telephony.UiccSlotInfo
 import android.util.Log
 import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
+import com.android.settings.sim.SimActivationNotifier
 import com.android.settings.spa.network.setAutomaticData
 import com.android.settings.spa.network.setDefaultData
 import com.android.settings.spa.network.setDefaultSms
@@ -32,9 +33,6 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 
-private const val TAG = "SimOnboardingService"
-private const val INVALID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
-
 class SimOnboardingService {
     var subscriptionManager:SubscriptionManager? = null
     var telephonyManager:TelephonyManager? = null
@@ -70,7 +68,7 @@
             }
             return  uiccCardInfoList.any { it.isMultipleEnabledProfilesSupported }
         }
-    var isRemovableSimEnabled: Boolean = false
+    var isRemovablePsimProfileEnabled: Boolean = false
         get() {
             if(slotInfoList.isEmpty()) {
                 Log.w(TAG, "UICC Slot info list is empty.")
@@ -78,7 +76,11 @@
             }
             return UiccSlotUtil.isRemovableSimEnabled(slotInfoList)
         }
-
+    var isEsimProfileEnabled: Boolean = false
+        get() {
+            activeSubInfoList.stream().anyMatch { it.isEmbedded }
+            return false
+        }
     var doesTargetSimHaveEsimOperation = false
         get() {
             return targetSubInfo?.isEmbedded ?: false
@@ -109,6 +111,19 @@
             }
             return getActiveModemCount != 0 && activeSubInfoList.size == getActiveModemCount
         }
+    var isMultiSimEnabled = false
+        get() {
+            return getActiveModemCount > 1
+        }
+    var isMultiSimSupported = false
+        get() {
+            return telephonyManager?.isMultiSimSupported == TelephonyManager.MULTISIM_ALLOWED
+        }
+
+    var doesSwitchMultiSimConfigTriggerReboot = false
+        get() {
+            return telephonyManager?.doesSwitchMultiSimConfigTriggerReboot() ?: false
+        }
 
     fun isValid(): Boolean {
         return targetSubId != INVALID
@@ -161,9 +176,10 @@
             targetPrimarySimCalls = SubscriptionManager.getDefaultVoiceSubscriptionId()
             targetPrimarySimTexts = SubscriptionManager.getDefaultSmsSubscriptionId()
             targetPrimarySimMobileData = SubscriptionManager.getDefaultDataSubscriptionId()
+
             Log.d(
                 TAG,"doesTargetSimHaveEsimOperation: $doesTargetSimHaveEsimOperation" +
-                    ", isRemovableSimEnabled: $isRemovableSimEnabled" +
+                    ", isRemovableSimEnabled: $isRemovablePsimProfileEnabled" +
                     ", isMultipleEnabledProfilesSupported: $isMultipleEnabledProfilesSupported" +
                     ", targetPrimarySimCalls: $targetPrimarySimCalls" +
                     ", targetPrimarySimTexts: $targetPrimarySimTexts" +
@@ -261,6 +277,45 @@
         }
     }
 
+    fun isDsdsConditionSatisfied(): Boolean {
+        if (isMultiSimEnabled) {
+            Log.d(
+                TAG,
+                "DSDS is already enabled. Condition not satisfied."
+            )
+            return false
+        }
+        if (!isMultiSimSupported) {
+            Log.d(TAG, "Hardware does not support DSDS.")
+            return false
+        }
+        val isActiveSim = activeSubInfoList.isNotEmpty()
+        if (isMultipleEnabledProfilesSupported && isActiveSim) {
+            Log.d(TAG,
+                "Device supports MEP and eSIM operation and eSIM profile is enabled."
+                        + " DSDS condition satisfied."
+            )
+            return true
+        }
+
+        if (doesTargetSimHaveEsimOperation && isRemovablePsimProfileEnabled) {
+            Log.d(TAG,
+                "eSIM operation and removable PSIM is enabled. DSDS condition satisfied."
+            )
+            return true
+        }
+
+        if (!doesTargetSimHaveEsimOperation && isEsimProfileEnabled) {
+            Log.d(TAG,
+                "Removable SIM operation and eSIM profile is enabled. DSDS condition"
+                        + " satisfied."
+            )
+            return true
+        }
+        Log.d(TAG, "DSDS condition not satisfied.")
+        return false
+    }
+
     fun startActivatingSim(){
         // TODO: start to activate sim
         callback(CallbackType.CALLBACK_FINISH)
@@ -281,30 +336,50 @@
 
     suspend fun startSetupPrimarySim(context: Context) {
         withContext(Dispatchers.Default) {
-            setDefaultVoice(subscriptionManager,targetPrimarySimCalls)
-            setDefaultSms(subscriptionManager,targetPrimarySimTexts)
-            setDefaultData(
-                context,
-                subscriptionManager,
-                null,
-                targetPrimarySimMobileData
-            )
+            if (SubscriptionUtil.getActiveSubscriptions(subscriptionManager).size <= 1) {
+                Log.d(TAG,
+                    "startSetupPrimarySim: number of active subscriptionInfo is less than 2"
+                )
+            } else {
+                setDefaultVoice(subscriptionManager, targetPrimarySimCalls)
+                setDefaultSms(subscriptionManager, targetPrimarySimTexts)
+                setDefaultData(
+                    context,
+                    subscriptionManager,
+                    null,
+                    targetPrimarySimMobileData
+                )
 
-            var nonDds = targetNonDds
-            Log.d(
-                TAG,
-                "setAutomaticData: targetNonDds: $nonDds," +
-                    " targetPrimarySimAutoDataSwitch: $targetPrimarySimAutoDataSwitch"
-            )
-            if (nonDds != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                val telephonyManagerForNonDds: TelephonyManager? =
-                    context.getSystemService(TelephonyManager::class.java)
-                        ?.createForSubscriptionId(nonDds)
-                setAutomaticData(telephonyManagerForNonDds, targetPrimarySimAutoDataSwitch)
+                var nonDds = targetNonDds
+                Log.d(
+                    TAG,
+                    "setAutomaticData: targetNonDds: $nonDds," +
+                            " targetPrimarySimAutoDataSwitch: $targetPrimarySimAutoDataSwitch"
+                )
+                if (nonDds != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    val telephonyManagerForNonDds: TelephonyManager? =
+                        context.getSystemService(TelephonyManager::class.java)
+                            ?.createForSubscriptionId(nonDds)
+                    setAutomaticData(telephonyManagerForNonDds, targetPrimarySimAutoDataSwitch)
+                }
             }
-
             // no next action, send finish
             callback(CallbackType.CALLBACK_FINISH)
         }
     }
+
+    suspend fun startEnableDsds(context: Context) {
+        withContext(Dispatchers.Default) {
+            Log.d(TAG, "User confirmed reboot to enable DSDS.")
+            SimActivationNotifier.setShowSimSettingsNotification(context, true)
+            telephonyManager?.switchMultiSimConfig(NUM_OF_SIMS_FOR_DSDS)
+            callback(CallbackType.CALLBACK_FINISH)
+        }
+    }
+
+    companion object{
+        private const val TAG = "SimOnboardingService"
+        private const val INVALID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+        const val NUM_OF_SIMS_FOR_DSDS = 2
+    }
 }
\ No newline at end of file