Surface self-updates through the regular updates list
Fold Aurora Store's self-update back into the existing update pipeline instead of a dedicated sheet/viewmodel/helper. The feed entry is mapped to a regular App and added to the update list, reusing the standard download + install path; it is never auto-installed silently. - Add a dedicated "Self-update" section in the Updates tab with the app-details navigation disabled (it's served from the Aurora OSS feed). - Gate eligibility via PackageUtil.isSelfUpdateSupported(): vanilla/preload flavors, not debug, not F-Droid (huawei excluded by flavor). - Add a build-aware Settings toggle as the opt-out, removing the row immediately when disabled. - Exempt nightly self-updates from deleteInvalidUpdates (static version code) and drop any stale self-update row when none is offered, so a phantom update doesn't linger after a self-install.
This commit is contained in:
@@ -23,6 +23,10 @@ val lastCommitHash = providers.exec {
|
||||
commandLine("git", "rev-parse", "--short", "HEAD")
|
||||
}.standardOutput.asText.map { it.trim() }
|
||||
|
||||
val lastCommitTimestamp = providers.exec {
|
||||
commandLine("git", "log", "-1", "--format=%ct")
|
||||
}.standardOutput.asText.map { it.trim() }
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
@@ -69,6 +73,7 @@ configure<ApplicationExtension> {
|
||||
testInstrumentationRunnerArguments["disableAnalytics"] = "true"
|
||||
|
||||
buildConfigField("String", "EXODUS_API_KEY", "\"bbe6ebae4ad45a9cbacb17d69739799b8df2c7ae\"")
|
||||
buildConfigField("long", "BUILD_TIMESTAMP", "${lastCommitTimestamp.get()}L")
|
||||
|
||||
missingDimensionStrategy("device", "vanilla")
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ object Constants {
|
||||
|
||||
const val SHARE_URL = "https://play.google.com/store/apps/details?id="
|
||||
|
||||
const val UPDATE_URL_STABLE = "https://gitlab.com/AuroraOSS/AuroraStore/raw/master/updates.json"
|
||||
const val UPDATE_URL_VANILLA =
|
||||
"https://auroraoss.com/downloads/AuroraStore/Feeds/release_feed.json"
|
||||
const val UPDATE_URL_NIGHTLY =
|
||||
"https://auroraoss.com/downloads/AuroraStore/Feeds/nightly_feed.json"
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ import com.aurora.store.compose.navigation.Destination
|
||||
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||
import com.aurora.store.compose.ui.preferences.network.SingleChoiceDialog
|
||||
import com.aurora.store.data.model.UpdateMode
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_BATTERY
|
||||
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_IDLE
|
||||
@@ -62,6 +63,7 @@ import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_METERE
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_AURORA_ONLY
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_INSTALLERS
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_SELF_UPDATE_ENABLED
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_AUTO
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||
@@ -79,6 +81,7 @@ fun UpdatesPreferenceScreen(
|
||||
onScheduleAutomatedCheck = { viewModel.updateHelper.scheduleAutomatedCheck() },
|
||||
onUpdateAutomatedCheck = { viewModel.updateHelper.updateAutomatedCheck() },
|
||||
onCheckUpdatesNow = { viewModel.updateHelper.checkUpdatesNow() },
|
||||
onDeleteSelfUpdate = { viewModel.updateHelper.deleteSelfUpdate() },
|
||||
onNavigateTo = onNavigateTo
|
||||
)
|
||||
}
|
||||
@@ -89,6 +92,7 @@ private fun ScreenContent(
|
||||
onScheduleAutomatedCheck: () -> Unit = {},
|
||||
onUpdateAutomatedCheck: () -> Unit = {},
|
||||
onCheckUpdatesNow: () -> Unit = {},
|
||||
onDeleteSelfUpdate: () -> Unit = {},
|
||||
onNavigateTo: (Destination) -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -109,6 +113,10 @@ private fun ScreenContent(
|
||||
var updatesExtended by remember {
|
||||
mutableStateOf(Preferences.getBoolean(context, PREFERENCE_UPDATES_EXTENDED))
|
||||
}
|
||||
val selfUpdateSupported = remember { PackageUtil.isSelfUpdateSupported(context) }
|
||||
var selfUpdateEnabled by remember {
|
||||
mutableStateOf(Preferences.getBoolean(context, PREFERENCE_SELF_UPDATE_ENABLED, true))
|
||||
}
|
||||
|
||||
// Re-read source-filter prefs after returning from SourceFiltersScreen.
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
@@ -335,6 +343,30 @@ private fun ScreenContent(
|
||||
}
|
||||
)
|
||||
}
|
||||
if (selfUpdateSupported) {
|
||||
item {
|
||||
fun onSelfUpdateChanged(enabled: Boolean) {
|
||||
selfUpdateEnabled = enabled
|
||||
context.save(PREFERENCE_SELF_UPDATE_ENABLED, enabled)
|
||||
if (enabled) onCheckUpdatesNow() else onDeleteSelfUpdate()
|
||||
}
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
onSelfUpdateChanged(!selfUpdateEnabled)
|
||||
},
|
||||
headlineContent = { Text(stringResource(R.string.pref_self_update)) },
|
||||
supportingContent = {
|
||||
Text(stringResource(R.string.pref_self_update_desc))
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = selfUpdateEnabled,
|
||||
onCheckedChange = ::onSelfUpdateChanged
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,19 +59,21 @@ fun UpdatesScreen(
|
||||
}
|
||||
|
||||
val groupedUpdates = remember(updateMap) {
|
||||
val self = mutableListOf<Map.Entry<Update, Download?>>()
|
||||
val main = mutableListOf<Map.Entry<Update, Download?>>()
|
||||
val approval = mutableListOf<Map.Entry<Update, Download?>>()
|
||||
val incompatible = mutableListOf<Map.Entry<Update, Download?>>()
|
||||
updateMap?.entries?.forEach { entry ->
|
||||
when {
|
||||
entry.key.isSelfUpdate(context) -> self += entry
|
||||
entry.key.isIncompatible -> incompatible += entry
|
||||
entry.key.requiresOwnershipTransfer(context) -> approval += entry
|
||||
else -> main += entry
|
||||
}
|
||||
}
|
||||
Triple(main.toList(), approval.toList(), incompatible.toList())
|
||||
listOf(self.toList(), main.toList(), approval.toList(), incompatible.toList())
|
||||
}
|
||||
val (mainEntries, approvalEntries, incompatibleEntries) = groupedUpdates
|
||||
val (selfEntries, mainEntries, approvalEntries, incompatibleEntries) = groupedUpdates
|
||||
|
||||
val mainAnyActive = mainEntries.any { it.value.isActive() }
|
||||
val approvalAnyActive = approvalEntries.any { it.value.isActive() }
|
||||
@@ -110,6 +112,29 @@ fun UpdatesScreen(
|
||||
dimensionResource(R.dimen.spacing_medium)
|
||||
)
|
||||
) {
|
||||
if (selfEntries.isNotEmpty()) {
|
||||
item(key = "header_self") {
|
||||
SectionHeader(
|
||||
title = stringResource(R.string.updates_self_header),
|
||||
subtitle = stringResource(R.string.updates_self_desc)
|
||||
)
|
||||
}
|
||||
items(
|
||||
items = selfEntries,
|
||||
key = { "self-${it.key.packageName}" }
|
||||
) { (update, download) ->
|
||||
AppUpdateItem(
|
||||
update = update,
|
||||
download = download,
|
||||
// Served from the Aurora OSS feed, not Play — there is
|
||||
// no app details page to open.
|
||||
onClick = {},
|
||||
onUpdate = { onRequestUpdate(update) },
|
||||
onCancel = { onCancelUpdate(update.packageName) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (mainEntries.isNotEmpty()) {
|
||||
item(key = "header_main") {
|
||||
val title = "${mainEntries.size} " + stringResource(
|
||||
|
||||
@@ -14,8 +14,10 @@ import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.aurora.extensions.TAG
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.model.BuildType
|
||||
import com.aurora.store.data.model.UpdateMode
|
||||
import com.aurora.store.data.room.update.IgnoredUpdate
|
||||
import com.aurora.store.data.room.update.IgnoredUpdateDao
|
||||
@@ -185,6 +187,14 @@ class UpdateHelper @Inject constructor(
|
||||
updateDao.deleteAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately drops Aurora Store's own update row, e.g. when the user turns the
|
||||
* self-update preference off. The periodic check won't re-add it while disabled.
|
||||
*/
|
||||
fun deleteSelfUpdate() {
|
||||
AuroraApp.scope.launch { deleteUpdate(BuildConfig.APPLICATION_ID) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide all future updates for [packageName] until [unignore] is called.
|
||||
*/
|
||||
@@ -243,6 +253,13 @@ class UpdateHelper @Inject constructor(
|
||||
|
||||
private suspend fun deleteInvalidUpdates() {
|
||||
updateDao.updates().firstOrNull()?.forEach { update ->
|
||||
// Nightly builds share a static version code, so the generic "up to date"
|
||||
// check would wrongly drop a freshly found self-update. The worker owns the
|
||||
// self-update lifecycle (re-checked by build timestamp); cleanup happens via
|
||||
// the install event instead.
|
||||
if (update.isSelfUpdate(context) && BuildType.CURRENT == BuildType.NIGHTLY) {
|
||||
return@forEach
|
||||
}
|
||||
if (!update.isInstalled(context) || update.isUpToDate(context)) {
|
||||
deleteUpdate(update.packageName)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.model
|
||||
@@ -24,51 +10,63 @@ import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.Artwork
|
||||
import com.aurora.gplayapi.data.models.EncodedCertificateSet
|
||||
import com.aurora.gplayapi.data.models.PlayFile
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.util.CertUtil
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Self-update feed entry returned by `release_feed.json` (vanilla release) and
|
||||
* `nightly_feed.json` (nightly), both served from the Aurora OSS server.
|
||||
*
|
||||
* The producer encodes numeric fields as JSON strings, so the raw fields below stay
|
||||
* `String` and we expose typed `Long` accessors that tolerate blank values. Decoding
|
||||
* with `Long`s would otherwise blow up on the first request.
|
||||
*/
|
||||
@Serializable
|
||||
data class SelfUpdate(
|
||||
@SerialName("version_name") var versionName: String = String(),
|
||||
@SerialName("version_code") var versionCode: Long = 0,
|
||||
@SerialName("aurora_build") var auroraBuild: String = String(),
|
||||
@SerialName("fdroid_build") var fdroidBuild: String = String(),
|
||||
@SerialName("updated_on") var updatedOn: String = String(),
|
||||
val changelog: String = String(),
|
||||
val size: Long = 0L,
|
||||
val timestamp: Long = 0L
|
||||
@SerialName("version_name")
|
||||
val versionName: String = "",
|
||||
@SerialName("version_code")
|
||||
val versionCodeRaw: String = "0",
|
||||
@SerialName("download_url")
|
||||
val downloadUrl: String = "",
|
||||
val changelog: String = "",
|
||||
@SerialName("size")
|
||||
val sizeRaw: String = "0",
|
||||
@SerialName("last_commit")
|
||||
val lastCommit: String = "",
|
||||
@SerialName("updated_on")
|
||||
val updatedOn: String = "",
|
||||
@SerialName("timestamp")
|
||||
val timestampRaw: String = "0"
|
||||
) {
|
||||
companion object {
|
||||
private const val BASE_URL = "https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master"
|
||||
val versionCode: Long get() = versionCodeRaw.toLongOrNull() ?: 0L
|
||||
val size: Long get() = sizeRaw.toLongOrNull() ?: 0L
|
||||
val timestamp: Long get() = timestampRaw.toLongOrNull() ?: 0L
|
||||
|
||||
fun toApp(selfUpdate: SelfUpdate, context: Context): App {
|
||||
// Keep paths updated with fastlane data on project
|
||||
val icon = "fastlane/metadata/android/en-US/images/icon.png"
|
||||
|
||||
val downloadURL = if (CertUtil.isFDroidApp(context, BuildConfig.APPLICATION_ID)) {
|
||||
selfUpdate.fdroidBuild
|
||||
} else {
|
||||
selfUpdate.auroraBuild
|
||||
}
|
||||
|
||||
return App(
|
||||
/**
|
||||
* Maps the feed entry onto a regular [App] so it can flow through the normal
|
||||
* update pipeline ([com.aurora.store.data.room.update.Update.fromApp] →
|
||||
* download → install). The certificate set is the currently installed app's own
|
||||
* hashes so [com.aurora.store.data.room.update.Update.hasValidCert] holds and the
|
||||
* update is never filtered out as untrusted.
|
||||
*/
|
||||
fun toApp(context: Context): App = App(
|
||||
packageName = context.packageName,
|
||||
versionCode = selfUpdate.versionCode,
|
||||
versionName = selfUpdate.versionName,
|
||||
changes = selfUpdate.changelog,
|
||||
size = selfUpdate.size,
|
||||
updatedOn = selfUpdate.updatedOn,
|
||||
versionCode = versionCode,
|
||||
versionName = versionName,
|
||||
changes = changelog,
|
||||
size = size,
|
||||
updatedOn = updatedOn,
|
||||
displayName = context.getString(R.string.app_name),
|
||||
developerName = "Rahul Kumar Patel",
|
||||
iconArtwork = Artwork(url = "$BASE_URL/$icon"),
|
||||
iconArtwork = Artwork(url = ICON_URL),
|
||||
fileList = mutableListOf(
|
||||
PlayFile(
|
||||
name = "${context.packageName}.apk",
|
||||
url = downloadURL,
|
||||
size = selfUpdate.size
|
||||
url = downloadUrl,
|
||||
size = size
|
||||
)
|
||||
),
|
||||
isFree = true,
|
||||
@@ -80,6 +78,11 @@ data class SelfUpdate(
|
||||
EncodedCertificateSet(certificateSet = it, sha256 = String())
|
||||
}.toMutableList()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Kept in sync with the fastlane metadata on the project.
|
||||
private const val ICON_URL =
|
||||
"https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/" +
|
||||
"fastlane/metadata/android/en-US/images/icon.png"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import com.aurora.extensions.isHyperOS
|
||||
import com.aurora.extensions.isIgnoringBatteryOptimizations
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import com.aurora.gplayapi.network.IHttpClient
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.data.helper.DownloadHelper
|
||||
import com.aurora.store.data.helper.UpdateHelper
|
||||
@@ -23,6 +22,7 @@ import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.model.BuildType
|
||||
import com.aurora.store.data.model.SelfUpdate
|
||||
import com.aurora.store.data.model.UpdateMode
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.data.providers.AccountProvider
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.data.providers.BlacklistProvider
|
||||
@@ -32,6 +32,7 @@ import com.aurora.store.util.CertUtil
|
||||
import com.aurora.store.util.NotificationUtil
|
||||
import com.aurora.store.util.PackageUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_SELF_UPDATE_ENABLED
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_AUTO
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
@@ -41,18 +42,22 @@ import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* A worker to check for updates for installed apps based on saved authentication data,
|
||||
* filters and the auto-updates mode selected by the user. The repeat interval
|
||||
* is configurable by the user, defaulting to 3 hours with a flex time of 30 minutes.
|
||||
* A worker that drives periodic app-update checks. The repeat interval is configurable
|
||||
* by the user, defaulting to 3 hours with a flex time of 30 minutes.
|
||||
*
|
||||
* Aurora Store's own update is fetched from the bundled release/nightly feed and added
|
||||
* to the regular update list (see [getSelfUpdate]); from there it reuses the standard
|
||||
* download + install pipeline. It is never auto-installed silently — the user triggers
|
||||
* it from the Updates tab.
|
||||
*
|
||||
* Avoid using this worker directly and prefer using [UpdateHelper] instead.
|
||||
* @see AuthWorker
|
||||
*/
|
||||
@HiltWorker
|
||||
class UpdateWorker @AssistedInject constructor(
|
||||
private val httpClient: HttpClient,
|
||||
private val json: Json,
|
||||
private val blacklistProvider: BlacklistProvider,
|
||||
private val httpClient: IHttpClient,
|
||||
private val updateDao: UpdateDao,
|
||||
private val downloadHelper: DownloadHelper,
|
||||
private val authProvider: AuthProvider,
|
||||
@@ -61,11 +66,18 @@ class UpdateWorker @AssistedInject constructor(
|
||||
@Assisted workerParams: WorkerParameters
|
||||
) : AuthWorker(authProvider, context, workerParams) {
|
||||
|
||||
private val notificationID = 100
|
||||
companion object {
|
||||
private const val NOTIFICATION_ID = 100
|
||||
}
|
||||
|
||||
private val canSelfUpdate = !CertUtil.isFDroidApp(context, BuildConfig.APPLICATION_ID) &&
|
||||
!CertUtil.isAppGalleryApp(context, BuildConfig.APPLICATION_ID) &&
|
||||
BuildType.CURRENT != BuildType.DEBUG
|
||||
/**
|
||||
* `true` when the build supports self-update ([PackageUtil.isSelfUpdateSupported])
|
||||
* and the user hasn't opted out via the Settings toggle. Read each check so flipping
|
||||
* the preference takes effect on the next run.
|
||||
*/
|
||||
private val canSelfUpdate: Boolean
|
||||
get() = PackageUtil.isSelfUpdateSupported(context) &&
|
||||
Preferences.getBoolean(context, PREFERENCE_SELF_UPDATE_ENABLED, true)
|
||||
|
||||
private val isAuroraOnlyFilterEnabled: Boolean
|
||||
get() = Preferences.getBoolean(context, Preferences.PREFERENCE_FILTER_AURORA_ONLY, false)
|
||||
@@ -124,7 +136,8 @@ class UpdateWorker @AssistedInject constructor(
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
// Clean the update list to prepare for installing
|
||||
// Clean the update list to prepare for installing. Aurora Store's own update
|
||||
// is never installed silently — the user triggers it from the Updates tab.
|
||||
val filteredUpdates = updates
|
||||
.filter { it.hasValidCert }
|
||||
.filterNot { it.isSelfUpdate(context) }
|
||||
@@ -152,7 +165,7 @@ class UpdateWorker @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo = ForegroundInfo(
|
||||
notificationID,
|
||||
NOTIFICATION_ID,
|
||||
NotificationUtil.getUpdateNotification(context)
|
||||
)
|
||||
|
||||
@@ -206,7 +219,18 @@ class UpdateWorker @AssistedInject constructor(
|
||||
.filter { PackageUtil.isUpdatable(context, it.packageName, it.versionCode) }
|
||||
.toMutableList()
|
||||
|
||||
if (canSelfUpdate) getSelfUpdate()?.let { updates.add(it) }
|
||||
// Aurora Store's own update comes from the feed, not Play. When one is
|
||||
// offered, add it; otherwise drop any stale self-update row. This is the
|
||||
// cleanup path for the row (nightly self-updates are exempt from
|
||||
// deleteInvalidUpdates, and the install event isn't delivered reliably when
|
||||
// the app replaces itself), so a previously shown self-update doesn't linger
|
||||
// after we've already updated to it.
|
||||
val selfUpdate = if (canSelfUpdate) getSelfUpdate() else null
|
||||
if (selfUpdate != null) {
|
||||
updates.add(selfUpdate)
|
||||
} else {
|
||||
updateDao.delete(context.packageName)
|
||||
}
|
||||
|
||||
return@withContext updates.map {
|
||||
Update.fromApp(
|
||||
@@ -219,15 +243,15 @@ class UpdateWorker @AssistedInject constructor(
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and returns updates for Aurora Store if available
|
||||
* Fetches Aurora Store's own update from the bundled release/nightly feed and maps
|
||||
* it onto an [App] so it joins the regular update list. Nightly version codes never
|
||||
* bump, so newness is decided by the build timestamp there; release uses the version
|
||||
* code. Best-effort: any failure logs and yields no update.
|
||||
*/
|
||||
private suspend fun getSelfUpdate(): App? {
|
||||
return withContext(Dispatchers.IO) {
|
||||
private suspend fun getSelfUpdate(): App? = withContext(Dispatchers.IO) {
|
||||
val updateUrl = when (BuildType.CURRENT) {
|
||||
BuildType.RELEASE -> Constants.UPDATE_URL_STABLE
|
||||
|
||||
BuildType.RELEASE -> Constants.UPDATE_URL_VANILLA
|
||||
BuildType.NIGHTLY -> Constants.UPDATE_URL_NIGHTLY
|
||||
|
||||
else -> {
|
||||
Log.i(TAG, "Self-updates are not available for this build!")
|
||||
return@withContext null
|
||||
@@ -235,42 +259,31 @@ class UpdateWorker @AssistedInject constructor(
|
||||
}
|
||||
|
||||
try {
|
||||
val response = httpClient.get(updateUrl, mapOf())
|
||||
val selfUpdate = json.decodeFromString<SelfUpdate>(String(response.responseBytes))
|
||||
val selfUpdate = httpClient.call(updateUrl).use {
|
||||
json.decodeFromString<SelfUpdate>(it.body.string())
|
||||
}
|
||||
|
||||
val isUpdate = when (BuildType.CURRENT) {
|
||||
BuildType.NIGHTLY,
|
||||
val isNewer = when (BuildType.CURRENT) {
|
||||
BuildType.RELEASE -> selfUpdate.versionCode > BuildConfig.VERSION_CODE
|
||||
|
||||
BuildType.NIGHTLY -> selfUpdate.timestamp > BuildConfig.BUILD_TIMESTAMP
|
||||
else -> false
|
||||
}
|
||||
|
||||
if (isUpdate) {
|
||||
if (CertUtil.isFDroidApp(context, BuildConfig.APPLICATION_ID)) {
|
||||
if (selfUpdate.fdroidBuild.isNotEmpty()) {
|
||||
return@withContext SelfUpdate.toApp(selfUpdate, context)
|
||||
}
|
||||
} else if (selfUpdate.auroraBuild.isNotEmpty()) {
|
||||
return@withContext SelfUpdate.toApp(selfUpdate, context)
|
||||
} else {
|
||||
Log.e(TAG, "Update file is missing!")
|
||||
return@withContext null
|
||||
}
|
||||
if (isNewer && selfUpdate.downloadUrl.isNotBlank()) {
|
||||
return@withContext selfUpdate.toApp(context)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to check self-updates", exception)
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
Log.i(TAG, "No self-updates found!")
|
||||
return@withContext null
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyUpdates(updates: List<Update>) {
|
||||
with(context.getSystemService<NotificationManager>()!!) {
|
||||
notify(
|
||||
notificationID,
|
||||
NOTIFICATION_ID,
|
||||
NotificationUtil.getUpdateNotification(context, updates)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.net.toUri
|
||||
import com.aurora.Constants.FLAVOUR_PRELOAD
|
||||
import com.aurora.Constants.FLAVOUR_VANILLA
|
||||
import com.aurora.Constants.PACKAGE_NAME_APP_GALLERY
|
||||
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||
import com.aurora.Constants.PACKAGE_NAME_PLAY_STORE
|
||||
@@ -48,6 +50,7 @@ import com.aurora.extensions.isVAndAbove
|
||||
import com.aurora.extensions.isValidApp
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.BuildType
|
||||
import java.util.Locale
|
||||
|
||||
object PackageUtil {
|
||||
@@ -184,6 +187,19 @@ object PackageUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build-level eligibility for Aurora Store's self-update: vanilla / preload flavors
|
||||
* only, never debug, and never an F-Droid-signed build. Huawei is excluded by the
|
||||
* flavor check. The user-facing toggle gates this further at runtime.
|
||||
*/
|
||||
fun isSelfUpdateSupported(context: Context): Boolean {
|
||||
val flavorEligible = BuildConfig.FLAVOR == FLAVOUR_VANILLA ||
|
||||
BuildConfig.FLAVOR == FLAVOUR_PRELOAD
|
||||
return flavorEligible &&
|
||||
BuildType.CURRENT != BuildType.DEBUG &&
|
||||
!CertUtil.isFDroidApp(context, BuildConfig.APPLICATION_ID)
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm if MicroG bundle is installed
|
||||
* Considering the following:
|
||||
|
||||
@@ -74,6 +74,8 @@ object Preferences {
|
||||
|
||||
const val PREFERENCE_MIGRATION_VERSION = "PREFERENCE_MIGRATION_VERSION"
|
||||
|
||||
const val PREFERENCE_SELF_UPDATE_ENABLED = "PREFERENCE_SELF_UPDATE_ENABLED"
|
||||
|
||||
private var prefs: SharedPreferences? = null
|
||||
|
||||
fun getPrefs(context: Context): SharedPreferences = when (BuildConfig.FLAVOR) {
|
||||
|
||||
@@ -228,6 +228,8 @@
|
||||
<string name="pref_ui_similar_apps">"Similar and related apps"</string>
|
||||
<string name="pref_updates_incompatible">"Show updates that may fail"</string>
|
||||
<string name="pref_updates_incompatible_desc">"Display updates for incompatible or disabled apps that may fail to install."</string>
|
||||
<string name="pref_self_update">"Self-update"</string>
|
||||
<string name="pref_self_update_desc">"Offer new Aurora Store builds in the Updates tab."</string>
|
||||
<string name="pref_aurora_only">"Filter apps from other sources."</string>
|
||||
<string name="pref_aurora_only_desc">"Do not check for updates for apps installed from sources outside Aurora Store"</string>
|
||||
<string name="pref_source_filters_title">"Filter apps from other sources"</string>
|
||||
@@ -317,6 +319,8 @@
|
||||
<string name="app_updater_service_notif_text">Enables background app download</string>
|
||||
<string name="update_available">update available</string>
|
||||
<string name="updates_available">updates available</string>
|
||||
<string name="updates_self_header">Self-update</string>
|
||||
<string name="updates_self_desc">A newer build of Aurora Store is available</string>
|
||||
<string name="updates_incompatible_header">Incompatible updates</string>
|
||||
<string name="updates_incompatible_desc">These system app updates cannot be installed on this OS</string>
|
||||
<string name="updates_approval_header">Updates requiring approval</string>
|
||||
|
||||
Reference in New Issue
Block a user