1 package com.android.onboarding.tasks.crossApp 2 3 import android.content.ComponentName 4 import android.content.Context 5 import android.content.Intent 6 import android.content.ServiceConnection 7 import android.os.IBinder 8 import android.util.Log 9 import com.android.onboarding.tasks.ERROR_FAILED_BIND_TASK_SERVICE 10 import com.android.onboarding.tasks.ERROR_RUNNING_TASK 11 import com.android.onboarding.tasks.OnboardingTaskContract 12 import com.android.onboarding.tasks.OnboardingTaskState 13 import com.android.onboarding.tasks.OnboardingTaskStateManager 14 import com.android.onboarding.tasks.OnboardingTaskToken 15 import kotlinx.coroutines.CoroutineScope 16 import kotlinx.coroutines.Dispatchers 17 import kotlinx.coroutines.channels.Channel 18 import kotlinx.coroutines.launch 19 20 /** 21 * Represents an item for interacting with an onboarding task service. Provides functionality to 22 * bind to the remote service, execute tasks, and query task state etc. 23 */ 24 internal class OnboardingTaskServiceItem( 25 private val context: Context, 26 private val taskStateManager: OnboardingTaskStateManager, 27 private val serviceScope: CoroutineScope = CoroutineScope(Dispatchers.IO), 28 ) { 29 30 private val serviceBindingChannel = Channel<IOnboardingTaskManagerService>() 31 private var taskManagerService: IOnboardingTaskManagerService? = null 32 private var isServiceBound = false 33 private lateinit var taskToken: OnboardingTaskToken 34 private lateinit var bindServiceIntent: Intent 35 36 /** 37 * Initiates execution of a task using the bound task manager service. 38 * 39 * @param bundle The PersistableBundle containing the task data. 40 */ runTasknull41 fun runTask(contract: OnboardingTaskContract<Any?, Any?>, args: Any?): OnboardingTaskToken { 42 43 bindServiceIntent = contract.taskServiceIntent 44 taskToken = 45 OnboardingTaskToken( 46 taskContractClass = contract::class.java.name, 47 taskComponentName = contract.componentName, 48 ) 49 50 serviceScope.launch { 51 try { 52 val service = awaitServiceBinding() 53 val persistableBundle = contract.encodeArgs(args) 54 service.runTask(persistableBundle) 55 } catch (e: Exception) { 56 Log.e(TAG, "Error running task: $taskToken", e) 57 taskStateManager.updateTaskState( 58 taskToken, 59 OnboardingTaskState.Failed<Nothing>(ERROR_RUNNING_TASK), 60 ) 61 } 62 } 63 64 return taskToken 65 } 66 67 /** Unbinds from the onboarding task manager service if currently bound. */ unbindServicenull68 fun unbindService() { 69 if (isServiceBound) { 70 context.unbindService(serviceConnection) 71 isServiceBound = false 72 taskManagerService = null 73 } 74 } 75 awaitServiceBindingnull76 private suspend fun awaitServiceBinding(): IOnboardingTaskManagerService { 77 if (!isServiceBound) { 78 bindService() 79 return serviceBindingChannel.receive() // Suspend until service is bound 80 } 81 // Based on current code implementation, this should be always non-null. 82 return taskManagerService!! 83 } 84 bindServicenull85 private fun bindService() { 86 if (!context.bindService(bindServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE)) { 87 Log.e( 88 TAG, 89 "Failed to bind service: ${bindServiceIntent.`package`}/${bindServiceIntent.action}", 90 ) 91 taskStateManager.updateTaskState( 92 taskToken, 93 OnboardingTaskState.Failed<Nothing>(ERROR_FAILED_BIND_TASK_SERVICE), 94 ) 95 } else { 96 Log.i(TAG, "Bound service: ${bindServiceIntent.`package`}/${bindServiceIntent.action}") 97 } 98 } 99 100 private val serviceConnection = 101 object : ServiceConnection { onServiceConnectednull102 override fun onServiceConnected(name: ComponentName?, service: IBinder?) { 103 Log.i(TAG, "Service connected: $name") 104 taskManagerService = IOnboardingTaskManagerService.Stub.asInterface(service) 105 isServiceBound = true 106 serviceScope.launch { serviceBindingChannel.send(taskManagerService!!) } 107 } 108 onServiceDisconnectednull109 override fun onServiceDisconnected(name: ComponentName?) { 110 Log.i(TAG, "Service disconnected: $name") 111 taskManagerService = null 112 isServiceBound = false 113 } 114 } 115 116 companion object { 117 private const val TAG = "OnboardingTaskServiceItem" 118 } 119 } 120