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