DownloadWorker: Move downloads observing logic into worker as well
This allows us to keep all the modifications related to DownloadWorker in a single place. Also, drop the 3seconds delay and do the cleanup instead of delegating it to worker if last job was failed. This is better and more error-proof. Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -185,7 +185,7 @@ dependencies {
|
|||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
|
||||||
//WorkManager
|
//WorkManager
|
||||||
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
implementation("androidx.work:work-runtime-ktx:2.9.0-rc01")
|
||||||
|
|
||||||
// LeakCanary
|
// LeakCanary
|
||||||
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
|
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
|
||||||
|
|||||||
@@ -28,15 +28,3 @@ fun <T> MutableSet<T>.flushAndAdd(list: Set<T>) {
|
|||||||
clear()
|
clear()
|
||||||
addAll(list)
|
addAll(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> MutableSet<T>.copyAndAdd(element: T): MutableSet<T> {
|
|
||||||
val newSet = this.toMutableSet()
|
|
||||||
newSet.add(element)
|
|
||||||
return newSet
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> MutableSet<T>.copyAndRemove(element: T): MutableSet<T> {
|
|
||||||
val newSet = this.toMutableSet()
|
|
||||||
newSet.remove(element)
|
|
||||||
return newSet
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ import com.aurora.gplayapi.data.models.App
|
|||||||
import com.aurora.store.data.downloader.DownloadManager
|
import com.aurora.store.data.downloader.DownloadManager
|
||||||
import com.aurora.store.data.receiver.PackageManagerReceiver
|
import com.aurora.store.data.receiver.PackageManagerReceiver
|
||||||
import com.aurora.store.data.service.NotificationService
|
import com.aurora.store.data.service.NotificationService
|
||||||
|
import com.aurora.store.data.work.DownloadWorker
|
||||||
import com.aurora.store.util.CommonUtil
|
import com.aurora.store.util.CommonUtil
|
||||||
import com.aurora.store.util.NotificationUtil
|
import com.aurora.store.util.NotificationUtil
|
||||||
import com.aurora.store.util.PackageUtil
|
import com.aurora.store.util.PackageUtil
|
||||||
import com.tonyodev.fetch2.Fetch
|
import com.tonyodev.fetch2.Fetch
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
|
|
||||||
class AuroraApplication : Application() {
|
class AuroraApplication : Application() {
|
||||||
@@ -39,7 +39,7 @@ class AuroraApplication : Application() {
|
|||||||
private lateinit var fetch: Fetch
|
private lateinit var fetch: Fetch
|
||||||
|
|
||||||
companion object{
|
companion object{
|
||||||
val enqueuedDownloads = MutableStateFlow<MutableSet<App>>(mutableSetOf())
|
val enqueuedDownloads = mutableSetOf<App>()
|
||||||
val enqueuedInstalls: MutableSet<String> = mutableSetOf()
|
val enqueuedInstalls: MutableSet<String> = mutableSetOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +58,9 @@ class AuroraApplication : Application() {
|
|||||||
|
|
||||||
fetch = DownloadManager.with(this).fetch
|
fetch = DownloadManager.with(this).fetch
|
||||||
|
|
||||||
|
// Initialize DownloadWorker to observe and trigger downloads
|
||||||
|
DownloadWorker.initDownloadWorker(applicationContext)
|
||||||
|
|
||||||
//Register broadcast receiver for package install/uninstall
|
//Register broadcast receiver for package install/uninstall
|
||||||
ContextCompat.registerReceiver(
|
ContextCompat.registerReceiver(
|
||||||
this,
|
this,
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ import com.aurora.extensions.toast
|
|||||||
import com.aurora.store.data.model.NetworkStatus
|
import com.aurora.store.data.model.NetworkStatus
|
||||||
import com.aurora.store.data.model.SelfUpdate
|
import com.aurora.store.data.model.SelfUpdate
|
||||||
import com.aurora.store.data.providers.NetworkProvider
|
import com.aurora.store.data.providers.NetworkProvider
|
||||||
import com.aurora.store.data.work.DownloadWorker
|
|
||||||
import com.aurora.store.databinding.ActivityMainBinding
|
import com.aurora.store.databinding.ActivityMainBinding
|
||||||
import com.aurora.store.util.Log
|
import com.aurora.store.util.Log
|
||||||
import com.aurora.store.util.Preferences
|
import com.aurora.store.util.Preferences
|
||||||
@@ -70,8 +69,6 @@ import com.aurora.store.view.ui.sheets.SelfUpdateSheet
|
|||||||
import com.aurora.store.viewmodel.MainViewModel
|
import com.aurora.store.viewmodel.MainViewModel
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
@@ -217,22 +214,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle enqueued downloads
|
|
||||||
GlobalScope.launch {
|
|
||||||
AuroraApplication.enqueuedDownloads.collect { enqueuedDownloads ->
|
|
||||||
try {
|
|
||||||
if (enqueuedDownloads.isNotEmpty()) {
|
|
||||||
// Let any existing work related to download/install get finished
|
|
||||||
delay(3000)
|
|
||||||
Log.i("Downloading ${enqueuedDownloads.first().packageName}")
|
|
||||||
DownloadWorker.downloadApp(applicationContext, enqueuedDownloads.first())
|
|
||||||
}
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
Log.i("Failed to download enqueued apps", exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
|||||||
@@ -12,13 +12,12 @@ import androidx.work.ExistingWorkPolicy.KEEP
|
|||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.OutOfQuotaPolicy
|
import androidx.work.OutOfQuotaPolicy
|
||||||
|
import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.aurora.Constants
|
import com.aurora.Constants
|
||||||
import com.aurora.extensions.copyAndAdd
|
|
||||||
import com.aurora.extensions.copyTo
|
import com.aurora.extensions.copyTo
|
||||||
import com.aurora.extensions.isQAndAbove
|
import com.aurora.extensions.isQAndAbove
|
||||||
import com.aurora.extensions.copyAndRemove
|
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||||
import com.aurora.store.AuroraApplication
|
import com.aurora.store.AuroraApplication
|
||||||
@@ -36,7 +35,8 @@ import kotlinx.coroutines.flow.collectLatest
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.io.path.ExperimentalPathApi
|
import kotlin.io.path.ExperimentalPathApi
|
||||||
import kotlin.io.path.createDirectories
|
import kotlin.io.path.createDirectories
|
||||||
import kotlin.io.path.deleteRecursively
|
import kotlin.io.path.deleteRecursively
|
||||||
@@ -58,16 +58,54 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
private const val DOWNLOAD_APP = "DOWNLOAD_APP"
|
private const val DOWNLOAD_APP = "DOWNLOAD_APP"
|
||||||
private const val DOWNLOAD_UPDATE = "DOWNLOAD_UPDATE"
|
private const val DOWNLOAD_UPDATE = "DOWNLOAD_UPDATE"
|
||||||
|
|
||||||
fun isEnqueued(packageName: String): Boolean {
|
private const val notificationID = 200
|
||||||
return AuroraApplication.enqueuedDownloads.value.any { it.packageName == packageName }
|
private val cleanupStates = listOf(
|
||||||
|
WorkInfo.State.CANCELLED,
|
||||||
|
WorkInfo.State.FAILED
|
||||||
|
)
|
||||||
|
|
||||||
|
fun initDownloadWorker(applicationContext: Context) {
|
||||||
|
GlobalScope.launch {
|
||||||
|
WorkManager.getInstance(applicationContext)
|
||||||
|
.getWorkInfosByTagFlow(DOWNLOAD_WORKER)
|
||||||
|
.collectLatest { downloadsList ->
|
||||||
|
try {
|
||||||
|
if (downloadsList.all { it.state.isFinished }) {
|
||||||
|
// Do cleanup for last download if required
|
||||||
|
downloadsList.getOrNull(0)?.let { workInfo ->
|
||||||
|
if (workInfo.state in cleanupStates) {
|
||||||
|
onFailure(
|
||||||
|
applicationContext,
|
||||||
|
AuroraApplication.enqueuedDownloads.first()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and trigger download if enqueue list is not empty
|
||||||
|
if (AuroraApplication.enqueuedDownloads.isNotEmpty()) {
|
||||||
|
val app = AuroraApplication.enqueuedDownloads.first()
|
||||||
|
Log.i(DOWNLOAD_WORKER, "Downloading ${app.packageName}")
|
||||||
|
downloadApp(applicationContext, app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Log.i(DOWNLOAD_WORKER, "Failed to download enqueued apps", exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enqueueApp(app: App) {
|
fun isEnqueued(packageName: String): Boolean {
|
||||||
AuroraApplication.enqueuedDownloads.update { it.copyAndAdd(app) }
|
return AuroraApplication.enqueuedDownloads.any { it.packageName == packageName }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enqueueApp(context: Context, app: App) {
|
||||||
|
if (AuroraApplication.enqueuedDownloads.isEmpty()) downloadApp(context, app)
|
||||||
|
AuroraApplication.enqueuedDownloads.add(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelDownload(context: Context, app: App) {
|
fun cancelDownload(context: Context, app: App) {
|
||||||
AuroraApplication.enqueuedDownloads.update {it.copyAndRemove(app) }
|
|
||||||
WorkManager.getInstance(context).cancelAllWorkByTag(app.packageName)
|
WorkManager.getInstance(context).cancelAllWorkByTag(app.packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,13 +116,7 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
if (updates) workManager.cancelAllWorkByTag(DOWNLOAD_UPDATE)
|
if (updates) workManager.cancelAllWorkByTag(DOWNLOAD_UPDATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun downloadApp(context: Context, app: App) {
|
||||||
* Downloads and install an [App]
|
|
||||||
*
|
|
||||||
* Triggers Immediate downloads and installation. In most cases, you don't need to call
|
|
||||||
* this method. Consider using [enqueueApp] instead.
|
|
||||||
*/
|
|
||||||
fun downloadApp(context: Context, app: App) {
|
|
||||||
val work = OneTimeWorkRequestBuilder<DownloadWorker>()
|
val work = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||||
.addTag(DOWNLOAD_WORKER)
|
.addTag(DOWNLOAD_WORKER)
|
||||||
.addTag(app.packageName)
|
.addTag(app.packageName)
|
||||||
@@ -98,6 +130,17 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
WorkManager.getInstance(context)
|
WorkManager.getInstance(context)
|
||||||
.enqueueUniqueWork("${DOWNLOAD_WORKER}/${app.packageName}", KEEP, work)
|
.enqueueUniqueWork("${DOWNLOAD_WORKER}/${app.packageName}", KEEP, work)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPathApi::class)
|
||||||
|
private fun onFailure(context: Context, app: App) {
|
||||||
|
Log.i(DOWNLOAD_WORKER, "Cleaning up!")
|
||||||
|
PathUtil.getAppDownloadDir(context, app.packageName, app.versionCode)
|
||||||
|
.deleteRecursively()
|
||||||
|
with (context.getSystemService(Service.NOTIFICATION_SERVICE) as NotificationManager) {
|
||||||
|
cancel(notificationID)
|
||||||
|
}
|
||||||
|
AuroraApplication.enqueuedDownloads.remove(app)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var app: App
|
private lateinit var app: App
|
||||||
@@ -109,7 +152,6 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
private var downloadedBytes = 0L
|
private var downloadedBytes = 0L
|
||||||
|
|
||||||
private val TAG = DownloadWorker::class.java.simpleName
|
private val TAG = DownloadWorker::class.java.simpleName
|
||||||
private val notificationID = 200
|
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
// Purchase the app (free apps needs to be purchased too)
|
// Purchase the app (free apps needs to be purchased too)
|
||||||
@@ -122,7 +164,7 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
|
|
||||||
// Try to parse input data into a valid app
|
// Try to parse input data into a valid app
|
||||||
try {
|
try {
|
||||||
app = AuroraApplication.enqueuedDownloads.value.first()
|
app = AuroraApplication.enqueuedDownloads.first()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Log.e(TAG, "No apps enqueued for downloads", exception)
|
Log.e(TAG, "No apps enqueued for downloads", exception)
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
@@ -155,13 +197,11 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
.onFailure {
|
.onFailure {
|
||||||
Log.e(TAG, "Failed to download ${app.packageName}", it)
|
Log.e(TAG, "Failed to download ${app.packageName}", it)
|
||||||
downloading = false
|
downloading = false
|
||||||
onFailure()
|
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
}
|
}
|
||||||
while (downloading) {
|
while (downloading) {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
if (isStopped) {
|
if (isStopped) {
|
||||||
onFailure()
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,19 +222,10 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the app from the list
|
// Remove the app from the list
|
||||||
AuroraApplication.enqueuedDownloads.update { it.copyAndRemove(app) }
|
AuroraApplication.enqueuedDownloads.remove(app)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalPathApi::class)
|
|
||||||
private fun onFailure() {
|
|
||||||
Log.i(TAG, "Cleaning up!")
|
|
||||||
PathUtil.getAppDownloadDir(appContext, app.packageName, app.versionCode)
|
|
||||||
.deleteRecursively()
|
|
||||||
notificationManager.cancel(notificationID)
|
|
||||||
AuroraApplication.enqueuedDownloads.update { it.copyAndRemove(app) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDownloadRequest(files: List<GPlayFile>): List<Request> {
|
private fun getDownloadRequest(files: List<GPlayFile>): List<Request> {
|
||||||
val downloadList = mutableListOf<Request>()
|
val downloadList = mutableListOf<Request>()
|
||||||
files.filter { it.url.isNotBlank() }.forEach {
|
files.filter { it.url.isNotBlank() }.forEach {
|
||||||
|
|||||||
Reference in New Issue
Block a user