From 79f73af04a22abbd09d54ad6b3fd5401701b5099 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 4 Feb 2026 08:54:29 +0000 Subject: [PATCH 01/11] migrate to Gradle version catalog --- app/build.gradle.kts | 52 +++++++++++++++----------------- build.gradle.kts | 13 +++----- gradle/libs.versions.toml | 42 ++++++++++++++++++++++++++ gradle/verification-metadata.xml | 30 ++++++++++++++++++ 4 files changed, 101 insertions(+), 36 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8f8407d1..25edc8a8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,9 +9,9 @@ if (useKeystoreProperties) { } plugins { - id("com.android.application") - id("com.google.devtools.ksp") - id("androidx.navigation.safeargs") + alias(libs.plugins.android.application) + alias(libs.plugins.google.devtools.ksp) + alias(libs.plugins.androidx.navigation.safeargs) id("kotlin-parcelize") } @@ -91,28 +91,26 @@ android { } dependencies { - implementation("androidx.core:core-ktx:1.17.0") - implementation("androidx.appcompat:appcompat:1.7.1") - implementation("androidx.constraintlayout:constraintlayout:2.2.1") - implementation("androidx.activity:activity-ktx:1.12.3") - implementation("androidx.fragment:fragment-ktx:1.8.9") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.7") - implementation("androidx.navigation:navigation-ui-ktx:2.9.7") - implementation("androidx.preference:preference-ktx:1.2.1") - implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0") - - val lifecycleVersion = "2.10.0" - implementation("androidx.lifecycle:lifecycle-viewmodel:$lifecycleVersion") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion") - - implementation("com.google.android.material:material:1.13.0") - - implementation("org.bouncycastle:bcprov-jdk18on:1.83") - - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2") - - val glideVersion = "5.0.5" - implementation("com.github.bumptech.glide:glide:$glideVersion") - ksp("com.github.bumptech.glide:ksp:$glideVersion") + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.fragment.ktx) + implementation(libs.androidx.navigation.fragment.ktx) + implementation(libs.androidx.navigation.ui.ktx) + implementation(libs.androidx.preference.ktx) + implementation(libs.androidx.swiperefreshlayout) + + implementation(libs.androidx.lifecycle.viewmodel) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + + implementation(libs.material) + + implementation(libs.bcprov.jdk18on) + + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.coroutines.android) + + implementation(libs.glide.core) + ksp(libs.glide.ksp) } diff --git a/build.gradle.kts b/build.gradle.kts index 5acc0fec..bbeeaf04 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,13 +1,8 @@ plugins { - id("com.android.application") version "9.0.0" apply false - id("androidx.navigation.safeargs") version "2.9.7" apply false -} - -buildscript { - dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.0") - classpath("com.google.devtools.ksp:symbol-processing-gradle-plugin:2.3.5") - } + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.androidx.navigation.safeargs) apply false + alias(libs.plugins.google.devtools.ksp) apply false } allprojects { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..9e087b44 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,42 @@ +[versions] +activityKtx = "1.12.3" +agp = "9.0.0" +appcompat = "1.7.1" +bcprovJdk18on = "1.83" +constraintlayout = "2.2.1" +coreKtx = "1.17.0" +fragmentKtx = "1.8.9" +glide = "5.0.5" +kotlin = "2.3.0" +kotlinxCoroutines = "1.10.2" +ksp = "2.3.5" +lifecycle = "2.10.0" +material = "1.13.0" +navigation = "2.9.7" +preferenceKtx = "1.2.1" +swiperefreshlayout = "1.2.0" + +[libraries] +androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" } +androidx-lifecycle-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "lifecycle" } +androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" } +androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" } +androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation" } +androidx-preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preferenceKtx" } +androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" } +bcprov-jdk18on = { group = "org.bouncycastle", name = "bcprov-jdk18on", version.ref = "bcprovJdk18on" } +glide-core = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" } +glide-ksp = { group = "com.github.bumptech.glide", name = "ksp", version.ref = "glide" } +kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } +kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +androidx-navigation-safeargs = { id = "androidx.navigation.safeargs", version.ref = "navigation" } +google-devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index bd7b09b9..e5833c6c 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1464,6 +1464,11 @@ + + + + + @@ -1517,6 +1522,11 @@ + + + + + @@ -1538,6 +1548,11 @@ + + + + + @@ -1551,6 +1566,11 @@ + + + + + @@ -1566,6 +1586,11 @@ + + + + + @@ -3021,6 +3046,11 @@ + + + + + From d4dd556f90017412accf5409bb84c20ad0581cce Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 4 Feb 2026 09:13:04 +0000 Subject: [PATCH 02/11] resolve resource parameter annotation warnings --- app/src/main/java/app/grapheneos/apps/core/Repo.kt | 4 ++-- app/src/main/java/app/grapheneos/apps/ui/ActivityUtils.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/grapheneos/apps/core/Repo.kt b/app/src/main/java/app/grapheneos/apps/core/Repo.kt index f48e7193..d07a370c 100644 --- a/app/src/main/java/app/grapheneos/apps/core/Repo.kt +++ b/app/src/main/java/app/grapheneos/apps/core/Repo.kt @@ -128,7 +128,7 @@ class Repo(json: JSONObject, val eTag: String, val isDummy: Boolean = false) { // ReleaseChannel enum entries are expected to be ordered from least stable to most stable by the // package variant selection code. -enum class ReleaseChannel(@StringRes val uiName: Int) { +enum class ReleaseChannel(@param:StringRes val uiName: Int) { alpha(R.string.release_channel_alpha), beta(R.string.release_channel_beta), stable(R.string.release_channel_stable), @@ -139,7 +139,7 @@ fun findRPackage(variants: List, channel: ReleaseChannel): RPackage { return variants.find { it.releaseChannel >= channel } ?: variants.last() } -enum class PackageSource(@StringRes val uiName: Int) { +enum class PackageSource(@param:StringRes val uiName: Int) { GrapheneOS(R.string.pkg_source_grapheneos), GrapheneOS_build(R.string.pkg_source_grapheneos_build), Mirror(R.string.pkg_source_mirror), diff --git a/app/src/main/java/app/grapheneos/apps/ui/ActivityUtils.kt b/app/src/main/java/app/grapheneos/apps/ui/ActivityUtils.kt index 85db62be..f0848edd 100644 --- a/app/src/main/java/app/grapheneos/apps/ui/ActivityUtils.kt +++ b/app/src/main/java/app/grapheneos/apps/ui/ActivityUtils.kt @@ -200,7 +200,7 @@ class PendingActivityIntent(val intent: Intent) : PendingAction() { } } -class PendingDialog(@IdRes val id: Int, val args: Bundle) : PendingAction() { +class PendingDialog(@param:IdRes val id: Int, val args: Bundle) : PendingAction() { override fun toBundleInner() = Bundle().apply { putInt(KEY_TYPE, TYPE_DIALOG) putInt(KEY_DIALOG_ID, id) From bcf609716c79128818abac17e65b53fe622ed1cc Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 4 Feb 2026 09:16:33 +0000 Subject: [PATCH 03/11] suppress java.util.List usage warning --- app/src/main/java/app/grapheneos/apps/core/InstallStart.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/grapheneos/apps/core/InstallStart.kt b/app/src/main/java/app/grapheneos/apps/core/InstallStart.kt index 51f467a5..871b279f 100644 --- a/app/src/main/java/app/grapheneos/apps/core/InstallStart.kt +++ b/app/src/main/java/app/grapheneos/apps/core/InstallStart.kt @@ -345,9 +345,10 @@ private val updateListOfBusyPackagesMethod: Method? by lazy { } try { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") pkgManager.javaClass.getDeclaredMethod("updateListOfBusyPackages", java.lang.Boolean.TYPE, java.util.List::class.java) - } catch (ignored: ReflectiveOperationException) { + } catch (_: ReflectiveOperationException) { null } } From 1b7b05db7724f2d037a68b8042ab877af6ffc601 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 4 Feb 2026 09:17:27 +0000 Subject: [PATCH 04/11] suppress isAppSourceCertificateTrusted() deprecation warning --- app/src/main/java/app/grapheneos/apps/core/Repo.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/grapheneos/apps/core/Repo.kt b/app/src/main/java/app/grapheneos/apps/core/Repo.kt index d07a370c..35c70831 100644 --- a/app/src/main/java/app/grapheneos/apps/core/Repo.kt +++ b/app/src/main/java/app/grapheneos/apps/core/Repo.kt @@ -118,6 +118,7 @@ class Repo(json: JSONObject, val eTag: String, val isDummy: Boolean = false) { val cert = certFactory.generateCertificate(ByteArrayInputStream(certBytes)) as X509Certificate + @Suppress("DEPRECATION") // not deprecated for SDK < 35 if (fim.isAppSourceCertificateTrusted(cert)) { return@run id.toInt() } From e7ea43b0f764715eaa8ac02e70195191cea54b86 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 4 Feb 2026 09:21:22 +0000 Subject: [PATCH 05/11] replace usages of deprecated Locale(String) constructor --- app/src/main/java/app/grapheneos/apps/core/Repo.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/grapheneos/apps/core/Repo.kt b/app/src/main/java/app/grapheneos/apps/core/Repo.kt index 35c70831..a4d789d5 100644 --- a/app/src/main/java/app/grapheneos/apps/core/Repo.kt +++ b/app/src/main/java/app/grapheneos/apps/core/Repo.kt @@ -373,7 +373,7 @@ class RPackage(val common: RPackageContainer, val versionCode: Long, val abis: A res.add(apk) Apk.Type.LANGUAGE -> { - if (neededLocales.contains(Locale(qualifier))) { + if (neededLocales.contains(Locale.Builder().setLanguage(qualifier).build())) { res.add(apk) } } @@ -428,7 +428,7 @@ class RPackage(val common: RPackageContainer, val versionCode: Long, val abis: A val set = ArraySet(len) for (i in 0 until len) { val locale = locales.get(i) - set.add(Locale(locale.language)) + set.add(Locale.Builder().setLanguage(locale.language).build()) } cache = Pair(tags, set) localeCache = cache From 883f696b17cd4d86e3ab914f590e7fcd56115377 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 4 Feb 2026 09:22:03 +0000 Subject: [PATCH 06/11] remove no longer needed cast in unmarshallParcelableInner() --- app/src/main/java/app/grapheneos/apps/util/BundleUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/grapheneos/apps/util/BundleUtils.kt b/app/src/main/java/app/grapheneos/apps/util/BundleUtils.kt index f2d28f80..b4e55efb 100644 --- a/app/src/main/java/app/grapheneos/apps/util/BundleUtils.kt +++ b/app/src/main/java/app/grapheneos/apps/util/BundleUtils.kt @@ -91,7 +91,7 @@ fun unmarshallParcelableInner(bytes: ByteArray, type: Class): Pa if (obj is Bundle) { obj.classLoader = ApplicationImpl::class.java.classLoader } - return obj as Parcelable + return obj } finally { parcel.recycle() } From 9efcfb70a39e6c35e052a93fb88cdab543e18363 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 4 Feb 2026 09:23:56 +0000 Subject: [PATCH 07/11] remove no longer needed setupWithNavController() workaround --- app/src/main/java/app/grapheneos/apps/ui/MainActivity.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/app/grapheneos/apps/ui/MainActivity.kt b/app/src/main/java/app/grapheneos/apps/ui/MainActivity.kt index 9642f74e..4dca73d4 100644 --- a/app/src/main/java/app/grapheneos/apps/ui/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/apps/ui/MainActivity.kt @@ -60,10 +60,7 @@ class MainActivity : AppCompatActivity() { navController = supportFragmentManager.findFragmentById(R.id.container)!!.findNavController() setSupportActionBar(views.toolbar) - // doesn't work properly if setup in onCreate() if activity is recreated - mainHandler.post { - NavigationUI.setupWithNavController(views.toolbar, navController) - } + NavigationUI.setupWithNavController(views.toolbar, navController) intent.let { if (it.action == Intent.ACTION_SHOW_APP_INFO) { From 1f263e2d02e33f9d3ef40b96bd1b1f721820ab01 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 4 Feb 2026 09:41:46 +0000 Subject: [PATCH 08/11] disable predictive back gesture --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b6483e7a..eccaa489 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ Date: Wed, 4 Feb 2026 15:53:33 +0000 Subject: [PATCH 09/11] add kotlin-retry dependency --- app/build.gradle.kts | 2 ++ gradle/libs.versions.toml | 3 +++ gradle/verification-metadata.xml | 39 ++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 25edc8a8..592212bf 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -110,6 +110,8 @@ dependencies { implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlin.retry) + implementation(libs.kotlin.retry.result) implementation(libs.glide.core) ksp(libs.glide.ksp) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e087b44..9ba91ce5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ coreKtx = "1.17.0" fragmentKtx = "1.8.9" glide = "5.0.5" kotlin = "2.3.0" +kotlinRetry = "2.0.2" kotlinxCoroutines = "1.10.2" ksp = "2.3.5" lifecycle = "2.10.0" @@ -31,6 +32,8 @@ androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "s bcprov-jdk18on = { group = "org.bouncycastle", name = "bcprov-jdk18on", version.ref = "bcprovJdk18on" } glide-core = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" } glide-ksp = { group = "com.github.bumptech.glide", name = "ksp", version.ref = "glide" } +kotlin-retry = { group = "com.michael-bull.kotlin-retry", name = "kotlin-retry", version.ref = "kotlinRetry" } +kotlin-retry-result = { group = "com.michael-bull.kotlin-retry", name = "kotlin-retry-result", version.ref = "kotlinRetry" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index e5833c6c..dd9d20c7 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1830,6 +1830,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8a24fd2c873858c4cee1c3afd1a62a1d7bd583e7 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 4 Feb 2026 15:54:54 +0000 Subject: [PATCH 10/11] don't swallow CancellationException in requestRepoUpdate() --- app/src/main/java/app/grapheneos/apps/core/PackageStates.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/app/grapheneos/apps/core/PackageStates.kt b/app/src/main/java/app/grapheneos/apps/core/PackageStates.kt index 156e52f0..f3e84267 100644 --- a/app/src/main/java/app/grapheneos/apps/core/PackageStates.kt +++ b/app/src/main/java/app/grapheneos/apps/core/PackageStates.kt @@ -48,6 +48,7 @@ import app.grapheneos.apps.core.getCachedRepo import app.grapheneos.apps.core.prunePackageCache import app.grapheneos.apps.util.getParcelableOrThrow import app.grapheneos.apps.util.simpleName +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.withContext import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes @@ -200,6 +201,9 @@ object PackageStates : LifecycleEventObserver { val repo = try { fetchRepo(currentRepo) } catch (t: Throwable) { + if (t is CancellationException) { + throw t + } Log.d(TAG, "", t) result = RepoUpdateError(t, isManuallyRequested) null From 5e80eff0352167aa8b5736aa06bc86ebdd0fe98d Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 4 Feb 2026 15:56:39 +0000 Subject: [PATCH 11/11] retry automatic repo update attempts --- .../main/java/app/grapheneos/apps/RpcProvider.kt | 3 +-- .../grapheneos/apps/autoupdate/AutoUpdateJob.kt | 2 +- .../grapheneos/apps/autoupdate/UpdateCheckJob.kt | 9 +-------- .../app/grapheneos/apps/core/PackageStates.kt | 15 +++++++++++++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/app/grapheneos/apps/RpcProvider.kt b/app/src/main/java/app/grapheneos/apps/RpcProvider.kt index fc8680a3..4a4eddf4 100644 --- a/app/src/main/java/app/grapheneos/apps/RpcProvider.kt +++ b/app/src/main/java/app/grapheneos/apps/RpcProvider.kt @@ -16,7 +16,6 @@ import app.grapheneos.apps.core.PackageState import app.grapheneos.apps.core.startPackageInstall import app.grapheneos.apps.core.pkgManager import app.grapheneos.apps.util.getApplicationInfo -import app.grapheneos.apps.util.getSharedPreferences import app.grapheneos.apps.util.maybeGetParcelable import app.grapheneos.apps.util.toInt import kotlinx.coroutines.CoroutineScope @@ -73,7 +72,7 @@ class RpcProvider : ContentProvider() { return false } - val repoUpdateError = PackageStates.requestRepoUpdate() + val repoUpdateError = PackageStates.requestRepoUpdateRetrying() val pkg = pkgState.rPackage if (repoUpdateError != null) { diff --git a/app/src/main/java/app/grapheneos/apps/autoupdate/AutoUpdateJob.kt b/app/src/main/java/app/grapheneos/apps/autoupdate/AutoUpdateJob.kt index 1adef0a2..e66e8793 100644 --- a/app/src/main/java/app/grapheneos/apps/autoupdate/AutoUpdateJob.kt +++ b/app/src/main/java/app/grapheneos/apps/autoupdate/AutoUpdateJob.kt @@ -42,7 +42,7 @@ class AutoUpdateJob : JobService() { val installParams = InstallParams(network, isUpdate = true, isUserInitiated = false) CoroutineScope(Dispatchers.Main).launch { - val repoUpdateError = PackageStates.requestRepoUpdate() + val repoUpdateError = PackageStates.requestRepoUpdateRetrying() if (repoUpdateError != null) { showUpdateCheckFailedNotification(repoUpdateError) diff --git a/app/src/main/java/app/grapheneos/apps/autoupdate/UpdateCheckJob.kt b/app/src/main/java/app/grapheneos/apps/autoupdate/UpdateCheckJob.kt index 46e78a90..98d001ea 100644 --- a/app/src/main/java/app/grapheneos/apps/autoupdate/UpdateCheckJob.kt +++ b/app/src/main/java/app/grapheneos/apps/autoupdate/UpdateCheckJob.kt @@ -25,9 +25,7 @@ import app.grapheneos.apps.util.isAppInstallationAllowed import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlin.time.Duration.Companion.seconds private const val TAG = "UpdateCheckJob" @@ -43,12 +41,7 @@ class UpdateCheckJob : JobService() { } job = CoroutineScope(Dispatchers.Main).launch { - // When there's an enabled VPN, this job is sometimes executed before VPN establishes - // its connection, which makes the job fail due to network not being up at that point. - // As a workaround, delay the network request for a bit. - delay(10.seconds) - - val repoUpdateError = PackageStates.requestRepoUpdate() + val repoUpdateError = PackageStates.requestRepoUpdateRetrying() if (repoUpdateError != null) { showUpdateCheckFailedNotification(repoUpdateError) } else { diff --git a/app/src/main/java/app/grapheneos/apps/core/PackageStates.kt b/app/src/main/java/app/grapheneos/apps/core/PackageStates.kt index f3e84267..86d72f89 100644 --- a/app/src/main/java/app/grapheneos/apps/core/PackageStates.kt +++ b/app/src/main/java/app/grapheneos/apps/core/PackageStates.kt @@ -9,7 +9,6 @@ import android.os.SystemClock import android.util.ArrayMap import android.util.ArraySet import android.util.Log -import androidx.core.content.ContextCompat import androidx.core.content.edit import androidx.core.os.postDelayed import androidx.core.util.isEmpty @@ -48,6 +47,10 @@ import app.grapheneos.apps.core.getCachedRepo import app.grapheneos.apps.core.prunePackageCache import app.grapheneos.apps.util.getParcelableOrThrow import app.grapheneos.apps.util.simpleName +import com.github.michaelbull.result.Err +import com.github.michaelbull.result.Ok +import com.github.michaelbull.retry.policy.binaryExponentialBackoff +import com.github.michaelbull.retry.result.retry import kotlinx.coroutines.CancellationException import kotlinx.coroutines.withContext import kotlin.time.Duration.Companion.hours @@ -180,6 +183,14 @@ object PackageStates : LifecycleEventObserver { } } + suspend fun requestRepoUpdateRetrying(force: Boolean = false, isManuallyRequested: Boolean = false): RepoUpdateError? { + val (_, err) = retry(binaryExponentialBackoff(1_000, 30_000)) { + val err = requestRepoUpdate(force, isManuallyRequested) + if (err != null) Err(err) else Ok(Unit) + } + return err + } + suspend fun requestRepoUpdate(force: Boolean = false, isManuallyRequested: Boolean = false): RepoUpdateError? { checkMainThread() @@ -204,7 +215,7 @@ object PackageStates : LifecycleEventObserver { if (t is CancellationException) { throw t } - Log.d(TAG, "", t) + Log.w(TAG, "unable to fetch repo", t) result = RepoUpdateError(t, isManuallyRequested) null }