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