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:
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user