diff options
author | Martynas Petuška <petuska@google.com> | 2023-10-24 09:05:49 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2023-10-24 09:07:18 -0700 |
commit | 4a20ef0dd672cbdec5e108b6f3044c9ae4013014 (patch) | |
tree | 36af6609218f06d77d21d983700ffa05cce2887f | |
parent | 7a0ee905ca1438313669b7e28fb3265f7ef1d674 (diff) | |
download | android_onboarding-4a20ef0dd672cbdec5e108b6f3044c9ae4013014.tar.gz |
Copybara ❤️: PreProvisioningActivity Contract
CL: cl/576158890
Bug: 298376528
NO_IFTTT=no API changes
PiperOrigin-RevId: 576158890
Change-Id: Ie8795b9dc7e3004041dae91b4d95b0f356263054
16 files changed, 141 insertions, 811 deletions
@@ -19,7 +19,6 @@ android_library { "android_onboarding.contracts.annotations", "android_onboarding.contracts.fragment", "android_onboarding.contracts.provisioning", - "android_onboarding.contracts.provisioning.managed.profile", "android_onboarding.contracts.setupwizard", "android_onboarding.flags", "android_onboarding.nodes", diff --git a/src/com/android/onboarding/contracts/IntentSerializer.kt b/src/com/android/onboarding/contracts/IntentSerializer.kt index 23a17f0..1f7e314 100644 --- a/src/com/android/onboarding/contracts/IntentSerializer.kt +++ b/src/com/android/onboarding/contracts/IntentSerializer.kt @@ -7,8 +7,38 @@ import android.os.Bundle import android.os.Parcelable import androidx.annotation.RequiresApi +/** Marks functions that read or write intent data */ +@DslMarker internal annotation class IntentManipulationDsl + /** Interface for writing an object to an [Intent] and reading the same object from an [Intent]. */ interface IntentSerializer<V> { + /** + * Utility intent builder that allows writing the [arg] and customising the intent settings + * + * @param arg to serialize into intent extras + * @param builder to configure the intent + */ + @IntentManipulationDsl + fun Intent(arg: V, builder: Intent.() -> Unit = {}): Intent = + Intent().also { + write(it, arg) + it.builder() + } + + /** + * Utility intent builder that allows writing the [arg] and customising the intent settings + * + * @param action the intent should send + * @param arg to serialize into intent extras + * @param builder to configure the intent + */ + @IntentManipulationDsl + fun Intent(action: String, arg: V, builder: Intent.() -> Unit = {}): Intent = + Intent(arg) { + this.action = action + builder() + } + fun write(intent: Intent, value: V) fun read(intent: Intent): V @@ -29,12 +59,14 @@ interface ScopedIntentSerializer<V> : IntentSerializer<V> { fun IntentScope.write(value: V) /** Writes a [value] to the [IntentScope] via a given [serializer] */ + @IntentManipulationDsl fun <T> IntentScope.write(serializer: ScopedIntentSerializer<T>, value: T): Unit = with(serializer) { write(value) } fun IntentScope.read(): V /** Reads a value [T] from the [IntentScope] via a given [serializer] */ + @IntentManipulationDsl fun <T> IntentScope.read(serializer: ScopedIntentSerializer<T>): T = with(serializer) { read() } } @@ -45,7 +77,7 @@ interface ScopedIntentSerializer<V> : IntentSerializer<V> { * @param afterRead function to call after each read * @param beforeWrite function to call before each write */ -@IntentScope.IntentManipulationDsl +@IntentManipulationDsl data class IntentScope( @PublishedApi internal val androidIntent: Intent, @PublishedApi internal val afterRead: (key: String, value: Any?) -> Unit = { _, _ -> }, @@ -53,11 +85,9 @@ data class IntentScope( ) { companion object { const val KEY_DATA = "com.android.onboarding.INTENT_DATA" + const val KEY_ACTION = "com.android.onboarding.INTENT_ACTION" } - /** Marks functions that read or write intent data */ - @DslMarker internal annotation class IntentManipulationDsl - /** * Self-reference for more fluid write access * @@ -69,6 +99,24 @@ data class IntentScope( */ @IntentManipulationDsl val intent: IntentScope = this + /** Provides observable access to [Intent.getAction] */ + @IntentManipulationDsl + var action: String? + get() = androidIntent.action.also { afterRead(KEY_ACTION, it) } + set(value) { + beforeWrite(KEY_ACTION, value) + value?.let(androidIntent::setAction) + } + + /** Provides observable access to [Intent.getType] */ + @IntentManipulationDsl + var type: String? + get() = androidIntent.type.also { afterRead(KEY_ACTION, it) } + set(value) { + beforeWrite(KEY_ACTION, value) + value?.let(androidIntent::setType) + } + /** Provides observable access to [Intent.getData] */ @IntentManipulationDsl var data: Uri? @@ -78,17 +126,13 @@ data class IntentScope( value?.let(androidIntent::setData) } - /** - * Copy over all [extras] to this [IntentScope] - */ + /** Copy over all [extras] to this [IntentScope] */ @IntentManipulationDsl operator fun plusAssign(extras: Bundle) { androidIntent.putExtras(extras) } - /** - * Copy over all extras from [other] to this [IntentScope] - */ + /** Copy over all extras from [other] to this [IntentScope] */ @IntentManipulationDsl operator fun plusAssign(other: IntentScope) { androidIntent.putExtras(other.androidIntent) @@ -222,6 +266,10 @@ data class IntentScope( } @IntentManipulationDsl + operator fun set(key: String, value: List<Parcelable>?): IntentScope = + set(key, value?.toTypedArray()) + + @IntentManipulationDsl operator fun set(key: String, value: Bundle?): IntentScope = apply { beforeWrite(key, value) value?.let { androidIntent.putExtra(key, value) } diff --git a/src/com/android/onboarding/contracts/provisioning/ACTIONS.kt b/src/com/android/onboarding/contracts/provisioning/ACTIONS.kt index 8bac608..3f66477 100644 --- a/src/com/android/onboarding/contracts/provisioning/ACTIONS.kt +++ b/src/com/android/onboarding/contracts/provisioning/ACTIONS.kt @@ -1,6 +1,7 @@ package com.android.onboarding.contracts.provisioning import android.app.admin.DevicePolicyManager +import android.nfc.NfcAdapter import android.os.Build import androidx.annotation.RequiresApi @@ -21,9 +22,11 @@ object ACTIONS { inline val ACTION_DEVICE_OWNER_CHANGED: String get() = DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED + @get:RequiresApi(Build.VERSION_CODES.Q) inline val ACTION_GET_PROVISIONING_MODE: String get() = DevicePolicyManager.ACTION_GET_PROVISIONING_MODE + @get:RequiresApi(Build.VERSION_CODES.Q) inline val ACTION_ADMIN_POLICY_COMPLIANCE: String get() = DevicePolicyManager.ACTION_ADMIN_POLICY_COMPLIANCE @@ -52,6 +55,10 @@ object ACTIONS { get() = DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE @get:Suppress("UNRESOLVED_REFERENCE") + inline val ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE: String + get() = DevicePolicyManager.ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE + + @get:Suppress("UNRESOLVED_REFERENCE") inline val ACTION_PROVISION_FINANCED_DEVICE: String get() = DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE @@ -91,4 +98,7 @@ object ACTIONS { */ inline val ACTION_PROVISION_MANAGED_DEVICE_SILENTLY: String get() = "com.android.managedprovisioning.action.PROVISION_MANAGED_DEVICE_SILENTLY" + + inline val ACTION_NDEF_DISCOVERED: String + get() = NfcAdapter.ACTION_NDEF_DISCOVERED } diff --git a/src/com/android/onboarding/contracts/provisioning/FLAGS.kt b/src/com/android/onboarding/contracts/provisioning/FLAGS.kt index 091f0e6..98ea669 100644 --- a/src/com/android/onboarding/contracts/provisioning/FLAGS.kt +++ b/src/com/android/onboarding/contracts/provisioning/FLAGS.kt @@ -4,9 +4,11 @@ import android.app.admin.DevicePolicyManager /** Container for various intent flags used by provisioning */ object FLAGS { + @get:Suppress("UNRESOLVED_REFERENCE") inline val FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED: Int get() = DevicePolicyManager.FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED + @get:Suppress("UNRESOLVED_REFERENCE") inline val FLAG_SUPPORTED_MODES_DEVICE_OWNER: Int get() = DevicePolicyManager.FLAG_SUPPORTED_MODES_DEVICE_OWNER } diff --git a/src/com/android/onboarding/contracts/provisioning/FinalizationInsideSuwContract.kt b/src/com/android/onboarding/contracts/provisioning/FinalizationInsideSuwContract.kt deleted file mode 100644 index 64d90e0..0000000 --- a/src/com/android/onboarding/contracts/provisioning/FinalizationInsideSuwContract.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.android.onboarding.contracts.provisioning - -import android.content.Context -import android.content.Intent -import com.android.onboarding.common.MANAGED_PROVISIONING -import com.android.onboarding.contracts.ContractResult -import com.android.onboarding.contracts.IntentScope -import com.android.onboarding.contracts.OnboardingActivityApiContract -import com.android.onboarding.contracts.ScopedIntentSerializer -import com.android.onboarding.contracts.annotations.OnboardingNode -import com.android.onboarding.contracts.setupwizard.SuwArguments -import com.android.onboarding.contracts.setupwizard.SuwArgumentsSerializer -import com.android.onboarding.contracts.setupwizard.WithOptionalSuwArguments -import javax.inject.Inject - -data class FinalizationInsideSuwArguments( - override val suwArguments: SuwArguments?, -) : WithOptionalSuwArguments - -/** - * Result is propagated from the child activities the activity described by this contract launches. - * - * The activity uses [ProvisioningParams], however they are fetched from the FS rather than intent - * extras. - */ -@OnboardingNode( - component = MANAGED_PROVISIONING, - name = "FinalizationInsideSuw", - hasUi = OnboardingNode.HasUi.YES -) -class FinalizationInsideSuwContract -@Inject -constructor(val suwArgumentsSerializer: SuwArgumentsSerializer) : - OnboardingActivityApiContract<FinalizationInsideSuwArguments, ContractResult>(), - ScopedIntentSerializer<FinalizationInsideSuwArguments> { - override fun performCreateIntent(context: Context, arg: FinalizationInsideSuwArguments): Intent = - Intent(ACTIONS.ACTION_ROLE_HOLDER_PROVISION_FINALIZATION).also { write(it, arg) } - - override fun IntentScope.write(value: FinalizationInsideSuwArguments) { - value.suwArguments?.let { write(suwArgumentsSerializer, it) } - } - - override fun IntentScope.read(): FinalizationInsideSuwArguments = - FinalizationInsideSuwArguments( - suwArguments = read(suwArgumentsSerializer), - ) - - override fun performExtractArgument(intent: Intent): FinalizationInsideSuwArguments = read(intent) - - override fun performParseResult(result: ContractResult): ContractResult = result - - override fun performSetResult(result: ContractResult): ContractResult = result -} diff --git a/src/com/android/onboarding/contracts/provisioning/ManagedDeviceProvisioningArguments.kt b/src/com/android/onboarding/contracts/provisioning/ManagedDeviceProvisioningArguments.kt deleted file mode 100644 index d8aede0..0000000 --- a/src/com/android/onboarding/contracts/provisioning/ManagedDeviceProvisioningArguments.kt +++ /dev/null @@ -1,545 +0,0 @@ -package com.android.onboarding.contracts.provisioning - -import android.app.admin.DevicePolicyManager -import android.content.ComponentName -import android.content.Intent -import android.os.PersistableBundle -import android.webkit.URLUtil -import com.android.onboarding.contracts.IntentSerializer -import com.android.onboarding.contracts.provisioning.DeviceAdminChecksum.PackageChecksum -import com.android.onboarding.contracts.provisioning.DeviceAdminChecksum.SignatureChecksum -import com.android.onboarding.contracts.provisioning.WifiSecurityType.EAP -import com.android.onboarding.contracts.provisioning.WifiSecurityType.NONE -import com.android.onboarding.contracts.provisioning.WifiSecurityType.WEP -import com.android.onboarding.contracts.provisioning.WifiSecurityType.WPA -import java.lang.IllegalArgumentException -import java.time.Instant -import java.util.Locale -import java.util.TimeZone - -/** The Checksum used to validate a Device Admin app. */ -sealed class DeviceAdminChecksum { - - abstract val checksum: String - - /** A base64 encoded SHA-256 hash of the Device Admin APK. */ - data class PackageChecksum(override val checksum: String) : DeviceAdminChecksum() - - /** A base64 encoded SHA-256 checksum of any signature applied to the Device Admin APK. */ - data class SignatureChecksum(override val checksum: String) : DeviceAdminChecksum() -} - -/** Arguments related to the device admin app to be provisioned. */ -data class DeviceAdmin( - /** - * The component name of the [android.app.admin.DeviceAdminReceiver] subclass which will be set as - * active. - */ - val componentName: ComponentName, - - /** - * Details related to the download of the Device Admin APK. - * - * <p>If this is not provided, then it will be assumed that the APK is already installed on the - * device - */ - val download: DeviceAdminDownload? = null, -) - -/** - * Information used to control the download of a Device Admin APK. - * - * @param location The URL to download the APK from - * @param cookieHeader An optional cookie header which will be used in the download request - * @param checksum The checksum used to validate the device admin APK - */ -data class DeviceAdminDownload( - val location: String, - val cookieHeader: String? = null, - val checksum: DeviceAdminChecksum -) - -/** The security of a Wifi Network. */ -sealed class WifiSecurityType { - /** An unsecured network. */ - object NONE : WifiSecurityType() - - /** - * A WPA-secured network - * - * @param password Plaintext representation of the password - */ - data class WPA(val password: String) : WifiSecurityType() - - /** - * A WEP-secured network - * - * @param password Plaintext representation of the password - */ - data class WEP(val password: String) : WifiSecurityType() - - /** - * A EAP-secured network - * - * @param password Plaintext representation of the password - */ - data class EAP(val password: String) : WifiSecurityType() -} - -/** - * Arguments for proxy settings when specifying wifi - * - * @param host The hostname of the proxy - * @param port The port of the proxy - * @param bypassHosts A list of hostnames for whom the proxy will be bypassed - * @param autoConfigUrl TODO: What does this do? - */ -data class ProxyArguments( - val host: String, - val port: Int, - val bypassHosts: List<String> = listOf(), - val autoConfigUrl: String? -) - -/** Arguments for specifying wifi connectivity */ -data class WifiArguments( - /** SSID of Wifi Network to be used during provisioning. */ - val ssid: String, - - /** {@code true} if the SSID represents a hidden network. */ - val hidden: Boolean = false, - - /** The security type of the network. */ - val security: WifiSecurityType, - - /** The proxy to use with this connection, if any. */ - val proxy: ProxyArguments? = null -) - -enum class ProvisioningTrigger(val intVal: Int) { - UNSPECIFIED(0), - CLOUD_ENROLLMENT(1), - QR_CODE(2), - PERSISTENT_DEVICE_OWNER(3), - MANAGED_ACCOUNT(4), - NFC(5) -} - -enum class ProvisioningMode(val intVal: Int) { - FULLY_MANAGED_DEVICE(1), - MANAGED_PROFILE(2); - - companion object { - fun fromIntVal(intVal: Int): ProvisioningMode = - when (intVal) { - FULLY_MANAGED_DEVICE.intVal -> FULLY_MANAGED_DEVICE - MANAGED_PROFILE.intVal -> MANAGED_PROFILE - else -> throw IllegalArgumentException("Unknown provisioning mode: $intVal") - } - } -} - -/** Arguments for provisioning a managed device. */ -data class ManagedDeviceProvisioningArguments( - - /** The Device Admin to be provisioned. */ - val deviceAdmin: DeviceAdmin, - - /** - * If not set to {@code true}, will require that a connection is made before proceeding with - * enterprise provisioning. - */ - val allowOfflineProvisioning: Boolean = false, - /** Indicates if mobile data should be used when wifi is unavailable. */ - val useMobileData: Boolean = false, - - /** Configures wifi connectivity. */ - val wifi: WifiArguments? = null, - /** - * The time which will be set as the device's local time upon provisioning complete. - * - * <p>If the provisioning request results in any mode other than Device Owner, this argument will - * be ignored. - */ - val localTime: Instant? = null, - - /** - * The time zone which will be set as the device's time zone upon provisioning complete. - * - * <p>If the provisioning request results in any mode other than Device Owner, this argument will - * be ignored. - */ - val timeZone: TimeZone? = null, - - /** - * The locale which will be set as the device's locale upon provisioning complete. - * - * <p>If the provisioning request results in any mode other than Device Owner, this argument will - * be ignored. - */ - val locale: Locale? = null, - - /** The name of the organization under management. */ - val organizationName: String? = null, - - /** - * The modes which are allowed to be provisioned into. - * - * <p>Defaults to all modes - */ - val allowedProvisioningModes: List<ProvisioningMode> = ProvisioningMode.values().asList(), - - /** - * Indicates if system apps should be left enabled after provisioning. - * - * <p>By default (if left to false), system apps will be disabled. - */ - val leaveAllSystemAppsEnabled: Boolean = false, - - /** Indicates if device encryption should be skipped. */ - val skipEncryption: Boolean = false, - - /** The action which triggered enterprise provisioning. */ - val provisioningTrigger: ProvisioningTrigger = ProvisioningTrigger.UNSPECIFIED, - - /** - * A URL to give more information to the user during provisioning. - * - * <p>This URL must use HTTPS. - */ - val supportUrl: String? = null, - - /** Arbitrary extras which will be passed to the Device Admin app upon provisioning. */ - val adminExtras: PersistableBundle? = null, - - /** Indicates if the provisioning education screens should be skipped. */ - val skipEducationScreens: Boolean = false -) { - init { - if (supportUrl != null && !URLUtil.isHttpsUrl(supportUrl)) { - throw IllegalArgumentException( - "If supplied, supportUrl must be a HTTPS URL. Was: $supportUrl" - ) - } - if (allowedProvisioningModes.isEmpty()) { - throw IllegalArgumentException("There must be at least one allowed provisioning mode") - } - } -} - -/** Argument Parser for [ManagedDeviceProvisioningArguments]. */ -class ManagedDeviceProvisioningArgumentsSerializer : - IntentSerializer<ManagedDeviceProvisioningArguments> { - override fun write(intent: Intent, value: ManagedDeviceProvisioningArguments) { - encodeDeviceAdmin(intent, value.deviceAdmin) - intent.putExtra(EXTRAS.EXTRA_PROVISIONING_ALLOW_OFFLINE, value.allowOfflineProvisioning) - intent.putExtra(EXTRAS.EXTRA_PROVISIONING_USE_MOBILE_DATA, value.useMobileData) - if (value.wifi != null) { - encodeWifi(intent, value.wifi) - } - intent.putExtra( - DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, - value.leaveAllSystemAppsEnabled - ) - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, value.skipEncryption) - intent.putExtra(EXTRAS.EXTRA_PROVISIONING_TRIGGER, value.provisioningTrigger.intVal) - if (value.localTime != null) { - intent.putExtra(EXTRAS.EXTRA_PROVISIONING_LOCAL_TIME, value.localTime.toEpochMilli()) - } - if (value.timeZone != null) { - intent.putExtra(EXTRAS.EXTRA_PROVISIONING_TIME_ZONE, value.timeZone.toZoneId().id) - } - if (value.locale != null) { - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_LOCALE, value.locale.toLanguageTag()) - } - if (value.organizationName != null) { - intent.putExtra(EXTRAS.EXTRA_PROVISIONING_ORGANIZATION_NAME, value.organizationName) - } - if (value.supportUrl != null) { - intent.putExtra(EXTRAS.EXTRA_PROVISIONING_SUPPORT_URL, value.supportUrl) - } - if (value.adminExtras != null) { - intent.putExtra(EXTRAS.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, value.adminExtras) - } - intent.putIntegerArrayListExtra( - EXTRAS.EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES, - ArrayList(value.allowedProvisioningModes.map { it.intVal }.toList()) - ) - intent.putExtra(EXTRAS.EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS, value.skipEducationScreens) - } - - private fun encodeWifi(intent: Intent, wifi: WifiArguments) { - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID, wifi.ssid) - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN, wifi.hidden) - encodeWifiSecurity(intent, wifi.security) - if (wifi.proxy != null) { - encodeWifiProxy(intent, wifi.proxy) - } - } - - private fun encodeWifiProxy(intent: Intent, proxy: ProxyArguments) { - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST, proxy.host) - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT, proxy.port) - if (proxy.bypassHosts.isNotEmpty()) { - intent.putExtra( - DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS, - proxy.bypassHosts.joinToString(",") - ) - } - if (proxy.autoConfigUrl != null) { - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL, proxy.autoConfigUrl) - } - } - - private fun encodeWifiSecurity(intent: Intent, security: WifiSecurityType) { - when (security) { - is WPA -> { - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, "WPA") - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD, security.password) - } - is WEP -> { - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, "WEP") - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD, security.password) - } - is EAP -> { - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, "EAP") - intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD, security.password) - } - is NONE -> intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, "NONE") - } - } - - private fun encodeDeviceAdmin(intent: Intent, deviceAdmin: DeviceAdmin) { - intent.putExtra( - DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, - deviceAdmin.componentName - ) - - if (deviceAdmin.download != null) { - encodeDeviceAdminDownload(intent, deviceAdmin.download) - } - } - - private fun encodeDeviceAdminDownload(intent: Intent, download: DeviceAdminDownload) { - intent.putExtra( - DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION, - download.location - ) - if (download.cookieHeader != null) { - intent.putExtra( - DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER, - download.cookieHeader - ) - } - when (download.checksum) { - is PackageChecksum -> - intent.putExtra( - DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM, - download.checksum.checksum - ) - is SignatureChecksum -> - intent.putExtra( - DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM, - download.checksum.checksum - ) - } - } - - override fun read(intent: Intent) = - ManagedDeviceProvisioningArguments( - deviceAdmin = extractDeviceAdmin(intent), - allowOfflineProvisioning = - intent.getBooleanExtra(EXTRAS.EXTRA_PROVISIONING_ALLOW_OFFLINE, false), - useMobileData = intent.getBooleanExtra(EXTRAS.EXTRA_PROVISIONING_USE_MOBILE_DATA, false), - wifi = extractWifi(intent), - leaveAllSystemAppsEnabled = - intent.getBooleanExtra( - DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, - false - ), - skipEncryption = - intent.getBooleanExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, false), - provisioningTrigger = extractProvisioningTrigger(intent), - localTime = extractLocalTime(intent), - timeZone = extractTimeZone(intent), - locale = extractLocale(intent), - organizationName = intent.getStringExtra(EXTRAS.EXTRA_PROVISIONING_ORGANIZATION_NAME), - supportUrl = intent.getStringExtra(EXTRAS.EXTRA_PROVISIONING_SUPPORT_URL), - allowedProvisioningModes = - (intent - .getIntegerArrayListExtra(EXTRAS.EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES) - ?.map { ProvisioningMode.fromIntVal(it) }) - ?: ProvisioningMode.values().asList(), - adminExtras = - intent.getParcelableExtra(DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE) - as PersistableBundle?, - skipEducationScreens = - intent.getBooleanExtra(EXTRAS.EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS, false), - ) - - private fun extractProvisioningTrigger(intent: Intent): ProvisioningTrigger { - return when (val c = intent.getIntExtra(EXTRAS.EXTRA_PROVISIONING_TRIGGER, 0)) { - 0 -> ProvisioningTrigger.UNSPECIFIED - 1 -> ProvisioningTrigger.CLOUD_ENROLLMENT - 2 -> ProvisioningTrigger.QR_CODE - 3 -> ProvisioningTrigger.PERSISTENT_DEVICE_OWNER - 4 -> ProvisioningTrigger.MANAGED_ACCOUNT - 5 -> ProvisioningTrigger.NFC - else -> throw IllegalArgumentException("Unknown value for EXTRA_PROVISIONING_TRIGGER: $c") - } - } - - private fun extractLocale(intent: Intent): Locale? = - if (intent.hasExtra(DevicePolicyManager.EXTRA_PROVISIONING_LOCALE)) { - Locale.Builder() - // I don't know why this replacement is needed - it came from the existing - // ManagedProvisioning code - .setLanguageTag( - intent.getStringExtra(DevicePolicyManager.EXTRA_PROVISIONING_LOCALE)!!.replace("_", "-") - ) - .build() - } else { - null - } - - private fun extractTimeZone(intent: Intent): TimeZone? = - if (intent.hasExtra(DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE)) { - TimeZone.getTimeZone(intent.getStringExtra(DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE)) - } else { - null - } - - private fun extractLocalTime(intent: Intent): Instant? = - if (intent.hasExtra(DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME)) { - Instant.ofEpochMilli( - intent.getLongExtra(DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME, 0) - ) // Will never be 0 due to previous check - } else { - null - } - - private fun extractWifi(intent: Intent): WifiArguments? = - if (intent.hasExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID)) { - WifiArguments( - ssid = - intent.getStringExtra( - DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID - )!!, // Must exist due to previous check - hidden = intent.getBooleanExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN, false), - security = extractWifiSecurity(intent), - proxy = extractWifiProxy(intent) - ) - } else { - null - } - - private fun extractWifiProxy(intent: Intent): ProxyArguments? = - if (intent.hasExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST)) { - if (!intent.hasExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT)) { - throw IllegalArgumentException( - "Cannot specify EXTRA_PROVISIONING_WIFI_PROXY_HOST without EXTRA_PROVISIONING_WIFI_PROXY_PORT" - ) - } - - ProxyArguments( - host = - intent.getStringExtra( - DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST - )!!, // Must exist due to previous check - port = - intent.getIntExtra( - DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT, - 0 - ), // Will never be 0 due to previous check - bypassHosts = extractProxyBypassHosts(intent), - autoConfigUrl = intent.getStringExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL) - ) - } else { - null - } - - private fun extractProxyBypassHosts(intent: Intent): List<String> = - (intent.getStringExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS) ?: "").split( - "," - ) - - private fun extractWifiSecurity(intent: Intent): WifiSecurityType = - when ( - val type = intent.getStringExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE) - ) { - null -> NONE - "NONE" -> NONE - "WEP" -> WEP(password = extractWifiPassword(intent)) - "WPA" -> WPA(password = extractWifiPassword(intent)) - "EAP" -> EAP(password = extractWifiPassword(intent)) - else -> - throw IllegalArgumentException( - "Unknown value for EXTRA_PROVISIONING_WIFI_SECURITY_TYPE: $type" - ) - } - - private fun extractWifiPassword(intent: Intent): String = - intent.getStringExtra(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD) - ?: throw IllegalArgumentException("EXTRA_PROVISIONING_WIFI_PASSWORD must be specified") - - private fun extractDeviceAdmin(intent: Intent): DeviceAdmin = - DeviceAdmin( - componentName = extractDeviceAdminComponentName(intent), - download = extractDeviceAdminDownload(intent) - ) - - private fun extractDeviceAdminDownload(intent: Intent): DeviceAdminDownload? = - if ( - intent.hasExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION) - ) { - DeviceAdminDownload( - location = - intent.getStringExtra( - DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION - )!!, // Must exist due to previous check - cookieHeader = - intent.getStringExtra( - DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER - ), - checksum = extractDeviceAdminChecksum(intent) - ) - } else { - null - } - - private fun extractDeviceAdminComponentName(intent: Intent): ComponentName { - return intent.getParcelableExtra( - DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME - ) - ?: throw IllegalArgumentException( - "Intent is missing EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME extra" - ) - } - - private fun extractDeviceAdminChecksum(intent: Intent): DeviceAdminChecksum { - val packageChecksum = - intent.getStringExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM) - val signatureChecksum = - intent.getStringExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM) - - return if (packageChecksum != null) { - if (signatureChecksum != null) { - throw IllegalArgumentException( - "Intent has both " + - "EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM and " + - "EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM" - ) - } - - PackageChecksum(checksum = packageChecksum) - } else if (signatureChecksum != null) { - SignatureChecksum(checksum = signatureChecksum) - } else { - throw IllegalArgumentException( - "Intent has no " + - "EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM or " + - "EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM" - ) - } - } -} diff --git a/src/com/android/onboarding/contracts/provisioning/PreProvisioningViaNfcContract.kt b/src/com/android/onboarding/contracts/provisioning/PreProvisioningViaNfcContract.kt deleted file mode 100644 index 7b45fac..0000000 --- a/src/com/android/onboarding/contracts/provisioning/PreProvisioningViaNfcContract.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.android.onboarding.contracts.provisioning - -import android.content.Context -import android.content.Intent -import android.nfc.NfcAdapter -import android.os.Parcelable -import com.android.onboarding.common.MANAGED_PROVISIONING -import com.android.onboarding.contracts.IntentScope -import com.android.onboarding.contracts.ScopedIntentSerializer -import com.android.onboarding.contracts.VoidOnboardingActivityApiContract -import com.android.onboarding.contracts.annotations.OnboardingNode -import javax.inject.Inject - -data class PreProvisioningViaNfcArguments( - val ndefMessages: Array<Parcelable>, -) - -@OnboardingNode( - component = MANAGED_PROVISIONING, - name = "PreProvisioningViaNfc", - hasUi = OnboardingNode.HasUi.YES -) -class PreProvisioningViaNfcContract @Inject constructor() : - VoidOnboardingActivityApiContract<PreProvisioningViaNfcArguments>(), - ScopedIntentSerializer<PreProvisioningViaNfcArguments> { - - override fun performCreateIntent(context: Context, arg: PreProvisioningViaNfcArguments): Intent = - Intent(NfcAdapter.ACTION_NDEF_DISCOVERED).also { write(it, arg) } - - override fun performExtractArgument(intent: Intent): PreProvisioningViaNfcArguments = read(intent) - - override fun IntentScope.write(value: PreProvisioningViaNfcArguments) { - this[NfcAdapter.EXTRA_NDEF_MESSAGES] = value.ndefMessages - } - - override fun IntentScope.read(): PreProvisioningViaNfcArguments = - PreProvisioningViaNfcArguments( - ndefMessages = parcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES) - ) -} diff --git a/src/com/android/onboarding/contracts/provisioning/ProvisionOfflineOrViaRoleHolderApiContract.kt b/src/com/android/onboarding/contracts/provisioning/ProvisionOfflineOrViaRoleHolderApiContract.kt deleted file mode 100644 index 705c3ff..0000000 --- a/src/com/android/onboarding/contracts/provisioning/ProvisionOfflineOrViaRoleHolderApiContract.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.android.onboarding.contracts.provisioning - -import android.content.Context -import android.content.Intent -import com.android.onboarding.common.MANAGED_PROVISIONING -import com.android.onboarding.contracts.VoidOnboardingActivityApiContract -import com.android.onboarding.contracts.annotations.OnboardingNode - -// From DevicePolicyManager -private const val ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = - "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE" - -val PARSER = ManagedDeviceProvisioningArgumentsSerializer() - -/** - * Starts the enterprise provisioning flow, either by updating and using the Device Policy Manager - * Role Holder or by offline provisioning if the role holder is not available or - * [ManagedDeviceProvisioningArguments.allowOfflineProvisioning] is set to true. - * - * <p>During device owner provisioning, a device admin app is downloaded and set as the owner of the - * device. A device owner has full control over the device. The device owner can not be modified by - * the user and the only way of resetting the device is via factory reset. - * - * <p>If there is no internet connection, and - * [ManagedDeviceProvisioningArguments.allowOfflineProvisioning] is not set to {code true}, then - * (TODO: How do we expose the failure?) - * - * <p>The DPMRH updater will be launched to allow updating of the Device Policy Manager Role Holder. - * - * <p>After interacting with the updater, (TODO: We will pass control to the role holder using X - * contract) - * - * <p>This API should only be called on a device which is not yet provisioned (TODO: Define what - * this means) - */ -@OnboardingNode( - component = MANAGED_PROVISIONING, - name = "ProvisionOfflineOrViaRoleHolder", - hasUi = OnboardingNode.HasUi.YES_TEMPORARILY -) -class ProvisionOfflineOrViaRoleHolderApiContract : - VoidOnboardingActivityApiContract<ManagedDeviceProvisioningArguments>() { - override fun performCreateIntent( - context: Context, - arg: ManagedDeviceProvisioningArguments - ): Intent { - val intent = Intent(ACTIONS.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE) - PARSER.write(intent, arg) - return intent - } - - override fun performExtractArgument(intent: Intent): ManagedDeviceProvisioningArguments = - PARSER.read(intent) -} diff --git a/src/com/android/onboarding/contracts/provisioning/ProvisioningMode.kt b/src/com/android/onboarding/contracts/provisioning/ProvisioningMode.kt new file mode 100644 index 0000000..fcb0f8f --- /dev/null +++ b/src/com/android/onboarding/contracts/provisioning/ProvisioningMode.kt @@ -0,0 +1,26 @@ +package com.android.onboarding.contracts.provisioning + +import android.app.admin.DevicePolicyManager +import android.os.Build +import androidx.annotation.RequiresApi + +@RequiresApi(Build.VERSION_CODES.Q) +enum class ProvisioningMode(val id: Int) { + FullyManagedDevice(DevicePolicyManager.PROVISIONING_MODE_FULLY_MANAGED_DEVICE), + + ManagedProfile(DevicePolicyManager.PROVISIONING_MODE_MANAGED_PROFILE), + + ManagedProfileOnPersonalDevice( + DevicePolicyManager.PROVISIONING_MODE_MANAGED_PROFILE_ON_PERSONAL_DEVICE + ); + + companion object { + operator fun invoke(id: Int): ProvisioningMode = + when (id) { + FullyManagedDevice.id -> FullyManagedDevice + ManagedProfile.id -> ManagedProfile + ManagedProfileOnPersonalDevice.id -> ManagedProfileOnPersonalDevice + else -> error("Unknown ProvisioningMode(id=$id)") + } + } +} diff --git a/src/com/android/onboarding/contracts/provisioning/ProvisioningTrigger.kt b/src/com/android/onboarding/contracts/provisioning/ProvisioningTrigger.kt new file mode 100644 index 0000000..416dc81 --- /dev/null +++ b/src/com/android/onboarding/contracts/provisioning/ProvisioningTrigger.kt @@ -0,0 +1,30 @@ +package com.android.onboarding.contracts.provisioning + +import android.app.admin.DevicePolicyManager + +enum class ProvisioningTrigger(val id: Int) { + @Suppress("UNRESOLVED_REFERENCE") + Unspecified(DevicePolicyManager.PROVISIONING_TRIGGER_UNSPECIFIED), + @Suppress("UNRESOLVED_REFERENCE") + CloudEnrollment(DevicePolicyManager.PROVISIONING_TRIGGER_CLOUD_ENROLLMENT), + @Suppress("UNRESOLVED_REFERENCE") QR(DevicePolicyManager.PROVISIONING_TRIGGER_QR_CODE), + @Deprecated("Deprecated in DevicePolicyManager") + @Suppress("UNRESOLVED_REFERENCE") + PersistentDeviceOwner(DevicePolicyManager.PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER), + @Suppress("UNRESOLVED_REFERENCE") + ManagedAccount(DevicePolicyManager.PROVISIONING_TRIGGER_MANAGED_ACCOUNT), + @Suppress("UNRESOLVED_REFERENCE") NFC(DevicePolicyManager.PROVISIONING_TRIGGER_NFC); + + companion object { + operator fun invoke(id: Int): ProvisioningTrigger = + when (id) { + Unspecified.id -> Unspecified + CloudEnrollment.id -> CloudEnrollment + QR.id -> QR + PersistentDeviceOwner.id -> PersistentDeviceOwner + ManagedAccount.id -> ManagedAccount + NFC.id -> NFC + else -> error("Unknown ProvisioningTrigger(id=$id)") + } + } +} diff --git a/src/com/android/onboarding/contracts/provisioning/RESULTS.kt b/src/com/android/onboarding/contracts/provisioning/RESULTS.kt new file mode 100644 index 0000000..11bf041 --- /dev/null +++ b/src/com/android/onboarding/contracts/provisioning/RESULTS.kt @@ -0,0 +1,9 @@ +package com.android.onboarding.contracts.provisioning + +import android.app.admin.DevicePolicyManager + +object RESULTS { + @get:Suppress("UNRESOLVED_REFERENCE") + inline val RESULT_UPDATE_ROLE_HOLDER: Int + get() = DevicePolicyManager.RESULT_UPDATE_ROLE_HOLDER +} diff --git a/src/com/android/onboarding/contracts/provisioning/managed/profile/Android.bp b/src/com/android/onboarding/contracts/provisioning/managed/profile/Android.bp deleted file mode 100644 index 9e3e4f0..0000000 --- a/src/com/android/onboarding/contracts/provisioning/managed/profile/Android.bp +++ /dev/null @@ -1,18 +0,0 @@ -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -android_library { - name: "android_onboarding.contracts.provisioning.managed.profile", - manifest: ":android_onboarding.AndroidManifest", - srcs: [ - "*.kt", - ], - dont_merge_manifests: true, - static_libs: [ - "android_onboarding.common", - "android_onboarding.contracts", - "android_onboarding.contracts.provisioning", - "android_onboarding.contracts.setupwizard", - ], -} diff --git a/src/com/android/onboarding/contracts/provisioning/managed/profile/ProvisionManagedProfileArguments.kt b/src/com/android/onboarding/contracts/provisioning/managed/profile/ProvisionManagedProfileArguments.kt deleted file mode 100644 index c15240e..0000000 --- a/src/com/android/onboarding/contracts/provisioning/managed/profile/ProvisionManagedProfileArguments.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.android.onboarding.contracts.provisioning.managed.profile - -import android.content.pm.ActivityInfo -import android.net.Uri -import com.android.onboarding.contracts.setupwizard.SuwArguments -import com.android.onboarding.contracts.setupwizard.WithSuwArguments - -/** */ -data class ProvisionManagedProfileArguments( - override val suwArguments: SuwArguments, - val trigger: Int, - val deviceAdminComponentName: ActivityInfo, - val deviceAdminSignatureChecksum: String, - val deviceAdminPackageDownloadLocation: Uri, -) : WithSuwArguments diff --git a/src/com/android/onboarding/contracts/provisioning/managed/profile/ProvisionManagedProfileArgumentsSerializer.kt b/src/com/android/onboarding/contracts/provisioning/managed/profile/ProvisionManagedProfileArgumentsSerializer.kt deleted file mode 100644 index b30fc75..0000000 --- a/src/com/android/onboarding/contracts/provisioning/managed/profile/ProvisionManagedProfileArgumentsSerializer.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.android.onboarding.contracts.provisioning.managed.profile - -import android.net.Uri -import com.android.onboarding.contracts.IntentScope -import com.android.onboarding.contracts.ScopedIntentSerializer -import com.android.onboarding.contracts.provisioning.EXTRAS -import com.android.onboarding.contracts.setupwizard.SuwArgumentsSerializer - -class ProvisionManagedProfileArgumentsSerializer( - private val suwArgumentsSerializer: SuwArgumentsSerializer = SuwArgumentsSerializer(), -) : ScopedIntentSerializer<ProvisionManagedProfileArguments> { - - override fun IntentScope.write(value: ProvisionManagedProfileArguments) { - intent.write(suwArgumentsSerializer, value.suwArguments) - intent["trigger"] = value.trigger - intent[EXTRAS.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME] = value.deviceAdminComponentName - intent[EXTRAS.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM] = - value.deviceAdminSignatureChecksum - intent[EXTRAS.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION] = - value.deviceAdminPackageDownloadLocation - } - - override fun IntentScope.read(): ProvisionManagedProfileArguments = - ProvisionManagedProfileArguments( - suwArguments = intent.read(suwArgumentsSerializer), - trigger = intent.intExtra(EXTRAS.EXTRA_PROVISIONING_TRIGGER), - deviceAdminComponentName = - intent.parcelableExtra(EXTRAS.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME), - deviceAdminSignatureChecksum = - intent.stringExtra(EXTRAS.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM), - deviceAdminPackageDownloadLocation = - intent - .stringExtra(EXTRAS.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION) - .let(Uri::parse), - ) -} diff --git a/src/com/android/onboarding/contracts/provisioning/managed/profile/ProvisionManagedProfileContract.kt b/src/com/android/onboarding/contracts/provisioning/managed/profile/ProvisionManagedProfileContract.kt deleted file mode 100644 index 5d9bee8..0000000 --- a/src/com/android/onboarding/contracts/provisioning/managed/profile/ProvisionManagedProfileContract.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.android.onboarding.contracts.provisioning.managed.profile - -import android.content.Context -import android.content.Intent -import com.android.onboarding.common.MANAGED_PROVISIONING -import com.android.onboarding.contracts.VoidOnboardingActivityApiContract -import com.android.onboarding.contracts.annotations.OnboardingNode -import com.android.onboarding.contracts.provisioning.ACTIONS - -/** - * | Activity | com.android.managedprovisioning.preprovisioning.PreProvisioningActivity | - * |----------|-------------------------------------------------------------------------| - * | | | - */ -@OnboardingNode( - component = MANAGED_PROVISIONING, - name = "ProvisionManagedProfile", - hasUi = OnboardingNode.HasUi.YES -) -class ProvisionManagedProfileContract( - private val serializer: ProvisionManagedProfileArgumentsSerializer = - ProvisionManagedProfileArgumentsSerializer(), -) : VoidOnboardingActivityApiContract<ProvisionManagedProfileArguments>() { - - override fun performCreateIntent( - context: Context, - arg: ProvisionManagedProfileArguments - ): Intent = Intent(ACTIONS.ACTION_PROVISION_MANAGED_PROFILE).also { serializer.write(it, arg) } - - override fun performExtractArgument(intent: Intent): ProvisionManagedProfileArguments = - serializer.read(intent) -} diff --git a/src/com/android/onboarding/nodes/OnboardingGraphLog.kt b/src/com/android/onboarding/nodes/OnboardingGraphLog.kt index b100923..c34966d 100644 --- a/src/com/android/onboarding/nodes/OnboardingGraphLog.kt +++ b/src/com/android/onboarding/nodes/OnboardingGraphLog.kt @@ -540,13 +540,12 @@ interface OnboardingGraphLog { companion object { - private fun extractNodeNameFromClass(nodeClass: Class<*>) = - nodeClass.getAnnotation(OnboardingNode::class.java)?.name?.takeIf { - it.length <= MAX_NODE_NAME_LENGTH - } - ?: throw IllegalArgumentException( - "All nodes must be annotated @OnboardingNode and have a valid node name" - ) + private fun extractNodeNameFromClass(nodeClass: Class<*>): String = + nodeClass.getAnnotation(OnboardingNode::class.java)?.name?.also { + require(it.length <= MAX_NODE_NAME_LENGTH) { + "Node name length (${it.length}) exceeds maximum length of $MAX_NODE_NAME_LENGTH characters" + } + } ?: throw IllegalArgumentException("All nodes must be annotated @OnboardingNode") fun deserialize(str: String): OnboardingEvent { val proto = OnboardingProtos.LogProto.parseFrom(BaseEncoding.base64().decode(str)) |