-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
385 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 47 additions & 1 deletion
48
distribution/distribution/src/main/kotlin/com/emergetools/distribution/Distribution.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,51 @@ | ||
package com.emergetools.distribution | ||
|
||
import android.content.Context | ||
import com.emergetools.distribution.internal.DistributionInternal | ||
import okhttp3.OkHttpClient | ||
|
||
/** | ||
* The public Android SDK for Emerge Tools Build Distribution. | ||
*/ | ||
object Distribution { | ||
// Left intentionally blank. | ||
/** | ||
* Initialize build distribution. This should be called once in each process. | ||
* This method may be called from any thread with a Looper. It is safe to | ||
* call this from the main thread. Options may be passed if you want to override the default values. | ||
* @param context Android context | ||
* @param options Override distribution settings | ||
*/ | ||
fun init(context: Context, options: DistributionOptions? = null) { | ||
DistributionInternal.init(context, options ?: DistributionOptions()) | ||
} | ||
|
||
/** | ||
* | ||
*/ | ||
suspend fun checkForUpdate(context: Context, apiKey: String? = null): UpdateStatus { | ||
return DistributionInternal.checkForUpdate(context, apiKey) | ||
} | ||
} | ||
|
||
/** | ||
* Optional settings for build distribution. | ||
*/ | ||
data class DistributionOptions( | ||
/** | ||
* Pass an existing OkHttpClient. If null Distribution will create it's own OkHttpClient. | ||
* This allows reuse of existing OkHttpClient thread pools etc. | ||
*/ | ||
val okHttpClient: OkHttpClient? = null, | ||
) | ||
|
||
sealed class UpdateStatus { | ||
class Error(val message: String) : UpdateStatus() | ||
class NewRelease( | ||
val id: String, | ||
val tag: String, | ||
val version: String, | ||
val appId: String, | ||
val downloadUrl: String | ||
) : UpdateStatus() | ||
object UpToDate : UpdateStatus() | ||
} |
16 changes: 16 additions & 0 deletions
16
...tion/distribution/src/main/kotlin/com/emergetools/distribution/DistributionInitializer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.emergetools.distribution | ||
|
||
import android.content.Context | ||
import androidx.startup.Initializer | ||
import androidx.work.WorkManagerInitializer | ||
|
||
class DistributionInitializer : Initializer<Distribution> { | ||
override fun create(context: Context): Distribution { | ||
Distribution.init(context) | ||
return Distribution | ||
} | ||
|
||
override fun dependencies(): List<Class<out Initializer<*>>> { | ||
return listOf(WorkManagerInitializer::class.java) | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
distribution/distribution/src/main/kotlin/com/emergetools/distribution/internal/Constants.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package com.emergetools.distribution.internal | ||
|
||
internal const val TAG = "Distribution" |
174 changes: 174 additions & 0 deletions
174
...istribution/src/main/kotlin/com/emergetools/distribution/internal/DistributionInternal.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
// We catch very generic exceptions on purpose. We need to avoid crashing the app. | ||
@file:Suppress("TooGenericExceptionCaught") | ||
|
||
package com.emergetools.distribution.internal | ||
|
||
import android.content.Context | ||
import android.os.Handler | ||
import android.os.Looper | ||
import android.util.Log | ||
import com.emergetools.distribution.DistributionOptions | ||
import com.emergetools.distribution.UpdateStatus | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import kotlinx.coroutines.suspendCancellableCoroutine | ||
import kotlinx.coroutines.withContext | ||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.SerializationException | ||
import kotlinx.serialization.json.Json | ||
import okhttp3.Call | ||
import okhttp3.Callback | ||
import okhttp3.HttpUrl | ||
import okhttp3.OkHttpClient | ||
import okhttp3.Request | ||
import okhttp3.Response | ||
import okhttp3.internal.closeQuietly | ||
import java.io.IOException | ||
import kotlin.coroutines.resumeWithException | ||
|
||
@Serializable | ||
internal data class CheckForUpdatesMessageResult( | ||
val message: String | ||
) | ||
|
||
private inline fun <reified T> tryDecode(s: String): T? { | ||
return try { | ||
Json.decodeFromString<T>(s) | ||
} catch (_: SerializationException) { | ||
null | ||
} catch (_: IllegalArgumentException) { | ||
null | ||
} | ||
} | ||
|
||
private fun decodeResult(body: String?): UpdateStatus { | ||
if (body == null) { | ||
return UpdateStatus.Error("Empty response from server") | ||
} | ||
|
||
val message = tryDecode<CheckForUpdatesMessageResult>(body) | ||
if (message !== null) { | ||
return UpdateStatus.Error(message.message) | ||
} | ||
|
||
return UpdateStatus.Error("Unexpected response $body") | ||
} | ||
|
||
private class State(val handler: Handler, private var okHttpClient: OkHttpClient?) { | ||
|
||
@Synchronized | ||
fun getOkHttpClient(): OkHttpClient { | ||
var client = okHttpClient | ||
if (client == null) { | ||
client = OkHttpClient() | ||
okHttpClient = client | ||
} | ||
return client | ||
} | ||
} | ||
|
||
object DistributionInternal { | ||
private var state: State? = null | ||
|
||
@Synchronized | ||
@Suppress("unused") | ||
fun init(context: Context, options: DistributionOptions) { | ||
if (state != null) { | ||
Log.e(TAG, "Distribution already initialized, ignoring Distribution.init().") | ||
return | ||
} | ||
|
||
val looper = Looper.myLooper() | ||
if (looper == null) { | ||
Log.e(TAG, "Distribution.init() must be called from a thread with a Looper.") | ||
return | ||
} | ||
|
||
val handler = Handler(looper) | ||
state = State(handler, options.okHttpClient) | ||
} | ||
|
||
private fun getState(): State? { | ||
var theState: State? | ||
synchronized(this) { | ||
theState = state | ||
} | ||
return theState | ||
} | ||
|
||
suspend fun checkForUpdate(context: Context, apiKey: String?): UpdateStatus { | ||
try { | ||
val state = getState() | ||
if (state == null) { | ||
Log.e(TAG, "Build distribution not initialized") | ||
return UpdateStatus.Error("Build distribution not initialized") | ||
} | ||
if (apiKey == null) { | ||
Log.e(TAG, "No API key available") | ||
return UpdateStatus.Error("No API key available") | ||
} | ||
return doCheckForUpdate(context, state, apiKey) | ||
} catch (e: Exception) { | ||
Log.e(TAG, "Error: $e") | ||
return UpdateStatus.Error("Error: $e") | ||
} | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
private suspend fun doCheckForUpdate(context: Context, state: State, apiKey: String): UpdateStatus { | ||
// Despite the name context.packageName is the actually the application id. | ||
val applicationId = context.packageName | ||
|
||
val url = HttpUrl.Builder().apply { | ||
scheme("https") | ||
host("api.emergetools.com") | ||
addPathSegment("distribution") | ||
addPathSegment("checkForUpdates") | ||
addQueryParameter("apiKey", apiKey) | ||
addQueryParameter("binaryIdentifier", "polar bears") | ||
addQueryParameter("appId", applicationId) | ||
addQueryParameter("platform", "android") | ||
}.build() | ||
|
||
val request = Request.Builder().apply { | ||
url(url) | ||
}.build() | ||
|
||
val client = state.getOkHttpClient() | ||
val call = client.newCall(request) | ||
executeAsync(call).use { response -> | ||
return withContext(Dispatchers.IO) { | ||
val body = response.body?.string() | ||
println(body) | ||
return@withContext decodeResult(body) | ||
} | ||
} | ||
} | ||
|
||
@ExperimentalCoroutinesApi // resume with a resource cleanup. | ||
suspend fun executeAsync(call: Call): Response = | ||
suspendCancellableCoroutine { continuation -> | ||
continuation.invokeOnCancellation { | ||
call.cancel() | ||
} | ||
call.enqueue( | ||
object : Callback { | ||
override fun onFailure( | ||
call: Call, | ||
e: IOException, | ||
) { | ||
continuation.resumeWithException(e) | ||
} | ||
|
||
override fun onResponse( | ||
call: Call, | ||
response: Response, | ||
) { | ||
continuation.resume(response) { | ||
response.closeQuietly() | ||
} | ||
} | ||
}, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Oops, something went wrong.