mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-13 03:51:10 +00:00
fix: offer updates to any copy signed with the release key, local builds included
This commit is contained in:
1 parent
02f0699b9c
commit
a800902aa5
5 files changed
+40
-9
No files matched your search
@@ -55,6 +55,8 @@ def sourceCommit = providers.environmentVariable('DROIDDECK_SOURCE_COMMIT').getO
|
||||
?: providers.environmentVariable('GITHUB_SHA').getOrNull() ?: gitValue(['rev-parse', 'HEAD']) ?: ''
|
||||
def prNumber = (providers.environmentVariable('DROIDDECK_PR_NUMBER').getOrNull() ?: '0').with { it.isInteger() ? it : '0' }
|
||||
def ciBuild = providers.environmentVariable('GITHUB_ACTIONS').getOrNull() == 'true'
|
||||
// The release key's certificate: a build signed with it, from CI or a computer, updates in place.
|
||||
def releaseSigner = rootProject.file('keystore/release-signer.sha256').text.trim()
|
||||
// When the source was committed, for "built 3 hours ago": fixed per commit, so it costs no rebuilds.
|
||||
def commitTime = (gitValue(['show', '-s', '--format=%ct', 'HEAD']) ?: '0').with { it.isLong() ? it : '0' }
|
||||
// tools/release/variants.txt as "package=suffix;...", so the updater picks the apk of the package it runs as.
|
||||
@@ -83,6 +85,7 @@ android {
|
||||
buildConfigField "String", "SOURCE_COMMIT", "\"${sourceCommit}\""
|
||||
buildConfigField "int", "PR_NUMBER", "${prNumber}"
|
||||
buildConfigField "boolean", "CI_BUILD", "${ciBuild}"
|
||||
buildConfigField "String", "RELEASE_SIGNER", "\"${releaseSigner}\""
|
||||
buildConfigField "long", "COMMIT_TIME", "${commitTime}L * 1000L"
|
||||
buildConfigField "String", "VARIANT_SUFFIXES", "\"${variantSuffixes}\""
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ class App : Application() {
|
||||
// Before anything else touches the session prefs: a new install's first read decides it.
|
||||
com.droiddeck.launcher.session.SessionPrefs.settleDeckModeDefault(this)
|
||||
com.droiddeck.launcher.session.SessionPrefs.settleGpuDriverMode(this)
|
||||
com.droiddeck.launcher.update.AppUpdates.init(this)
|
||||
GpuClockPin.clearLeftover(this)
|
||||
BwrapSpawner.start(this)
|
||||
}
|
||||
|
||||
@@ -140,13 +140,13 @@ private fun StatusPanel(s: FrontEndState, u: UpdatesState, ua: UpdatesActions, m
|
||||
val pal = LocalPalette.current
|
||||
val catalog = u.catalog
|
||||
val release = catalog?.let { AppUpdates.release(it, u.follow) }
|
||||
val offer = if (me.ci) catalog?.let { AppUpdates.offer(it, u.follow, me) } else null
|
||||
val offer = if (me.updatable) catalog?.let { AppUpdates.offer(it, u.follow, me) } else null
|
||||
val name = channelName(u.follow)
|
||||
val offered = release != null && (offer == Offer.UPDATE || offer == Offer.SWITCH)
|
||||
class Look(val tint: Color, val status: String, val headline: String, val detail: String?)
|
||||
val look = when {
|
||||
!me.ci -> Look(colors.onSurfaceVariant, "Local build", "Built on a computer",
|
||||
"It's signed differently from the builds here, so Android won't update it in place. Uninstall it to switch.")
|
||||
!me.updatable -> Look(colors.onSurfaceVariant, "Signed differently", "Can't update in place",
|
||||
"This copy isn't signed with DroidDeck's release key, so Android won't install the builds here over it. Uninstall it to switch.")
|
||||
catalog == null -> Look(colors.onSurfaceVariant, if (u.checking) "Checking…" else "Not checked yet", "Updates", null)
|
||||
offer == Offer.UPDATE -> Look(AttentionAmber, "Update available", newBuild(u.follow, release!!), null)
|
||||
offer == Offer.SWITCH -> Look(pal.signal, "Ready to switch", newBuild(u.follow, release!!), null)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.droiddeck.launcher.update
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import com.droiddeck.launcher.BuildConfig
|
||||
import com.droiddeck.launcher.core.Hashes
|
||||
import org.json.JSONArray
|
||||
@@ -8,6 +10,7 @@ import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.security.MessageDigest
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
@@ -47,8 +50,11 @@ object AppUpdates {
|
||||
|
||||
class Catalog(val stable: Release?, val nightly: Release?, val tests: List<Release>, val checkedAt: Long)
|
||||
|
||||
/** What is running: the commit it was built from, its PR (0 for none), whether CI made it, and when it was committed. */
|
||||
class Installed(val commit: String, val pr: Int, val version: String, val ci: Boolean, val committedAt: Long = 0L)
|
||||
/**
|
||||
* What is running: the commit it was built from, its PR (0 for none), whether the builds here
|
||||
* install over it (it is signed with the release key), and when it was committed.
|
||||
*/
|
||||
class Installed(val commit: String, val pr: Int, val version: String, val updatable: Boolean, val committedAt: Long = 0L)
|
||||
|
||||
enum class Offer {
|
||||
/** This is the build the channel has. */
|
||||
@@ -63,7 +69,28 @@ object AppUpdates {
|
||||
GONE,
|
||||
}
|
||||
|
||||
fun installed(): Installed = Installed(BuildConfig.SOURCE_COMMIT, BuildConfig.PR_NUMBER, BuildConfig.VERSION_NAME, BuildConfig.CI_BUILD, BuildConfig.COMMIT_TIME)
|
||||
@Volatile private var releaseSigned = BuildConfig.CI_BUILD
|
||||
|
||||
/** From App.onCreate: whether this copy is signed with the release key, which a local build may be too. */
|
||||
fun init(context: Context) {
|
||||
releaseSigned = runCatching { signers(context).any { it.equals(BuildConfig.RELEASE_SIGNER, ignoreCase = true) } }
|
||||
.getOrDefault(BuildConfig.CI_BUILD)
|
||||
}
|
||||
|
||||
/** SHA-256 of each certificate the package is signed with, including the ones a rotated key vouches for. */
|
||||
private fun signers(context: Context): List<String> {
|
||||
val pm = context.packageManager
|
||||
@Suppress("DEPRECATION")
|
||||
val certs = if (Build.VERSION.SDK_INT >= 28) {
|
||||
val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES).signingInfo ?: return emptyList()
|
||||
if (info.hasMultipleSigners()) info.apkContentsSigners else info.signingCertificateHistory
|
||||
} else {
|
||||
pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES).signatures
|
||||
} ?: return emptyList()
|
||||
return certs.map { cert -> MessageDigest.getInstance("SHA-256").digest(cert.toByteArray()).joinToString("") { "%02x".format(it) } }
|
||||
}
|
||||
|
||||
fun installed(): Installed = Installed(BuildConfig.SOURCE_COMMIT, BuildConfig.PR_NUMBER, BuildConfig.VERSION_NAME, releaseSigned, BuildConfig.COMMIT_TIME)
|
||||
|
||||
fun release(catalog: Catalog, follow: Follow): Release? = when (follow.channel) {
|
||||
Channel.STABLE -> catalog.stable
|
||||
@@ -87,7 +114,7 @@ object AppUpdates {
|
||||
|
||||
/** The dot on the rail: the followed channel has a newer build of what is running. */
|
||||
fun hasUpdate(catalog: Catalog?, follow: Follow): Boolean =
|
||||
catalog != null && installed().ci && offer(catalog, follow) == Offer.UPDATE
|
||||
catalog != null && installed().updatable && offer(catalog, follow) == Offer.UPDATE
|
||||
|
||||
fun isRunning(r: Release, me: Installed = installed()): Boolean {
|
||||
if (me.commit.length < 7 || r.commit.length < 7 || r.pr != me.pr) return false
|
||||
@@ -105,7 +132,7 @@ object AppUpdates {
|
||||
me.pr != 0 -> Follow(Channel.TEST, me.pr)
|
||||
catalog == null -> return Follow(Channel.STABLE)
|
||||
catalog.stable != null && isRunning(catalog.stable, me) -> Follow(Channel.STABLE)
|
||||
me.ci -> Follow(Channel.NIGHTLY)
|
||||
me.updatable -> Follow(Channel.NIGHTLY)
|
||||
else -> Follow(Channel.STABLE)
|
||||
}
|
||||
setFollow(context, inferred)
|
||||
|
||||
@@ -20,7 +20,7 @@ class AppUpdatesTest {
|
||||
checkedAt = 0L,
|
||||
)
|
||||
|
||||
private fun me(commit: String, pr: Int = 0, version: String = "0.2.0") = Installed(commit, pr, version, ci = true)
|
||||
private fun me(commit: String, pr: Int = 0, version: String = "0.2.0") = Installed(commit, pr, version, updatable = true)
|
||||
|
||||
@Test
|
||||
fun theStableBuildIsCurrentOnStable() {
|
||||
|
||||
Reference in new issue
Block a user