Switch to an expedited worker to export apps & downloads
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
145
app/src/main/java/com/aurora/store/data/work/ExportWorker.kt
Normal file
145
app/src/main/java/com/aurora/store/data/work/ExportWorker.kt
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package com.aurora.store.data.work
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.hilt.work.HiltWorker
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.Data
|
||||||
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.OutOfQuotaPolicy
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.aurora.store.util.PackageUtil.getPackageInfo
|
||||||
|
import com.aurora.store.util.PathUtil
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import java.io.File
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An expedited worker to export downloaded or installed apps.
|
||||||
|
*/
|
||||||
|
@HiltWorker
|
||||||
|
class ExportWorker @AssistedInject constructor(
|
||||||
|
@Assisted private val appContext: Context,
|
||||||
|
@Assisted workerParams: WorkerParameters
|
||||||
|
) : CoroutineWorker(appContext, workerParams) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ExportWorker"
|
||||||
|
|
||||||
|
private const val URI = "URI"
|
||||||
|
private const val IS_DOWNLOAD = "IS_DOWNLOAD"
|
||||||
|
private const val PACKAGE_NAME = "PACKAGE_NAME"
|
||||||
|
private const val VERSION_CODE = "VERSION_CODE"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the installed package to the given URI
|
||||||
|
* @param packageName packageName of the app to export
|
||||||
|
* @see [ExportWorker]
|
||||||
|
*/
|
||||||
|
fun exportInstalledApp(context: Context, packageName: String, uri: Uri) {
|
||||||
|
val inputData = Data.Builder()
|
||||||
|
.putBoolean(IS_DOWNLOAD, false)
|
||||||
|
.putString(URI, uri.toString())
|
||||||
|
.putString(PACKAGE_NAME, packageName)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val oneTimeWorkRequest = OneTimeWorkRequestBuilder<ExportWorker>()
|
||||||
|
.setInputData(inputData)
|
||||||
|
.setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
Log.i(TAG, "Exporting $packageName")
|
||||||
|
WorkManager.getInstance(context).enqueue(oneTimeWorkRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the given download to the URI
|
||||||
|
* @param packageName Name of the package to export
|
||||||
|
* @param versionCode version of the package
|
||||||
|
* @see [ExportWorker]
|
||||||
|
*/
|
||||||
|
fun exportDownloadedApp(context: Context, packageName: String, versionCode: Int, uri: Uri) {
|
||||||
|
val inputData = Data.Builder()
|
||||||
|
.putBoolean(IS_DOWNLOAD, true)
|
||||||
|
.putString(URI, uri.toString())
|
||||||
|
.putString(PACKAGE_NAME, packageName)
|
||||||
|
.putInt(VERSION_CODE, versionCode)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val oneTimeWorkRequest = OneTimeWorkRequestBuilder<ExportWorker>()
|
||||||
|
.setInputData(inputData)
|
||||||
|
.setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
Log.i(TAG, "Exporting download for ${packageName}/${versionCode}")
|
||||||
|
WorkManager.getInstance(context).enqueue(oneTimeWorkRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
val isDownload = inputData.getBoolean(IS_DOWNLOAD, false)
|
||||||
|
val uri = Uri.parse(inputData.getString(URI))
|
||||||
|
val packageName = inputData.getString(PACKAGE_NAME)
|
||||||
|
val versionCode = inputData.getInt(VERSION_CODE, -1)
|
||||||
|
|
||||||
|
if (packageName.isNullOrEmpty() || isDownload && versionCode == -1) {
|
||||||
|
Log.e(TAG, "Input data is corrupt, bailing out!")
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isDownload) {
|
||||||
|
copyDownloadedApp(packageName, versionCode, uri)
|
||||||
|
} else {
|
||||||
|
copyInstalledApp(packageName, uri)
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Log.e(TAG, "Failed to export $packageName", exception)
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyInstalledApp(packageName: String, uri: Uri) {
|
||||||
|
val packageInfo = getPackageInfo(appContext, packageName, PackageManager.GET_META_DATA)
|
||||||
|
val fileList: MutableList<File?> = mutableListOf()
|
||||||
|
|
||||||
|
fileList.add(File(packageInfo.applicationInfo.sourceDir))
|
||||||
|
packageInfo.applicationInfo.splitSourceDirs?.let { splits ->
|
||||||
|
fileList.addAll(splits.map { File(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
bundleAllAPKs(fileList.filterNotNull(), uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyDownloadedApp(packageName: String, versionCode: Int, uri: Uri) {
|
||||||
|
return bundleAllAPKs(
|
||||||
|
PathUtil.getAppDownloadDir(appContext, packageName, versionCode).listFiles()!!.toList(),
|
||||||
|
uri
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundles all the given APKs to a zip file
|
||||||
|
* @param fileList List of APKs to add to the zip
|
||||||
|
* @param uri [Uri] of the file to write the APKs
|
||||||
|
*/
|
||||||
|
private fun bundleAllAPKs(fileList: List<File>, uri: Uri) {
|
||||||
|
ZipOutputStream(appContext.contentResolver.openOutputStream(uri)).use { zipOutput ->
|
||||||
|
fileList.forEach { file ->
|
||||||
|
file.inputStream().use { input ->
|
||||||
|
val zipEntry = ZipEntry(file.name)
|
||||||
|
zipOutput.putNextEntry(zipEntry)
|
||||||
|
input.copyTo(zipOutput)
|
||||||
|
zipOutput.closeEntry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* Aurora Store
|
|
||||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
|
||||||
*
|
|
||||||
* Aurora Store is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Aurora Store is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.aurora.store.util
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageInfo
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.net.Uri
|
|
||||||
import android.util.Log
|
|
||||||
import com.aurora.store.util.PackageUtil.getPackageInfo
|
|
||||||
import java.io.File
|
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipOutputStream
|
|
||||||
|
|
||||||
object ApkCopier {
|
|
||||||
|
|
||||||
private const val TAG = "ApkCopier"
|
|
||||||
|
|
||||||
fun copyInstalledApp(context: Context, packageName: String, uri: Uri) {
|
|
||||||
val packageInfo = getPackageInfo(context, packageName, PackageManager.GET_META_DATA)
|
|
||||||
val baseApk = getBaseApk(packageInfo)
|
|
||||||
val fileList: MutableList<File?> = mutableListOf()
|
|
||||||
|
|
||||||
/*Add base APK*/
|
|
||||||
fileList.add(baseApk)
|
|
||||||
|
|
||||||
if (!packageInfo.applicationInfo.splitSourceDirs.isNullOrEmpty()) {
|
|
||||||
fileList.addAll(getSplitAPKs(packageInfo))
|
|
||||||
}
|
|
||||||
bundleAllAPKs(context, fileList.filterNotNull(), uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copyDownloadedApp(context: Context, packageName: String, versionCode: Int, uri: Uri) {
|
|
||||||
return bundleAllAPKs(
|
|
||||||
context,
|
|
||||||
PathUtil.getAppDownloadDir(context, packageName, versionCode).listFiles()!!.toList(),
|
|
||||||
uri
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getBaseApk(packageInfo: PackageInfo?): File? {
|
|
||||||
return if (packageInfo?.applicationInfo != null) {
|
|
||||||
File(packageInfo.applicationInfo.sourceDir)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSplitAPKs(packageInfo: PackageInfo): MutableList<File> {
|
|
||||||
val fileList: MutableList<File> = ArrayList()
|
|
||||||
val splitSourceDirs = packageInfo.applicationInfo.splitSourceDirs
|
|
||||||
if (splitSourceDirs != null) {
|
|
||||||
for (fileName in splitSourceDirs) fileList.add(File(fileName))
|
|
||||||
}
|
|
||||||
return fileList
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bundleAllAPKs(context: Context, fileList: List<File>, uri: Uri) {
|
|
||||||
try {
|
|
||||||
val zipOutputStream = ZipOutputStream(context.contentResolver.openOutputStream(uri))
|
|
||||||
|
|
||||||
fileList.forEach { file ->
|
|
||||||
file.inputStream().use { output ->
|
|
||||||
val zipEntry = ZipEntry(file.name)
|
|
||||||
zipOutputStream.putNextEntry(zipEntry)
|
|
||||||
output.copyTo(zipOutputStream)
|
|
||||||
zipOutputStream.closeEntry()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zipOutputStream.close()
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
Log.e(TAG, "Failed to copy app bundles", exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -104,7 +104,7 @@ class ManualDownloadSheet : BottomSheetDialogFragment(R.layout.sheet_manual_down
|
|||||||
if (customVersionString.isEmpty())
|
if (customVersionString.isEmpty())
|
||||||
binding.versionCodeInp.error = "Enter version code"
|
binding.versionCodeInp.error = "Enter version code"
|
||||||
else {
|
else {
|
||||||
viewModel.purchase(requireContext(), args.app, customVersionString.toInt())
|
viewModel.purchase(args.app, customVersionString.toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import com.aurora.gplayapi.data.models.App
|
|||||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||||
import com.aurora.store.data.event.BusEvent
|
import com.aurora.store.data.event.BusEvent
|
||||||
import com.aurora.store.data.providers.AuthProvider
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
import com.aurora.store.util.ApkCopier
|
import com.aurora.store.data.work.ExportWorker
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -28,7 +28,7 @@ class SheetsViewModel @Inject constructor(
|
|||||||
private val _purchaseStatus = MutableSharedFlow<Boolean>()
|
private val _purchaseStatus = MutableSharedFlow<Boolean>()
|
||||||
val purchaseStatus = _purchaseStatus.asSharedFlow()
|
val purchaseStatus = _purchaseStatus.asSharedFlow()
|
||||||
|
|
||||||
fun purchase(context: Context, app: App, customVersion: Int) {
|
fun purchase(app: App, customVersion: Int) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val purchaseHelper = PurchaseHelper(authProvider.authData)
|
val purchaseHelper = PurchaseHelper(authProvider.authData)
|
||||||
@@ -46,18 +46,10 @@ class SheetsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun copyInstalledApp(context: Context, packageName: String, uri: Uri) {
|
fun copyInstalledApp(context: Context, packageName: String, uri: Uri) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
ExportWorker.exportInstalledApp(context, packageName, uri)
|
||||||
ApkCopier.copyInstalledApp(context, packageName, uri)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copyDownloadedApp(context: Context, packageName: String, versionCode: Int, uri: Uri) {
|
fun copyDownloadedApp(context: Context, packageName: String, versionCode: Int, uri: Uri) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
ExportWorker.exportDownloadedApp(context, packageName, versionCode, uri)
|
||||||
try {
|
|
||||||
ApkCopier.copyDownloadedApp(context, packageName, versionCode, uri)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
Log.e(TAG, "Failed to copy downloads", exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user