• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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