feat(installer): Store install metadata in APK

This commit is contained in:
rushiiMachine 2024-07-31 14:49:42 -07:00
parent c5eded4f63
commit e4eddf7c89
No known key found for this signature in database
GPG key ID: DCBE5952BB3B6420
25 changed files with 160 additions and 28 deletions

View file

@ -24,6 +24,7 @@ import com.aliucord.manager.ui.screens.home.HomeScreen
import com.aliucord.manager.ui.screens.install.InstallScreen
import com.aliucord.manager.ui.screens.installopts.InstallOptionsScreen
import com.aliucord.manager.ui.widgets.updater.UpdaterDialog
import com.aliucord.manager.util.IS_CUSTOM_BUILD
import org.koin.android.ext.android.inject
// TODO: move to a path provider in DI
@ -46,12 +47,7 @@ class MainActivity : ComponentActivity() {
) {
StoragePermissionsDialog()
@Suppress("KotlinConstantConditions")
if (
BuildConfig.GIT_BRANCH == "release" &&
!BuildConfig.GIT_LOCAL_CHANGES &&
!BuildConfig.GIT_LOCAL_COMMITS
) {
if (!IS_CUSTOM_BUILD) {
UpdaterDialog()
}

View file

@ -0,0 +1,35 @@
package com.aliucord.manager.installer
import com.aliucord.manager.ui.screens.installopts.InstallOptions
import kotlinx.serialization.Serializable
/**
* Data stored inside patched APKs as "aliucord.json" in order to preserve install-time information about Aliucord and the Manager.
*/
@Serializable
data class InstallMetadata(
/**
* The user-selected options for this installation.
*/
val options: InstallOptions,
/**
* The semver version of this manager that performed the installation.
*/
val managerVersionName: String,
/**
* Whether the manager is a real release or built from source.
*/
val customManager: Boolean,
/**
* Aliuhook release version injected into the APK.
*/
val aliuhookVersion: String,
/**
* Short commit hash of the commit the injector was built from.
*/
val injectorCommitHash: String,
)

View file

@ -1,4 +1,4 @@
package com.aliucord.manager.installer.steps
package com.aliucord.manager.installer
import com.aliucord.manager.installer.steps.download.*
import com.aliucord.manager.installer.steps.install.*
@ -29,6 +29,7 @@ class KotlinInstallRunner(options: InstallOptions) : StepRunner() {
PatchManifestStep(options),
AddInjectorStep(),
AddAliuhookStep(),
SaveMetadataStep(options),
// Install
AlignmentStep(),

View file

@ -1,9 +1,10 @@
package com.aliucord.manager.installer.steps
package com.aliucord.manager.installer
import android.content.Context
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.base.DownloadStep
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.manager.PreferencesManager

View file

@ -4,7 +4,7 @@ import android.content.Context
import androidx.compose.runtime.Stable
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.manager.DownloadManager
import com.aliucord.manager.util.showToast
import kotlinx.coroutines.Dispatchers

View file

@ -3,7 +3,7 @@ package com.aliucord.manager.installer.steps.base
import androidx.annotation.StringRes
import androidx.compose.runtime.*
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.util.toPrecision
import kotlinx.coroutines.*
import org.koin.core.time.measureTimedValue

View file

@ -2,7 +2,7 @@ package com.aliucord.manager.installer.steps.download
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.manager.PathManager
import org.koin.core.component.KoinComponent

View file

@ -3,7 +3,7 @@ package com.aliucord.manager.installer.steps.download
import androidx.compose.runtime.Stable
import com.aliucord.manager.R
import com.aliucord.manager.domain.repository.AliucordMavenRepository
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.DownloadStep
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.network.utils.getOrThrow
@ -21,7 +21,8 @@ class DownloadAliuhookStep : DownloadStep(), KoinComponent {
/**
* This is populated right before the download starts (ref: [execute])
*/
private lateinit var targetVersion: String
lateinit var targetVersion: String
private set
override val localizedName = R.string.install_step_dl_aliuhook
override val targetUrl get() = AliucordMavenRepository.getAliuhookUrl(targetVersion)
@ -33,4 +34,3 @@ class DownloadAliuhookStep : DownloadStep(), KoinComponent {
super.execute(container)
}
}

View file

@ -2,7 +2,7 @@ package com.aliucord.manager.installer.steps.download
import androidx.compose.runtime.Stable
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.DownloadStep
import com.aliucord.manager.installer.steps.prepare.FetchInfoStep
import com.aliucord.manager.manager.PathManager
@ -21,7 +21,8 @@ class DownloadInjectorStep : DownloadStep(), KoinComponent {
* Populated from a dependency step ([FetchInfoStep]).
* This is used as cache invalidation (ref: [Version.aliucordHash])
*/
private lateinit var aliucordHash: String
lateinit var aliucordHash: String
private set
override val localizedName = R.string.install_step_dl_injector
override val targetUrl = URL

View file

@ -3,7 +3,7 @@ package com.aliucord.manager.installer.steps.install
import android.os.Build
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.base.StepState
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep
@ -14,8 +14,6 @@ import org.koin.core.component.KoinComponent
* Align certain files in the APK to a 4KiB boundary.
*/
class AlignmentStep : Step(), KoinComponent {
private val currentDeviceArch = Build.SUPPORTED_ABIS.first()
override val group = StepGroup.Install
override val localizedName = R.string.install_step_alignment

View file

@ -2,7 +2,7 @@ package com.aliucord.manager.installer.steps.install
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.base.StepState
import com.aliucord.manager.manager.PathManager

View file

@ -4,7 +4,7 @@ import android.content.Context
import androidx.lifecycle.*
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.base.StepState
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep

View file

@ -2,7 +2,7 @@ package com.aliucord.manager.installer.steps.install
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep
import com.aliucord.manager.installer.util.Signer

View file

@ -3,7 +3,7 @@ package com.aliucord.manager.installer.steps.patch
import android.os.Build
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep
import com.aliucord.manager.installer.steps.download.DownloadAliuhookStep

View file

@ -2,7 +2,7 @@ package com.aliucord.manager.installer.steps.patch
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.download.*
import com.github.diamondminer88.zip.ZipReader

View file

@ -2,7 +2,7 @@ package com.aliucord.manager.installer.steps.patch
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep
import com.aliucord.manager.installer.util.ManifestPatcher

View file

@ -5,7 +5,7 @@ import android.os.Build
import androidx.compose.runtime.Stable
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.base.StepState
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep

View file

@ -0,0 +1,46 @@
package com.aliucord.manager.installer.steps.patch
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.installer.InstallMetadata
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.download.*
import com.aliucord.manager.ui.screens.installopts.InstallOptions
import com.aliucord.manager.util.IS_CUSTOM_BUILD
import com.github.diamondminer88.zip.ZipWriter
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
/**
* Store the install options and additional data inside the APK for future use,
* for example checking what library versions were used, or performing "updates" while
* maintaining the same install options as what was used upon first install.
*/
class SaveMetadataStep(private val options: InstallOptions) : Step() {
override val group = StepGroup.Patch
override val localizedName = R.string.install_step_save_metadata
private val json = Json {
prettyPrint = true
}
override suspend fun execute(container: StepRunner) {
val apk = container.getStep<CopyDependenciesStep>().patchedApk
val aliuhook = container.getStep<DownloadAliuhookStep>()
val injector = container.getStep<DownloadInjectorStep>()
val metadata = InstallMetadata(
options = options,
managerVersionName = BuildConfig.VERSION_NAME,
customManager = IS_CUSTOM_BUILD,
aliuhookVersion = aliuhook.targetVersion,
injectorCommitHash = injector.aliucordHash,
)
ZipWriter(apk, /* append = */ true).use {
it.writeEntry("aliucord.json", json.encodeToString<InstallMetadata>(metadata))
}
}
}

View file

@ -3,7 +3,7 @@ package com.aliucord.manager.installer.steps.prepare
import android.content.Context
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.base.StepState
import com.aliucord.manager.installer.util.uninstallApk

View file

@ -3,7 +3,7 @@ package com.aliucord.manager.installer.steps.prepare
import androidx.compose.runtime.Stable
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.StepGroup
import com.aliucord.manager.installer.steps.StepRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.network.dto.Version
import com.aliucord.manager.network.service.AliucordGithubService

View file

@ -13,6 +13,8 @@ import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.installer.KotlinInstallRunner
import com.aliucord.manager.installer.StepRunner
import com.aliucord.manager.installer.steps.*
import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.base.StepState

View file

@ -4,11 +4,15 @@ import android.os.Parcelable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import com.aliucord.manager.util.ColorParceler
import com.aliucord.manager.util.ColorSerializer
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.TypeParceler
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Immutable
@Parcelize
@Serializable
data class InstallOptions(
/**
* The app name that's user-facing in launchers.
@ -38,12 +42,15 @@ data class InstallOptions(
) : Parcelable {
@Immutable
@Parcelize
@Serializable
sealed interface IconReplacement : Parcelable {
/**
* Keeps the original icons that are present in the APK.
*/
@Immutable
@Parcelize
@Serializable
@SerialName("original")
data object Original : IconReplacement
/**
@ -52,8 +59,11 @@ data class InstallOptions(
*/
@Immutable
@Parcelize
@Serializable
@SerialName("color")
data class CustomColor(
@TypeParceler<Color, ColorParceler>
@Serializable(ColorSerializer::class)
val color: Color,
) : IconReplacement
@ -63,6 +73,8 @@ data class InstallOptions(
*/
@Immutable
@Parcelize
@Serializable
@SerialName("image")
data class CustomImage(val imageBytes: ByteArray) : IconReplacement {
override fun hashCode() = imageBytes.contentHashCode()
override fun equals(other: Any?) = this === other

View file

@ -0,0 +1,28 @@
package com.aliucord.manager.util
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* (De)serialize compose's [Color] into/from an ARGB hex string.
*/
object ColorSerializer : KSerializer<Color> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Color", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Color) {
val string = value
.toArgb().toUInt()
.toString(16)
.padStart(8, '0')
encoder.encodeString(string)
}
override fun deserialize(decoder: Decoder): Color {
val string = decoder.decodeString()
return Color(string.toInt(16))
}
}

View file

@ -1,6 +1,7 @@
package com.aliucord.manager.util
import androidx.collection.ObjectList
import com.aliucord.manager.BuildConfig
import kotlin.math.pow
import kotlin.math.truncate
@ -20,3 +21,13 @@ inline fun <E> ObjectList<E>.find(block: (E) -> Boolean): E? {
return null
}
/**
* Whether this manager build is not an official release.
*/
@Suppress("KotlinConstantConditions")
val IS_CUSTOM_BUILD by lazy {
BuildConfig.GIT_BRANCH != "release"
|| BuildConfig.GIT_LOCAL_CHANGES
|| BuildConfig.GIT_LOCAL_COMMITS
}

View file

@ -110,6 +110,7 @@
<string name="install_step_patch_icon">Patching app icon</string>
<string name="install_step_add_injector">Adding Aliucord injector</string>
<string name="install_step_add_aliuhook">Adding Aliuhook library</string>
<string name="install_step_save_metadata">Store metadata</string>
<string name="install_step_alignment">Aligning APKs</string>
<string name="install_step_signing">Signing APKs</string>
<string name="install_step_installing">Installing APKs</string>