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
This commit is contained in:
Rahul Patel
2025-02-28 03:21:38 +05:30
parent 33462d772a
commit b24eafea7a
4 changed files with 46 additions and 39 deletions

View File

@@ -22,8 +22,8 @@ package com.aurora.store
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.util.Log.INFO
import android.util.Log.DEBUG import android.util.Log.DEBUG
import android.util.Log.INFO
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.hilt.work.HiltWorkerFactory import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration import androidx.work.Configuration
@@ -43,8 +43,11 @@ import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp 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.cancel
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.lsposed.hiddenapibypass.HiddenApiBypass import org.lsposed.hiddenapibypass.HiddenApiBypass
import javax.inject.Inject import javax.inject.Inject
@@ -71,8 +74,7 @@ class AuroraApp : Application(), Configuration.Provider, SingletonImageLoader.Fa
.build() .build()
companion object { companion object {
// Alternative to GlobalScope var scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
var scope = MainScope()
private set private set
val enqueuedInstalls: MutableSet<String> = mutableSetOf() val enqueuedInstalls: MutableSet<String> = mutableSetOf()
@@ -95,8 +97,10 @@ class AuroraApp : Application(), Configuration.Provider, SingletonImageLoader.Fa
NotificationUtil.createNotificationChannel(this) NotificationUtil.createNotificationChannel(this)
// Initialize Download and Update helpers to observe and trigger downloads // Initialize Download and Update helpers to observe and trigger downloads
downloadHelper.init() scope.launch {
updateHelper.init() downloadHelper.init()
updateHelper.init()
}
//Register broadcast receiver for package install/uninstall //Register broadcast receiver for package install/uninstall
ContextCompat.registerReceiver( ContextCompat.registerReceiver(
@@ -112,7 +116,12 @@ class AuroraApp : Application(), Configuration.Provider, SingletonImageLoader.Fa
override fun onLowMemory() { override fun onLowMemory() {
super.onLowMemory() super.onLowMemory()
scope.cancel("onLowMemory() called by system") 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 { override fun newImageLoader(context: Context): ImageLoader {

View File

@@ -16,12 +16,12 @@ import com.aurora.store.data.room.update.Update
import com.aurora.store.data.work.DownloadWorker import com.aurora.store.data.work.DownloadWorker
import com.aurora.store.util.PathUtil import com.aurora.store.util.PathUtil
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
import javax.inject.Inject 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. * Removes failed download from the queue and starts observing for newly enqueued apps.
*/ */
fun init() { suspend fun init() {
AuroraApp.scope.launch { withContext(Dispatchers.IO) {
cancelFailedDownloads(downloadDao.downloads().firstOrNull() ?: emptyList()) cancelFailedDownloads(downloadDao.downloads().firstOrNull() ?: emptyList())
}.invokeOnCompletion {
observeDownloads()
} }
observeDownloads()
} }
private fun observeDownloads() { private suspend fun observeDownloads() {
AuroraApp.scope.launch { downloadDao.downloads().collect { list ->
downloadDao.downloads().collectLatest { list -> try {
// Check and trigger next download in queue, if any if (list.none { it.downloadStatus == DownloadStatus.DOWNLOADING }) {
if (!list.any { it.downloadStatus == DownloadStatus.DOWNLOADING }) { list.find { it.downloadStatus == DownloadStatus.QUEUED }
val enqueuedDownloads = list.filter { it.downloadStatus == DownloadStatus.QUEUED } ?.let { queuedDownload ->
enqueuedDownloads.firstOrNull()?.let { Log.i(TAG, "Enqueued download worker for ${queuedDownload.packageName}")
try { trigger(queuedDownload)
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)
} }
}
} }
} 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 // Ensure all app downloads are unique to preserve individual records
WorkManager.getInstance(context) WorkManager.getInstance(context)
.enqueueUniqueWork( .enqueueUniqueWork(
"$DOWNLOAD_WORKER/${download.packageName}", "$DOWNLOAD_WORKER/${download.packageName}/${download.versionCode}",
ExistingWorkPolicy.KEEP, work ExistingWorkPolicy.KEEP,
work
) )
} }
} }

View File

@@ -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.PREFERENCES_UPDATES_RESTRICTIONS_METERED
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit.HOURS import java.util.concurrent.TimeUnit.HOURS
import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.TimeUnit.MINUTES
import javax.inject.Inject import javax.inject.Inject
@@ -68,12 +69,12 @@ class UpdateHelper @Inject constructor(
/** /**
* Deletes invalid updates from database and starts observing events * Deletes invalid updates from database and starts observing events
*/ */
fun init() { suspend fun init() {
AuroraApp.scope.launch { withContext(Dispatchers.IO) {
deleteInvalidUpdates() deleteInvalidUpdates()
}.invokeOnCompletion {
observeUpdates()
} }
observeUpdates()
} }
private fun observeUpdates() { private fun observeUpdates() {
@@ -137,7 +138,7 @@ class UpdateHelper @Inject constructor(
* @see [UpdateWorker] * @see [UpdateWorker]
*/ */
fun scheduleAutomatedCheck() { fun scheduleAutomatedCheck() {
Log.i(TAG,"Scheduling periodic app updates!") Log.i(TAG, "Scheduling periodic app updates!")
WorkManager.getInstance(context) WorkManager.getInstance(context)
.enqueueUniquePeriodicWork( .enqueueUniquePeriodicWork(
UPDATE_WORKER, UPDATE_WORKER,
@@ -151,7 +152,7 @@ class UpdateHelper @Inject constructor(
* @see [UpdateWorker] * @see [UpdateWorker]
*/ */
fun updateAutomatedCheck() { fun updateAutomatedCheck() {
Log.i(TAG,"Updating periodic app updates!") Log.i(TAG, "Updating periodic app updates!")
WorkManager.getInstance(context).updateWork(getAutoUpdateWork()) WorkManager.getInstance(context).updateWork(getAutoUpdateWork())
} }

View File

@@ -176,7 +176,7 @@ class DownloadWorker @AssistedInject constructor(
throw Exceptions.DownloadCancelledException() throw Exceptions.DownloadCancelledException()
} }
downloadFile(file) downloadFile(download.packageName, file)
download.downloadedFiles++ download.downloadedFiles++
} }
} catch (exception: Exception) { } catch (exception: Exception) {
@@ -298,8 +298,8 @@ class DownloadWorker @AssistedInject constructor(
* @param gFile A [GPlayFile] to download * @param gFile A [GPlayFile] to download
* @return A [Boolean] indicating whether the file was downloaded or not. * @return A [Boolean] indicating whether the file was downloaded or not.
*/ */
private suspend fun downloadFile(gFile: GPlayFile): Boolean = withContext(Dispatchers.IO) { private suspend fun downloadFile(packageName: String, gFile: GPlayFile): Boolean = withContext(Dispatchers.IO) {
Log.i(TAG, "Downloading ${gFile.name}") Log.i(TAG, "Downloading $packageName @ ${gFile.name}")
val file = PathUtil.getLocalFile(appContext, gFile, download) val file = PathUtil.getLocalFile(appContext, gFile, download)
// If file exists and has integrity intact, no need to download again // If file exists and has integrity intact, no need to download again