From b24eafea7a517f5be22db353e2abdfbaef8115d6 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Fri, 28 Feb 2025 03:21:38 +0530 Subject: [PATCH] Audit DownloadHelper & UpdateHelper scopes Action Items: - monitor changes for ~3days, do self-test to ensure it doesn't break anything - also verify changes on older devices & other android skins MIUI, OneUI, EMUI --- .../main/java/com/aurora/store/AuroraApp.kt | 23 +++++++---- .../store/data/helper/DownloadHelper.kt | 41 +++++++++---------- .../aurora/store/data/helper/UpdateHelper.kt | 15 +++---- .../aurora/store/data/work/DownloadWorker.kt | 6 +-- 4 files changed, 46 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/com/aurora/store/AuroraApp.kt b/app/src/main/java/com/aurora/store/AuroraApp.kt index a6d4a677a..32eb4aa06 100644 --- a/app/src/main/java/com/aurora/store/AuroraApp.kt +++ b/app/src/main/java/com/aurora/store/AuroraApp.kt @@ -22,8 +22,8 @@ package com.aurora.store import android.app.Application import android.content.Context -import android.util.Log.INFO import android.util.Log.DEBUG +import android.util.Log.INFO import androidx.core.content.ContextCompat import androidx.hilt.work.HiltWorkerFactory import androidx.work.Configuration @@ -43,8 +43,11 @@ import com.aurora.store.util.PackageUtil import com.aurora.store.util.Preferences import com.google.android.material.color.DynamicColors import dagger.hilt.android.HiltAndroidApp -import kotlinx.coroutines.MainScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import okhttp3.OkHttpClient import org.lsposed.hiddenapibypass.HiddenApiBypass import javax.inject.Inject @@ -71,8 +74,7 @@ class AuroraApp : Application(), Configuration.Provider, SingletonImageLoader.Fa .build() companion object { - // Alternative to GlobalScope - var scope = MainScope() + var scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private set val enqueuedInstalls: MutableSet = mutableSetOf() @@ -95,8 +97,10 @@ class AuroraApp : Application(), Configuration.Provider, SingletonImageLoader.Fa NotificationUtil.createNotificationChannel(this) // Initialize Download and Update helpers to observe and trigger downloads - downloadHelper.init() - updateHelper.init() + scope.launch { + downloadHelper.init() + updateHelper.init() + } //Register broadcast receiver for package install/uninstall ContextCompat.registerReceiver( @@ -112,7 +116,12 @@ class AuroraApp : Application(), Configuration.Provider, SingletonImageLoader.Fa override fun onLowMemory() { super.onLowMemory() scope.cancel("onLowMemory() called by system") - scope = MainScope() + scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + } + + override fun onTerminate() { + super.onTerminate() + scope.cancel() } override fun newImageLoader(context: Context): ImageLoader { diff --git a/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt b/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt index 9bde44706..433f1c2af 100644 --- a/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt +++ b/app/src/main/java/com/aurora/store/data/helper/DownloadHelper.kt @@ -16,12 +16,12 @@ import com.aurora.store.data.room.update.Update import com.aurora.store.data.work.DownloadWorker import com.aurora.store.util.PathUtil import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject /** @@ -49,30 +49,26 @@ class DownloadHelper @Inject constructor( /** * Removes failed download from the queue and starts observing for newly enqueued apps. */ - fun init() { - AuroraApp.scope.launch { + suspend fun init() { + withContext(Dispatchers.IO) { cancelFailedDownloads(downloadDao.downloads().firstOrNull() ?: emptyList()) - }.invokeOnCompletion { - observeDownloads() } + + observeDownloads() } - private fun observeDownloads() { - AuroraApp.scope.launch { - downloadDao.downloads().collectLatest { list -> - // Check and trigger next download in queue, if any - if (!list.any { it.downloadStatus == DownloadStatus.DOWNLOADING }) { - val enqueuedDownloads = list.filter { it.downloadStatus == DownloadStatus.QUEUED } - enqueuedDownloads.firstOrNull()?.let { - try { - Log.i(DOWNLOAD_WORKER, "Downloading ${it.packageName}") - trigger(it) - } catch (exception: Exception) { - Log.i(DOWNLOAD_WORKER, "Failed to download app", exception) - downloadDao.updateStatus(it.packageName, DownloadStatus.FAILED) + private suspend fun observeDownloads() { + downloadDao.downloads().collect { list -> + try { + if (list.none { it.downloadStatus == DownloadStatus.DOWNLOADING }) { + list.find { it.downloadStatus == DownloadStatus.QUEUED } + ?.let { queuedDownload -> + Log.i(TAG, "Enqueued download worker for ${queuedDownload.packageName}") + trigger(queuedDownload) } - } } + } catch (exception: Exception) { + Log.e(TAG, "Failed to enqueue download worker", exception) } } } @@ -178,8 +174,9 @@ class DownloadHelper @Inject constructor( // Ensure all app downloads are unique to preserve individual records WorkManager.getInstance(context) .enqueueUniqueWork( - "$DOWNLOAD_WORKER/${download.packageName}", - ExistingWorkPolicy.KEEP, work + "$DOWNLOAD_WORKER/${download.packageName}/${download.versionCode}", + ExistingWorkPolicy.KEEP, + work ) } } diff --git a/app/src/main/java/com/aurora/store/data/helper/UpdateHelper.kt b/app/src/main/java/com/aurora/store/data/helper/UpdateHelper.kt index d9617df8c..7f7447bcf 100644 --- a/app/src/main/java/com/aurora/store/data/helper/UpdateHelper.kt +++ b/app/src/main/java/com/aurora/store/data/helper/UpdateHelper.kt @@ -25,13 +25,14 @@ import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_IDLE import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_METERED import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.util.concurrent.TimeUnit.HOURS import java.util.concurrent.TimeUnit.MINUTES import javax.inject.Inject @@ -68,12 +69,12 @@ class UpdateHelper @Inject constructor( /** * Deletes invalid updates from database and starts observing events */ - fun init() { - AuroraApp.scope.launch { + suspend fun init() { + withContext(Dispatchers.IO) { deleteInvalidUpdates() - }.invokeOnCompletion { - observeUpdates() } + + observeUpdates() } private fun observeUpdates() { @@ -137,7 +138,7 @@ class UpdateHelper @Inject constructor( * @see [UpdateWorker] */ fun scheduleAutomatedCheck() { - Log.i(TAG,"Scheduling periodic app updates!") + Log.i(TAG, "Scheduling periodic app updates!") WorkManager.getInstance(context) .enqueueUniquePeriodicWork( UPDATE_WORKER, @@ -151,7 +152,7 @@ class UpdateHelper @Inject constructor( * @see [UpdateWorker] */ fun updateAutomatedCheck() { - Log.i(TAG,"Updating periodic app updates!") + Log.i(TAG, "Updating periodic app updates!") WorkManager.getInstance(context).updateWork(getAutoUpdateWork()) } diff --git a/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt b/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt index caed2b3ca..26efe0fe2 100644 --- a/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt +++ b/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt @@ -176,7 +176,7 @@ class DownloadWorker @AssistedInject constructor( throw Exceptions.DownloadCancelledException() } - downloadFile(file) + downloadFile(download.packageName, file) download.downloadedFiles++ } } catch (exception: Exception) { @@ -298,8 +298,8 @@ class DownloadWorker @AssistedInject constructor( * @param gFile A [GPlayFile] to download * @return A [Boolean] indicating whether the file was downloaded or not. */ - private suspend fun downloadFile(gFile: GPlayFile): Boolean = withContext(Dispatchers.IO) { - Log.i(TAG, "Downloading ${gFile.name}") + private suspend fun downloadFile(packageName: String, gFile: GPlayFile): Boolean = withContext(Dispatchers.IO) { + Log.i(TAG, "Downloading $packageName @ ${gFile.name}") val file = PathUtil.getLocalFile(appContext, gFile, download) // If file exists and has integrity intact, no need to download again