1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.network 18 19 import android.content.Context 20 import android.content.Intent 21 import android.os.Bundle 22 import android.os.UserHandle; 23 import android.provider.Settings 24 import android.telephony.SubscriptionManager 25 import android.util.Log 26 import androidx.compose.foundation.layout.Column 27 import androidx.compose.foundation.layout.Row 28 import androidx.compose.foundation.layout.fillMaxWidth 29 import androidx.compose.foundation.layout.padding 30 import androidx.compose.foundation.layout.size 31 import androidx.compose.foundation.layout.width 32 import androidx.compose.material.icons.Icons 33 import androidx.compose.material.icons.outlined.SignalCellularAlt 34 import androidx.compose.material3.AlertDialogDefaults 35 import androidx.compose.material3.BasicAlertDialog 36 import androidx.compose.material3.CircularProgressIndicator 37 import androidx.compose.material3.ExperimentalMaterial3Api 38 import androidx.compose.material3.Icon 39 import androidx.compose.material3.MaterialTheme 40 import androidx.compose.material3.Surface 41 import androidx.compose.material3.Text 42 import androidx.compose.runtime.Composable 43 import androidx.compose.runtime.LaunchedEffect 44 import androidx.compose.runtime.MutableState 45 import androidx.compose.runtime.mutableStateOf 46 import androidx.compose.runtime.rememberCoroutineScope 47 import androidx.compose.runtime.saveable.rememberSaveable 48 import androidx.compose.ui.Alignment 49 import androidx.compose.ui.Modifier 50 import androidx.compose.ui.platform.LocalContext 51 import androidx.compose.ui.platform.LocalLifecycleOwner 52 import androidx.compose.ui.res.stringResource 53 import androidx.compose.ui.text.style.TextAlign 54 import androidx.lifecycle.LifecycleRegistry 55 import com.android.settings.R 56 import com.android.settings.SidecarFragment 57 import com.android.settings.network.telephony.SimRepository 58 import com.android.settings.network.telephony.SubscriptionActionDialogActivity 59 import com.android.settings.network.telephony.SubscriptionRepository 60 import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity 61 import com.android.settings.network.telephony.requireSubscriptionManager 62 import com.android.settings.spa.SpaActivity.Companion.startSpaActivity 63 import com.android.settings.spa.network.SimOnboardingPageProvider.getRoute 64 import com.android.settings.wifi.WifiPickerTrackerHelper 65 import com.android.settingslib.spa.SpaBaseDialogActivity 66 import com.android.settingslib.spa.framework.theme.SettingsDimension 67 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle 68 import com.android.settingslib.spa.widget.dialog.AlertDialogButton 69 import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon 70 import com.android.settingslib.spa.widget.dialog.getDialogWidth 71 import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter 72 import com.android.settingslib.spa.widget.ui.SettingsTitle 73 import com.android.settingslib.spaprivileged.framework.common.userManager 74 import java.util.concurrent.CountDownLatch 75 import java.util.concurrent.TimeUnit 76 import kotlinx.coroutines.CoroutineScope 77 import kotlinx.coroutines.Dispatchers 78 import kotlinx.coroutines.channels.awaitClose 79 import kotlinx.coroutines.flow.Flow 80 import kotlinx.coroutines.flow.callbackFlow 81 import kotlinx.coroutines.flow.catch 82 import kotlinx.coroutines.flow.conflate 83 import kotlinx.coroutines.flow.firstOrNull 84 import kotlinx.coroutines.launch 85 import kotlinx.coroutines.withContext 86 import kotlinx.coroutines.withTimeoutOrNull 87 88 class SimOnboardingActivity : SpaBaseDialogActivity() { 89 lateinit var scope: CoroutineScope 90 lateinit var wifiPickerTrackerHelper: WifiPickerTrackerHelper 91 lateinit var context: Context 92 lateinit var showStartingDialog: MutableState<Boolean> 93 lateinit var showError: MutableState<ErrorType> 94 lateinit var showProgressDialog: MutableState<Boolean> 95 lateinit var showDsdsProgressDialog: MutableState<Boolean> 96 lateinit var showRestartDialog: MutableState<Boolean> 97 98 private var switchToEuiccSubscriptionSidecar: SwitchToEuiccSubscriptionSidecar? = null 99 private var switchToRemovableSlotSidecar: SwitchToRemovableSlotSidecar? = null 100 private var enableMultiSimSidecar: EnableMultiSimSidecar? = null 101 onCreatenull102 override fun onCreate(savedInstanceState: Bundle?) { 103 super.onCreate(savedInstanceState) 104 105 if (!this.userManager.isAdminUser) { 106 Log.e(TAG, "It is not the admin user. Unable to toggle subscription.") 107 finish() 108 return 109 } 110 111 var targetSubId = intent.getIntExtra(SUB_ID,SubscriptionManager.INVALID_SUBSCRIPTION_ID) 112 if (targetSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 113 targetSubId = intent.getIntExtra( 114 Settings.EXTRA_SUB_ID, 115 SubscriptionManager.INVALID_SUBSCRIPTION_ID 116 ) 117 } 118 initServiceData(this, targetSubId, callbackListener) 119 if (!onboardingService.isUsableTargetSubscriptionId) { 120 Log.e(TAG, "The subscription id is not usable.") 121 finish() 122 return 123 } 124 125 if (onboardingService.activeSubInfoList.isEmpty() 126 || (!onboardingService.isMultiSimEnabled && !onboardingService.isMultiSimSupported)) { 127 // TODO: refactor and replace the ToggleSubscriptionDialogActivity 128 Log.d(TAG, "onboardingService.activeSubInfoList is empty or restricted ss mode " + 129 ", start ToggleSubscriptionDialogActivity") 130 this.startActivity(ToggleSubscriptionDialogActivity 131 .getIntent(this.applicationContext, targetSubId, true)) 132 finish() 133 return 134 } 135 136 switchToEuiccSubscriptionSidecar = SwitchToEuiccSubscriptionSidecar.get(fragmentManager) 137 switchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(fragmentManager) 138 enableMultiSimSidecar = EnableMultiSimSidecar.get(fragmentManager) 139 } 140 finishnull141 override fun finish() { 142 setProgressDialog(false) 143 onboardingService.clear() 144 super.finish() 145 } 146 <lambda>null147 var callbackListener: (CallbackType) -> Unit = { 148 Log.d(TAG, "Receive the CALLBACK: $it") 149 when (it) { 150 CallbackType.CALLBACK_ERROR -> { 151 setProgressDialog(false) 152 } 153 154 CallbackType.CALLBACK_ENABLE_DSDS-> { 155 scope.launch { 156 onboardingService.startEnableDsds(this@SimOnboardingActivity) 157 } 158 } 159 160 CallbackType.CALLBACK_ONBOARDING_COMPLETE -> { 161 showStartingDialog.value = false 162 setProgressDialog(true) 163 scope.launch { 164 // TODO: refactor the Sidecar 165 // start to activate the sim 166 startSimSwitching() 167 } 168 } 169 170 CallbackType.CALLBACK_SETUP_NAME -> { 171 scope.launch { 172 onboardingService.startSetupName() 173 } 174 } 175 176 CallbackType.CALLBACK_SETUP_PRIMARY_SIM -> { 177 scope.launch { 178 onboardingService.startSetupPrimarySim( 179 this@SimOnboardingActivity, 180 wifiPickerTrackerHelper 181 ) 182 } 183 } 184 185 CallbackType.CALLBACK_FINISH -> { 186 finish() 187 } 188 } 189 } 190 setProgressDialognull191 fun setProgressDialog(enable: Boolean) { 192 if (!this::showProgressDialog.isInitialized) { 193 return 194 } 195 showProgressDialog.value = enable 196 val progressState = if (enable) { 197 SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING 198 } else { 199 SubscriptionActionDialogActivity.PROGRESS_IS_NOT_SHOWING 200 } 201 setProgressState(progressState) 202 } 203 204 @OptIn(ExperimentalMaterial3Api::class) 205 @Composable Contentnull206 override fun Content() { 207 showStartingDialog = rememberSaveable { mutableStateOf(false) } 208 showError = rememberSaveable { mutableStateOf(ErrorType.ERROR_NONE) } 209 showProgressDialog = rememberSaveable { mutableStateOf(false) } 210 showDsdsProgressDialog = rememberSaveable { mutableStateOf(false) } 211 showRestartDialog = rememberSaveable { mutableStateOf(false) } 212 scope = rememberCoroutineScope() 213 context = LocalContext.current 214 val lifecycleOwner = LocalLifecycleOwner.current 215 wifiPickerTrackerHelper = WifiPickerTrackerHelper( 216 LifecycleRegistry(lifecycleOwner), context, 217 null /* WifiPickerTrackerCallback */ 218 ) 219 220 registerSidecarReceiverFlow() 221 222 ErrorDialogImpl() 223 RestartDialogImpl() 224 LaunchedEffect(Unit) { 225 if (showError.value != ErrorType.ERROR_NONE 226 || showProgressDialog.value 227 || showDsdsProgressDialog.value 228 || showRestartDialog.value) { 229 Log.d(TAG, "status: showError:${showError.value}, " + 230 "showProgressDialog:${showProgressDialog.value}, " + 231 "showDsdsProgressDialog:${showDsdsProgressDialog.value}, " + 232 "showRestartDialog:${showRestartDialog.value}") 233 showStartingDialog.value = false 234 } else if (onboardingService.activeSubInfoList.isNotEmpty()) { 235 Log.d(TAG, "status: showStartingDialog.value:${showStartingDialog.value}") 236 showStartingDialog.value = true 237 } 238 } 239 240 if (showStartingDialog.value) { 241 StartingDialogImpl( 242 nextAction = { 243 if (onboardingService.isDsdsConditionSatisfied()) { 244 // TODO: if the phone is SS mode and the isDsdsConditionSatisfied is true, 245 // then enable the DSDS mode. 246 // case#1: the device need the reboot after enabling DSDS. Showing the 247 // confirm dialog to user whether reboot device or not. 248 // case#2: The device don't need the reboot. Enabling DSDS and then showing 249 // the SIM onboarding UI. 250 if (onboardingService.doesSwitchMultiSimConfigTriggerReboot) { 251 // case#1 252 Log.d(TAG, "Device does not support reboot free DSDS.") 253 showRestartDialog.value = true 254 } else { 255 // case#2 256 Log.d(TAG, "Enable DSDS mode") 257 showDsdsProgressDialog.value = true 258 enableMultiSimSidecar?.run(SimOnboardingService.NUM_OF_SIMS_FOR_DSDS) 259 } 260 } else { 261 startSimOnboardingProvider() 262 } 263 }, 264 cancelAction = { finish() }, 265 ) 266 } 267 268 if (showProgressDialog.value) { 269 ProgressDialogImpl( 270 stringResource( 271 R.string.sim_onboarding_progressbar_turning_sim_on, 272 onboardingService.targetSubInfo?.displayName ?: "" 273 ) 274 ) 275 } 276 if (showDsdsProgressDialog.value) { 277 ProgressDialogImpl( 278 stringResource(R.string.sim_action_enabling_sim_without_carrier_name) 279 ) 280 } 281 } 282 @Composable RestartDialogImplnull283 private fun RestartDialogImpl() { 284 val restartDialogPresenter = rememberAlertDialogPresenter( 285 confirmButton = AlertDialogButton( 286 stringResource(R.string.sim_action_reboot) 287 ) { 288 callbackListener(CallbackType.CALLBACK_ENABLE_DSDS) 289 }, 290 dismissButton = AlertDialogButton( 291 stringResource( 292 R.string.sim_action_restart_dialog_cancel, 293 onboardingService.targetSubInfo?.displayName ?: "") 294 ) { 295 callbackListener(CallbackType.CALLBACK_ONBOARDING_COMPLETE) 296 }, 297 title = stringResource(R.string.sim_action_restart_dialog_title), 298 text = { 299 Text(stringResource(R.string.sim_action_restart_dialog_msg)) 300 }, 301 ) 302 303 if(showRestartDialog.value){ 304 LaunchedEffect(Unit) { 305 restartDialogPresenter.open() 306 } 307 } 308 } 309 310 @OptIn(ExperimentalMaterial3Api::class) 311 @Composable ProgressDialogImplnull312 fun ProgressDialogImpl(title: String) { 313 // TODO: Create the SPA's ProgressDialog and using SPA's widget 314 BasicAlertDialog( 315 onDismissRequest = {}, 316 modifier = Modifier.width( 317 getDialogWidth() 318 ), 319 ) { 320 Surface( 321 color = AlertDialogDefaults.containerColor, 322 shape = AlertDialogDefaults.shape 323 ) { 324 Row( 325 modifier = Modifier 326 .fillMaxWidth() 327 .padding(SettingsDimension.itemPaddingStart), 328 verticalAlignment = Alignment.CenterVertically 329 ) { 330 CircularProgressIndicator() 331 Column(modifier = Modifier 332 .padding(start = SettingsDimension.itemPaddingStart)) { 333 SettingsTitle(title) 334 } 335 } 336 } 337 } 338 } 339 340 @Composable ErrorDialogImplnull341 fun ErrorDialogImpl(){ 342 // EuiccSlotSidecar showErrorDialog 343 val errorDialogPresenterForSimSwitching = rememberAlertDialogPresenter( 344 confirmButton = AlertDialogButton( 345 stringResource(android.R.string.ok) 346 ) { 347 finish() 348 }, 349 title = stringResource(R.string.sim_action_enable_sim_fail_title), 350 text = { 351 Text(stringResource(R.string.sim_action_enable_sim_fail_text)) 352 }, 353 ) 354 355 // enableDSDS showErrorDialog 356 val errorDialogPresenterForMultiSimSidecar = rememberAlertDialogPresenter( 357 confirmButton = AlertDialogButton( 358 stringResource(android.R.string.ok) 359 ) { 360 finish() 361 }, 362 title = stringResource(R.string.dsds_activation_failure_title), 363 text = { 364 Text(stringResource(R.string.dsds_activation_failure_body_msg2)) 365 }, 366 ) 367 368 // show error 369 when (showError.value) { 370 ErrorType.ERROR_SIM_SWITCHING -> errorDialogPresenterForSimSwitching.open() 371 ErrorType.ERROR_ENABLE_DSDS -> errorDialogPresenterForMultiSimSidecar.open() 372 else -> {} 373 } 374 } 375 376 @Composable registerSidecarReceiverFlownull377 fun registerSidecarReceiverFlow(){ 378 switchToEuiccSubscriptionSidecar?.sidecarReceiverFlow() 379 ?.collectLatestWithLifecycle(LocalLifecycleOwner.current) { 380 onStateChange(it) 381 } 382 switchToRemovableSlotSidecar?.sidecarReceiverFlow() 383 ?.collectLatestWithLifecycle(LocalLifecycleOwner.current) { 384 onStateChange(it) 385 } 386 enableMultiSimSidecar?.sidecarReceiverFlow() 387 ?.collectLatestWithLifecycle(LocalLifecycleOwner.current) { 388 onStateChange(it) 389 } 390 } 391 <lambda>null392 fun SidecarFragment.sidecarReceiverFlow(): Flow<SidecarFragment> = callbackFlow { 393 val broadcastReceiver = SidecarFragment.Listener { 394 Log.d(TAG, "onReceive: $it") 395 trySend(it) 396 } 397 addListener(broadcastReceiver) 398 399 awaitClose { removeListener(broadcastReceiver) } 400 }.catch { e -> 401 Log.e(TAG, "Error while sidecarReceiverFlow", e) 402 }.conflate() 403 startSimSwitchingnull404 fun startSimSwitching() { 405 Log.d(TAG, "startSimSwitching:") 406 407 var targetSubInfo = onboardingService.targetSubInfo 408 if(onboardingService.doesTargetSimActive) { 409 Log.d(TAG, "target subInfo is already active") 410 callbackListener(CallbackType.CALLBACK_SETUP_NAME) 411 return 412 } 413 targetSubInfo?.let { 414 var removedSubInfo = onboardingService.getRemovedSim() 415 if (targetSubInfo.isEmbedded) { 416 switchToEuiccSubscriptionSidecar!!.run( 417 targetSubInfo.subscriptionId, 418 UiccSlotUtil.INVALID_PORT_ID, 419 removedSubInfo 420 ) 421 return@let 422 } 423 switchToRemovableSlotSidecar!!.run( 424 UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, 425 removedSubInfo 426 ) 427 } ?: run { 428 Log.e(TAG, "no target subInfo in onboardingService") 429 finish() 430 } 431 } 432 onStateChangenull433 fun onStateChange(fragment: SidecarFragment?) { 434 if (fragment === switchToEuiccSubscriptionSidecar) { 435 handleSwitchToEuiccSubscriptionSidecarStateChange() 436 } else if (fragment === switchToRemovableSlotSidecar) { 437 handleSwitchToRemovableSlotSidecarStateChange() 438 } else if (fragment === enableMultiSimSidecar) { 439 handleEnableMultiSimSidecarStateChange() 440 } 441 } 442 handleSwitchToEuiccSubscriptionSidecarStateChangenull443 fun handleSwitchToEuiccSubscriptionSidecarStateChange() { 444 when (switchToEuiccSubscriptionSidecar!!.state) { 445 SidecarFragment.State.SUCCESS -> { 446 Log.i(TAG, "Successfully enable the eSIM profile.") 447 switchToEuiccSubscriptionSidecar!!.reset() 448 scope.launch { 449 checkSimIsReadyAndGoNext() 450 } 451 } 452 453 SidecarFragment.State.ERROR -> { 454 Log.i(TAG, "Failed to enable the eSIM profile.") 455 switchToEuiccSubscriptionSidecar!!.reset() 456 showError.value = ErrorType.ERROR_SIM_SWITCHING 457 callbackListener(CallbackType.CALLBACK_ERROR) 458 } 459 } 460 } 461 handleSwitchToRemovableSlotSidecarStateChangenull462 fun handleSwitchToRemovableSlotSidecarStateChange() { 463 when (switchToRemovableSlotSidecar!!.state) { 464 SidecarFragment.State.SUCCESS -> { 465 Log.i(TAG, "Successfully switched to removable slot.") 466 switchToRemovableSlotSidecar!!.reset() 467 onboardingService.handleTogglePsimAction() 468 scope.launch { 469 checkSimIsReadyAndGoNext() 470 } 471 } 472 473 SidecarFragment.State.ERROR -> { 474 Log.e(TAG, "Failed switching to removable slot.") 475 switchToRemovableSlotSidecar!!.reset() 476 showError.value = ErrorType.ERROR_SIM_SWITCHING 477 callbackListener(CallbackType.CALLBACK_ERROR) 478 } 479 } 480 } 481 handleEnableMultiSimSidecarStateChangenull482 fun handleEnableMultiSimSidecarStateChange() { 483 when (enableMultiSimSidecar!!.state) { 484 SidecarFragment.State.SUCCESS -> { 485 enableMultiSimSidecar!!.reset() 486 Log.i(TAG, "Successfully switched to DSDS without reboot.") 487 showDsdsProgressDialog.value = false 488 // refresh data 489 initServiceData(this, onboardingService.targetSubId, callbackListener) 490 startSimOnboardingProvider() 491 } 492 493 SidecarFragment.State.ERROR -> { 494 enableMultiSimSidecar!!.reset() 495 showDsdsProgressDialog.value = false 496 Log.i(TAG, "Failed to switch to DSDS without rebooting.") 497 showError.value = ErrorType.ERROR_ENABLE_DSDS 498 callbackListener(CallbackType.CALLBACK_ERROR) 499 } 500 } 501 } 502 checkSimIsReadyAndGoNextnull503 private suspend fun checkSimIsReadyAndGoNext() { 504 withContext(Dispatchers.Default) { 505 val waitingTimeMillis = 506 Settings.Global.getLong( 507 context.contentResolver, 508 Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS, 509 UiccSlotUtil.DEFAULT_WAIT_AFTER_SWITCH_TIMEOUT_MILLIS, 510 ) 511 Log.d(TAG, "Start waiting, waitingTime is $waitingTimeMillis") 512 val isTimeout = 513 withTimeoutOrNull(waitingTimeMillis) { 514 SubscriptionRepository(context) 515 .isSubscriptionEnabledFlow(onboardingService.targetSubId) 516 .firstOrNull { it } 517 } == null 518 Log.d( 519 TAG, 520 if (isTimeout) "Sim is not ready after timeout" else "Sim is ready then go to next", 521 ) 522 callbackListener(CallbackType.CALLBACK_SETUP_NAME) 523 } 524 } 525 526 @Composable StartingDialogImplnull527 fun StartingDialogImpl( 528 nextAction: () -> Unit, 529 cancelAction: () -> Unit, 530 ) { 531 SettingsAlertDialogWithIcon( 532 onDismissRequest = cancelAction, 533 confirmButton = AlertDialogButton( 534 text = getString(R.string.sim_onboarding_setup), 535 onClick = nextAction, 536 ), 537 dismissButton = AlertDialogButton( 538 text = getString(R.string.sim_onboarding_close), 539 onClick = cancelAction, 540 ), 541 title = stringResource(R.string.sim_onboarding_dialog_starting_title), 542 icon = { 543 Icon( 544 imageVector = Icons.Outlined.SignalCellularAlt, 545 contentDescription = null, 546 modifier = Modifier 547 .size(SettingsDimension.iconLarge), 548 tint = MaterialTheme.colorScheme.primary, 549 ) 550 }, 551 text = { 552 Text( 553 stringResource(R.string.sim_onboarding_dialog_starting_msg), 554 modifier = Modifier.fillMaxWidth(), 555 textAlign = TextAlign.Center 556 ) 557 }) 558 559 } 560 setProgressStatenull561 fun setProgressState(state: Int) { 562 val prefs = getSharedPreferences( 563 SubscriptionActionDialogActivity.SIM_ACTION_DIALOG_PREFS, 564 MODE_PRIVATE 565 ) 566 prefs.edit().putInt(SubscriptionActionDialogActivity.KEY_PROGRESS_STATE, state).apply() 567 Log.i(TAG, "setProgressState:$state") 568 } 569 initServiceDatanull570 fun initServiceData(context: Context,targetSubId: Int, callback:(CallbackType)->Unit) { 571 onboardingService.initData(targetSubId, context,callback) 572 } 573 startSimOnboardingProvidernull574 private fun startSimOnboardingProvider() { 575 val route = getRoute(onboardingService.targetSubId) 576 startSpaActivity(route) 577 } 578 579 companion object { 580 @JvmStatic startSimOnboardingActivitynull581 fun startSimOnboardingActivity( 582 context: Context, 583 subId: Int, 584 isNewTask: Boolean = false, 585 ) { 586 if (!SimRepository(context).canEnterMobileNetworkPage()) { 587 Log.i(TAG, "Unable to start SimOnboardingActivity due to missing permissions") 588 return 589 } 590 val intent = Intent(context, SimOnboardingActivity::class.java).apply { 591 putExtra(SUB_ID, subId) 592 if(isNewTask) { 593 setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 594 } 595 } 596 context.startActivityAsUser(intent, UserHandle.CURRENT) 597 } 598 599 var onboardingService:SimOnboardingService = SimOnboardingService() 600 const val TAG = "SimOnboardingActivity" 601 const val SUB_ID = "sub_id" 602 603 enum class ErrorType(val value:Int){ 604 ERROR_NONE(-1), 605 ERROR_SIM_SWITCHING(1), 606 ERROR_ENABLE_DSDS(2) 607 } 608 609 enum class CallbackType(val value:Int){ 610 CALLBACK_ERROR(-1), 611 CALLBACK_ONBOARDING_COMPLETE(1), 612 CALLBACK_ENABLE_DSDS(2), 613 CALLBACK_SETUP_NAME(3), 614 CALLBACK_SETUP_PRIMARY_SIM(4), 615 CALLBACK_FINISH(5) 616 } 617 } 618 } 619