From c289b475a344e1250869732c74703ae68f7ea841 Mon Sep 17 00:00:00 2001 From: Hector Dearman Date: Fri, 25 Oct 2024 11:40:41 +0100 Subject: [PATCH] [distribution] Small API tweaks (#291) --- .../distribution/src/main/AndroidManifest.xml | 3 ++ .../emergetools/distribution/Distribution.kt | 37 +++++++++++++++ .../internal/DistributionInternal.kt | 45 +++++++++++++++++-- distribution/sample/app/build.gradle.kts | 1 + 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/distribution/distribution/src/main/AndroidManifest.xml b/distribution/distribution/src/main/AndroidManifest.xml index 84e039d7..08b5c740 100644 --- a/distribution/distribution/src/main/AndroidManifest.xml +++ b/distribution/distribution/src/main/AndroidManifest.xml @@ -12,6 +12,9 @@ + { + return DistributionInternal.checkForUpdateCompletableFuture(context) + } + /** * Download the provided update. */ @@ -62,15 +74,40 @@ data class DistributionOptions( val tag: String? = null, ) +/** + * An available update that it is possible to upgrade to. + */ @Serializable data class UpdateInfo( + /** + * The Emerge id for the update build. + */ val id: String, + + /** + * The Emerge tag for the update build. + */ val tag: String, + + /** + * The version of the update. + */ val version: String, + + /** + * The package name of the update app. + */ val appId: String, + + /** + * A signed URL for downloading the update. + */ val downloadUrl: String, ) +/** + * The result of checking for an update. + */ sealed class UpdateStatus { class Error(val message: String) : UpdateStatus() class NewRelease(val info: UpdateInfo) : UpdateStatus() diff --git a/distribution/distribution/src/main/kotlin/com/emergetools/distribution/internal/DistributionInternal.kt b/distribution/distribution/src/main/kotlin/com/emergetools/distribution/internal/DistributionInternal.kt index fe3fad11..6da76dc6 100644 --- a/distribution/distribution/src/main/kotlin/com/emergetools/distribution/internal/DistributionInternal.kt +++ b/distribution/distribution/src/main/kotlin/com/emergetools/distribution/internal/DistributionInternal.kt @@ -12,8 +12,12 @@ import android.util.Log import com.emergetools.distribution.DistributionOptions import com.emergetools.distribution.UpdateInfo import com.emergetools.distribution.UpdateStatus +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable @@ -27,9 +31,11 @@ import okhttp3.Request import okhttp3.Response import okhttp3.internal.closeQuietly import java.io.IOException +import java.util.concurrent.CompletableFuture import kotlin.coroutines.resumeWithException private const val MANIFEST_TAG_API_KEY = "com.emergetools.distribution.API_KEY" +private const val MANIFEST_TAG_TAG_KEY = "com.emergetools.distribution.TAG" internal fun getApiKey(metadata: Bundle): String? { val apiKey = metadata.getString(MANIFEST_TAG_API_KEY, null) @@ -39,6 +45,14 @@ internal fun getApiKey(metadata: Bundle): String? { return apiKey } +internal fun getTag(metadata: Bundle): String? { + val tag = metadata.getString(MANIFEST_TAG_TAG_KEY, null) + if (tag == "") { + return null + } + return tag +} + @Serializable internal data class CheckForUpdatesMessageResult( val message: String @@ -79,7 +93,7 @@ private fun decodeResult(body: String?): UpdateStatus { private class State( val handler: Handler, - val tag: String, + val tag: String?, val apiKey: String?, private var okHttpClient: OkHttpClient? ) { @@ -113,16 +127,28 @@ object DistributionInternal { val handler = Handler(looper) - // apiKey may be null if + // apiKey may be null if build distribution is disabled. val metaData = context.packageManager.getApplicationInfo( context.packageName, PackageManager.GET_META_DATA ).metaData val apiKey = getApiKey(metaData) - val tag = options.tag ?: "release" + // tag: + // - defaults to "" + // - can be set from the manifest + // - and overridden by init argument (via state) + var tag = "" + val manifestTag = getTag(metaData) + if (manifestTag != null) { + tag = manifestTag + } + val optionsTag = options.tag + if (optionsTag != null) { + tag = optionsTag + } - state = State(handler, tag, apiKey, options.okHttpClient) + state = State(handler = handler, tag = tag, apiKey = apiKey, okHttpClient = options.okHttpClient) } private fun getState(): State? { @@ -133,6 +159,17 @@ object DistributionInternal { return theState } + @OptIn(DelicateCoroutinesApi::class) + fun checkForUpdateCompletableFuture(context: Context): CompletableFuture { + // GlobalScope is correct here since we're converting this to a CompletableFuture immediately. + // The calling code is opting out of structured concurrency and takes responsibility for waiting + // for (or canceling) the future. + val job = GlobalScope.async { + return@async checkForUpdate(context) + } + return job.asCompletableFuture() + } + suspend fun checkForUpdate(context: Context): UpdateStatus { try { val state = getState() diff --git a/distribution/sample/app/build.gradle.kts b/distribution/sample/app/build.gradle.kts index 88564567..5bee0a95 100644 --- a/distribution/sample/app/build.gradle.kts +++ b/distribution/sample/app/build.gradle.kts @@ -31,6 +31,7 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" manifestPlaceholders["emerge.distribution.apiKey"] = "" + manifestPlaceholders["emerge.distribution.tag"] = "" vectorDrawables { useSupportLibrary = true