diff --git a/app/src/main/java/com/aurora/extensions/Collection.kt b/app/src/main/java/com/aurora/extensions/Collection.kt index c987fc362..7ead5a888 100644 --- a/app/src/main/java/com/aurora/extensions/Collection.kt +++ b/app/src/main/java/com/aurora/extensions/Collection.kt @@ -27,4 +27,16 @@ fun MutableList.flushAndAdd(list: List) { fun MutableSet.flushAndAdd(list: Set) { clear() addAll(list) -} \ No newline at end of file +} + +fun MutableSet.copyAndAdd(element: T): MutableSet { + val newSet = this.toMutableSet() + newSet.add(element) + return newSet +} + +fun MutableSet.copyAndRemove(element: T): MutableSet { + val newSet = this.toMutableSet() + newSet.remove(element) + return newSet +} diff --git a/app/src/main/java/com/aurora/store/AuroraApplication.kt b/app/src/main/java/com/aurora/store/AuroraApplication.kt index 9e0ecbfb9..401f6b672 100644 --- a/app/src/main/java/com/aurora/store/AuroraApplication.kt +++ b/app/src/main/java/com/aurora/store/AuroraApplication.kt @@ -23,6 +23,7 @@ package com.aurora.store import android.app.Application import androidx.core.content.ContextCompat import com.aurora.extensions.isPAndAbove +import com.aurora.gplayapi.data.models.App import com.aurora.store.data.downloader.DownloadManager import com.aurora.store.data.receiver.PackageManagerReceiver import com.aurora.store.data.service.NotificationService @@ -30,6 +31,7 @@ import com.aurora.store.util.CommonUtil import com.aurora.store.util.NotificationUtil import com.aurora.store.util.PackageUtil import com.tonyodev.fetch2.Fetch +import kotlinx.coroutines.flow.MutableStateFlow import org.lsposed.hiddenapibypass.HiddenApiBypass class AuroraApplication : Application() { @@ -37,6 +39,7 @@ class AuroraApplication : Application() { private lateinit var fetch: Fetch companion object{ + val enqueuedDownloads = MutableStateFlow>(mutableSetOf()) val enqueuedInstalls: MutableSet = mutableSetOf() } diff --git a/app/src/main/java/com/aurora/store/MainActivity.kt b/app/src/main/java/com/aurora/store/MainActivity.kt index 623ab684b..88f7e2e80 100644 --- a/app/src/main/java/com/aurora/store/MainActivity.kt +++ b/app/src/main/java/com/aurora/store/MainActivity.kt @@ -59,8 +59,8 @@ import com.aurora.extensions.toast import com.aurora.store.data.model.NetworkStatus import com.aurora.store.data.model.SelfUpdate import com.aurora.store.data.providers.NetworkProvider +import com.aurora.store.data.work.DownloadWorker import com.aurora.store.databinding.ActivityMainBinding -import com.aurora.store.util.CertUtil import com.aurora.store.util.Log import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB @@ -69,6 +69,9 @@ import com.aurora.store.view.ui.sheets.NetworkDialogSheet import com.aurora.store.view.ui.sheets.SelfUpdateSheet import com.aurora.store.viewmodel.MainViewModel import com.google.android.material.bottomnavigation.BottomNavigationView +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -92,6 +95,7 @@ class MainActivity : AppCompatActivity() { R.id.updatesFragment ) + @OptIn(DelicateCoroutinesApi::class) override fun onCreate(savedInstanceState: Bundle?) { applyThemeAccent() super.onCreate(savedInstanceState) @@ -213,6 +217,22 @@ 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 { diff --git a/app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt b/app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt index 94083840f..bfb43b443 100644 --- a/app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt +++ b/app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt @@ -46,11 +46,7 @@ class DownloadManager private constructor(var context: Context) { } private fun getFetchConfiguration(context: Context): FetchConfiguration { - var maxActive = Preferences.getInteger(context, Preferences.PREFERENCE_DOWNLOAD_ACTIVE) - if (maxActive == 0) - maxActive = 3 return FetchConfiguration.Builder(context) - .setDownloadConcurrentLimit(maxActive) .enableLogging(BuildConfig.DEBUG) .enableHashCheck(true) .enableFileExistChecks(true) 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 c37d49dae..7a67de495 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 @@ -8,17 +8,20 @@ import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC import android.util.Log import androidx.work.CoroutineWorker import androidx.work.Data -import androidx.work.ExistingWorkPolicy.REPLACE +import androidx.work.ExistingWorkPolicy.KEEP import androidx.work.ForegroundInfo import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OutOfQuotaPolicy import androidx.work.WorkManager import androidx.work.WorkerParameters import com.aurora.Constants +import com.aurora.extensions.copyAndAdd import com.aurora.extensions.copyTo import com.aurora.extensions.isQAndAbove +import com.aurora.extensions.copyAndRemove import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.helpers.PurchaseHelper +import com.aurora.store.AuroraApplication import com.aurora.store.data.model.DownloadStatus import com.aurora.store.data.model.Request import com.aurora.store.data.network.HttpClient @@ -26,13 +29,13 @@ import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.receiver.InstallReceiver import com.aurora.store.util.NotificationUtil import com.aurora.store.util.PathUtil -import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.withContext import java.io.File import java.net.URL +import kotlinx.coroutines.flow.update import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.createDirectories import kotlin.io.path.deleteRecursively @@ -42,25 +45,27 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame CoroutineWorker(appContext, workerParams) { companion object { - const val DOWNLOAD_DATA = "DOWNLOAD_DATA" + const val DOWNLOAD_WORKER = "DOWNLOAD_WORKER" const val DOWNLOAD_PROGRESS = "DOWNLOAD_PROGRESS" - private const val TAG = "DownloadWorker" + fun enqueueApp(app: App) { + AuroraApplication.enqueuedDownloads.update { it.copyAndAdd(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) { - Log.i(TAG, "Downloading ${app.packageName}") - - val downloadData = Data.Builder() - .putString(DOWNLOAD_DATA, Gson().toJson(app)) - .build() - val work = OneTimeWorkRequestBuilder() - .setInputData(downloadData) + .addTag(app.packageName) .addTag(app.versionCode.toString()) .setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST) .build() - WorkManager.getInstance(context).enqueueUniqueWork(app.packageName, REPLACE, work) + WorkManager.getInstance(context).enqueueUniqueWork(DOWNLOAD_WORKER, KEEP, work) } } @@ -71,8 +76,6 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame private val TAG = DownloadWorker::class.java.simpleName private val notificationID = 200 - private val gson = Gson() - override suspend fun doWork(): Result { // Purchase the app (free apps needs to be purchased too) val authData = AuthProvider.with(appContext).getAuthData() @@ -83,14 +86,11 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame appContext.getSystemService(Service.NOTIFICATION_SERVICE) as NotificationManager // Try to parse input data into a valid app - withContext(Dispatchers.Default) { - try { - app = gson.fromJson(inputData.getString(DOWNLOAD_DATA), App::class.java) - } catch (exception: Exception) { - Log.e(TAG, "Failed parsing requested app!", exception) - notifyStatus(DownloadStatus.FAILED) - return@withContext Result.failure() - } + try { + app = AuroraApplication.enqueuedDownloads.value.first() + } catch (exception: Exception) { + Log.e(TAG, "No apps enqueued for downloads", exception) + return Result.failure() } // Set work/service to foreground on < Android 12.0 @@ -140,6 +140,9 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame appContext.sendBroadcast(it) } + + // Remove the app from the list + AuroraApplication.enqueuedDownloads.update { it.copyAndRemove(app) } return Result.success() } @@ -149,6 +152,7 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame PathUtil.getAppDownloadDir(appContext, app.packageName, app.versionCode) .deleteRecursively() notificationManager.cancel(notificationID) + AuroraApplication.enqueuedDownloads.update { it.copyAndRemove(app) } } private fun getDownloadRequest(files: List): List { diff --git a/app/src/main/java/com/aurora/store/util/Preferences.kt b/app/src/main/java/com/aurora/store/util/Preferences.kt index da9b49beb..01589c08e 100644 --- a/app/src/main/java/com/aurora/store/util/Preferences.kt +++ b/app/src/main/java/com/aurora/store/util/Preferences.kt @@ -48,7 +48,6 @@ object Preferences { const val INSTALLATION_ABANDON_SESSION = "INSTALLATION_ABANDON_SESSION" - const val PREFERENCE_DOWNLOAD_ACTIVE = "PREFERENCE_DOWNLOAD_ACTIVE" const val PREFERENCE_DOWNLOAD_EXTERNAL = "PREFERENCE_DOWNLOAD_EXTERNAL" const val PREFERENCE_DOWNLOAD_DIRECTORY = "PREFERENCE_DOWNLOAD_DIRECTORY" const val PREFERENCE_DOWNLOAD_WIFI_ONLY = "PREFERENCE_DOWNLOAD_WIFI_ONLY" diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt index bbdabcd09..2d7461f5e 100644 --- a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt @@ -38,7 +38,6 @@ import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB -import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_ACTIVE import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_DIRECTORY import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_EXTERNAL import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_WIFI_ONLY @@ -165,7 +164,6 @@ class OnboardingFragment : Fragment(R.layout.fragment_onboarding) { save(PREFERENCE_FILTER_SEARCH, true) /*Downloader*/ - save(PREFERENCE_DOWNLOAD_ACTIVE, 3) save(PREFERENCE_DOWNLOAD_EXTERNAL, false) save(PREFERENCE_DOWNLOAD_DIRECTORY, PathUtil.getExternalPath(requireContext())) save(PREFERENCE_DOWNLOAD_WIFI_ONLY, false) diff --git a/app/src/main/res/xml/preferences_download.xml b/app/src/main/res/xml/preferences_download.xml index 0de0de2c4..1479a16e3 100644 --- a/app/src/main/res/xml/preferences_download.xml +++ b/app/src/main/res/xml/preferences_download.xml @@ -47,14 +47,4 @@ app:key="PREFERENCE_DOWNLOAD_WIFI_ONLY" app:title="@string/pref_downloader_wifi_title" /> -