xref: /aosp_15_r20/external/android_onboarding/java/com/android/onboarding/tasks/OnboardingTaskContract.kt (revision c625018464ae97c56936c82b1b617e11aa899faa)
1 package com.android.onboarding.tasks
2 
3 import android.content.Intent
4 import android.os.PersistableBundle
5 import android.util.Log
6 import androidx.annotation.VisibleForTesting
7 import com.android.onboarding.contracts.annotations.OnboardingNode
8 import com.android.onboarding.contracts.annotations.OnboardingNodeMetadata
9 import com.google.errorprone.annotations.CanIgnoreReturnValue
10 
11 /**
12  * Abstract class representing the contract for an onboarding task.
13  *
14  * This class defines the contract for an onboarding task, specifying how task arguments and results
15  * should be handled. Implementing classes are expected to provide concrete implementations for
16  * encoding and extracting task arguments/results.
17  *
18  * @param TaskArgsT The type representing the task arguments.
19  * @param TaskResultT The type representing the task result.
20  */
21 abstract class OnboardingTaskContract<TaskArgsT, TaskResultT> {
22   /**
23    * Metadata associated with the onboarding task node, extracted from the component name.
24    *
25    * This includes the package name and an optional sub-name for further identification.
26    */
<lambda>null27   val onboardingTaskNodeMetaData: OnboardingNodeMetadata by lazy {
28     OnboardingNode.getOnboardingNodeMetadata(this::class.java)
29   }
30 
31   /** The `ComponentName` for this contract. */
<lambda>null32   val componentName: String by lazy {
33     OnboardingNode.extractComponentNameFromClass(this::class.java)
34   }
35 
36   /** Returns the intent which connects to the corresponding service to run the [OnboardingTask]. */
<lambda>null37   val taskServiceIntent: Intent by lazy {
38     // If no sub module in the same package(such as GMS), then just use default intent action.
39     val intentAction =
40       if (onboardingTaskNodeMetaData.packageName == onboardingTaskNodeMetaData.component) {
41         ACTION_PREFIX + ACTION_NAME
42       } else {
43         ACTION_PREFIX + onboardingTaskNodeMetaData.component + "." + ACTION_NAME
44       }
45     Intent(intentAction).setPackage(onboardingTaskNodeMetaData.packageName)
46   }
47 
48   /**
49    * Validates the integrity of the provided task arguments.
50    *
51    * This function checks whether the provided task arguments are valid and returns a Boolean
52    * indicating the validation result.
53    *
54    * @param taskArgs The task arguments to validate.
55    * @return `true` if the task arguments are valid, `false` otherwise.
56    */
validatenull57   @CanIgnoreReturnValue abstract fun validate(taskArgs: TaskArgsT?): Boolean
58 
59   /**
60    * Encodes task arguments additional contract information into a [PersistableBundle] object.
61    *
62    * @param args The task arguments to encode.
63    * @return The encoded [PersistableBundle].
64    */
65   fun encodeArgs(args: TaskArgsT): PersistableBundle {
66     val persistableBundle = performEncodeArgs(args)
67     // We save the name of the contract class to facilitate creating an instance of the contract in
68     // another process.
69     persistableBundle.putString(EXTRA_CONTRACT_CLASS, this::class.java.name)
70     return persistableBundle
71   }
72 
73   /**
74    * Encodes task arguments into a [PersistableBundle] object.
75    *
76    * @param taskArgs The task arguments to encode.
77    * @return The encoded [PersistableBundle].
78    */
performEncodeArgsnull79   abstract fun performEncodeArgs(taskArgs: TaskArgsT): PersistableBundle
80 
81   /**
82    * Extracts task arguments from a [PersistableBundle] and returns them as an [TaskArgsT].
83    *
84    * @param bundle The [PersistableBundle] containing the encoded task arguments.
85    * @return The extracted task arguments.
86    */
87   fun extractArgs(bundle: PersistableBundle): TaskArgsT {
88     val args = performExtractArgs(bundle)
89     return args
90   }
91 
92   /**
93    * Extracts task arguments from a [PersistableBundle] and returns them as an [TaskArgsT].
94    *
95    * @param bundle The [PersistableBundle] containing the encoded task arguments.
96    * @return The extracted task arguments.
97    */
performExtractArgsnull98   abstract fun performExtractArgs(bundle: PersistableBundle): TaskArgsT
99 
100   /**
101    * Encodes a task result into a [PersistableBundle].
102    *
103    * @param result The task result to encode.
104    * @return The encoded [PersistableBundle].
105    */
106   fun encodeResult(taskResult: TaskResultT): PersistableBundle {
107     return performEncodeResult(taskResult)
108   }
109 
110   /**
111    * Encodes a task result into a [PersistableBundle].
112    *
113    * @param taskResult The task result to encode.
114    * @return The encoded [PersistableBundle].
115    */
performEncodeResultnull116   abstract fun performEncodeResult(taskResult: TaskResultT): PersistableBundle
117 
118   /**
119    * Extracts task results from a [PersistableBundle] and returns them as a [TaskResult].
120    *
121    * @param bundle The [PersistableBundle] containing the encoded task results.
122    * @return The extracted task results.
123    */
124   fun extractResult(bundle: PersistableBundle): TaskResultT {
125     return performExtractResult(bundle)
126   }
127 
128   /**
129    * Extracts task results from a [PersistableBundle] and returns them as a [Result].
130    *
131    * @param bundle The [PersistableBundle] containing the encoded task results.
132    * @return The extracted task results.
133    */
performExtractResultnull134   abstract fun performExtractResult(bundle: PersistableBundle): TaskResultT
135 
136   companion object {
137     const val EXTRA_CONTRACT_CLASS = "contract_class"
138     private const val TAG = "OnboardingTaskContract"
139 
140     @VisibleForTesting const val ACTION_PREFIX = "com.android.onboarding.tasks."
141     @VisibleForTesting const val ACTION_NAME = "RUN_ONBOARDING_TASK"
142 
143     /**
144      * Attempts to create an instance of the specified contract class using reflection. This
145      * function takes a Class object representing the contract class and attempts to instantiate it
146      * using its default constructor. If successful, it returns the created instance; otherwise, it
147      * logs an error and returns null.
148      *
149      * @param contractClass The Class object representing the contract class.
150      * @return An instance of the contract class, or `null` if instantiation fails.
151      */
152     fun <T : OnboardingTaskContract<*, *>> tryCreateContractInstance(contractClass: Class<T>): T? {
153       try {
154         val constructor = contractClass.getDeclaredConstructor()
155         return constructor.newInstance() as T
156       } catch (e: Exception) {
157         Log.w(TAG, "Error instantiating contract: $e")
158       }
159       return null
160     }
161   }
162 }
163 
164 /** Equivalent to [OnboardingTaskContract] for contracts which do not take arguments. */
165 abstract class ArgumentFreeOnboardingTaskContract<TaskResultT> :
166   OnboardingTaskContract<Unit, TaskResultT>() {
167 
validatenull168   override fun validate(taskArgs: Unit?): Boolean = true
169 
170   override fun performEncodeArgs(taskArgs: Unit): PersistableBundle = PersistableBundle()
171 
172   override fun performExtractArgs(bundle: PersistableBundle) {
173     // Do nothing as no arguments.
174   }
175 }
176 
177 /** Equivalent to [OnboardingTaskContract] for contracts which do not return result. */
178 abstract class VoidOnboardingTaskContract<TaskArgsT> : OnboardingTaskContract<TaskArgsT, Unit>() {
179 
performEncodeResultnull180   override fun performEncodeResult(taskResult: Unit): PersistableBundle = PersistableBundle()
181 
182   override fun performExtractResult(bundle: PersistableBundle) {
183     // Do nothing as no result.
184   }
185 }
186 
187 /**
188  * Equivalent to [OnboardingTaskContract] for contracts which do not take arguments or return
189  * results.
190  */
191 abstract class ArgumentFreeVoidOnboardingTaskContract : ArgumentFreeOnboardingTaskContract<Unit>() {
192 
performEncodeResultnull193   override fun performEncodeResult(taskResult: Unit): PersistableBundle = PersistableBundle()
194 
195   override fun performExtractResult(bundle: PersistableBundle) {
196     // Do nothing as no result.
197   }
198 }
199