Skip to content
Merged
54 changes: 27 additions & 27 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down Expand Up @@ -91,28 +91,28 @@ 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.kotlin.retry)
implementation(libs.kotlin.retry.result)

implementation(libs.glide.core)
ksp(libs.glide.ksp)
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<application
android:name=".ApplicationImpl"
android:dataExtractionRules="@xml/backup_rules"
android:enableOnBackInvokedCallback="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/java/app/grapheneos/apps/RpcProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -73,7 +72,7 @@ class RpcProvider : ContentProvider() {
return false
}

val repoUpdateError = PackageStates.requestRepoUpdate()
val repoUpdateError = PackageStates.requestRepoUpdateRetrying()
val pkg = pkgState.rPackage

if (repoUpdateError != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/app/grapheneos/apps/core/InstallStart.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
19 changes: 17 additions & 2 deletions app/src/main/java/app/grapheneos/apps/core/PackageStates.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -48,6 +47,11 @@ 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
import kotlin.time.Duration.Companion.minutes
Expand Down Expand Up @@ -179,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()

Expand All @@ -200,7 +212,10 @@ object PackageStates : LifecycleEventObserver {
val repo = try {
fetchRepo(currentRepo)
} catch (t: Throwable) {
Log.d(TAG, "", t)
if (t is CancellationException) {
throw t
}
Log.w(TAG, "unable to fetch repo", t)
result = RepoUpdateError(t, isManuallyRequested)
null
}
Expand Down
9 changes: 5 additions & 4 deletions app/src/main/java/app/grapheneos/apps/core/Repo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -128,7 +129,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),
Expand All @@ -139,7 +140,7 @@ fun findRPackage(variants: List<RPackage>, 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),
Expand Down Expand Up @@ -372,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)
}
}
Expand Down Expand Up @@ -427,7 +428,7 @@ class RPackage(val common: RPackageContainer, val versionCode: Long, val abis: A
val set = ArraySet<Locale>(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
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/app/grapheneos/apps/ui/ActivityUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 1 addition & 4 deletions app/src/main/java/app/grapheneos/apps/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/app/grapheneos/apps/util/BundleUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ fun unmarshallParcelableInner(bytes: ByteArray, type: Class<out Parcelable>): Pa
if (obj is Bundle) {
obj.classLoader = ApplicationImpl::class.java.classLoader
}
return obj as Parcelable
return obj
} finally {
parcel.recycle()
}
Expand Down
13 changes: 4 additions & 9 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
45 changes: 45 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[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"
kotlinRetry = "2.0.2"
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" }
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" }

[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" }
Loading
Loading