AppMenuSheet: Use ACTION_CREATE_DOCUMENT to export APK

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-02-01 11:58:57 +01:00
parent aa95542b0d
commit 4297bac4fe
5 changed files with 29 additions and 72 deletions

View File

@@ -22,40 +22,29 @@ package com.aurora.store.util
import android.content.Context import android.content.Context
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri
import android.util.Log
import com.aurora.store.util.PackageUtil.getPackageInfo import com.aurora.store.util.PackageUtil.getPackageInfo
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
class ApkCopier(private val context: Context, private val packageName: String) { object ApkCopier {
fun copy() { private const val TAG = "ApkCopier"
val destination = File(PathUtil.getBaseCopyDirectory(context))
destination.let {
if (it.exists()) {
Log.i("Base copy directory is available")
} else {
it.mkdirs()
Log.e("Base copy directory is created : ${it.path}")
}
}
fun copy(context: Context, packageName: String, uri: Uri) {
val packageInfo = getPackageInfo(context, packageName, PackageManager.GET_META_DATA) val packageInfo = getPackageInfo(context, packageName, PackageManager.GET_META_DATA)
val baseApk = getBaseApk(packageInfo) val baseApk = getBaseApk(packageInfo)
val fileList: MutableList<File?> = mutableListOf() val fileList: MutableList<File?> = mutableListOf()
/*Add base APK*/ /*Add base APK*/
fileList.add(baseApk) fileList.add(baseApk)
val splitSourceDirs = packageInfo.applicationInfo.splitSourceDirs if (!packageInfo.applicationInfo.splitSourceDirs.isNullOrEmpty()) {
if (splitSourceDirs != null && splitSourceDirs.isNotEmpty()) {
/*Add Split APKs*/
fileList.addAll(getSplitAPKs(packageInfo)) fileList.addAll(getSplitAPKs(packageInfo))
} }
bundleAllAPKs(context, fileList) bundleAllAPKs(context, fileList.filterNotNull(), uri)
} }
private fun getBaseApk(packageInfo: PackageInfo?): File? { private fun getBaseApk(packageInfo: PackageInfo?): File? {
@@ -75,25 +64,22 @@ class ApkCopier(private val context: Context, private val packageName: String) {
return fileList return fileList
} }
private fun bundleAllAPKs(context: Context, fileList: List<File?>) { private fun bundleAllAPKs(context: Context, fileList: List<File>, uri: Uri) {
try { try {
val fileOutputStream = val zipOutputStream = ZipOutputStream(context.contentResolver.openOutputStream(uri))
FileOutputStream(PathUtil.getBaseCopyDirectory(context) + "$packageName.zip")
val zipOutputStream = ZipOutputStream(fileOutputStream)
for (file in fileList) { fileList.forEach { file ->
file?.inputStream()?.use { file.inputStream().use { output ->
val zipEntry = ZipEntry(file.name) val zipEntry = ZipEntry(file.name)
zipOutputStream.putNextEntry(zipEntry) zipOutputStream.putNextEntry(zipEntry)
it.copyTo(zipOutputStream) output.copyTo(zipOutputStream)
zipOutputStream.closeEntry() zipOutputStream.closeEntry()
} }
} }
zipOutputStream.close() zipOutputStream.close()
} catch (e: Exception) { } catch (exception: Exception) {
e.printStackTrace() Log.e(TAG, "Failed to copy app bundles", exception)
Log.e("ApkCopier : %s", e.message)
} }
} }
} }

View File

@@ -90,10 +90,6 @@ object PathUtil {
) + "/${file.name}" ) + "/${file.name}"
} }
fun getApkDownloadFile(context: Context, packageName: String, versionCode: Int): String {
return getVersionDirectory(context, packageName, versionCode)
}
fun getExternalPath(context: Context): String { fun getExternalPath(context: Context): String {
val defaultDir = val defaultDir =
File("${Environment.getExternalStorageDirectory().absolutePath}/Aurora/Store") File("${Environment.getExternalStorageDirectory().absolutePath}/Aurora/Store")
@@ -108,10 +104,6 @@ object PathUtil {
) )
} }
fun getBaseCopyDirectory(context: Context): String {
return "${getExternalPath(context)}/Exports/"
}
private fun getObbDownloadPath(packageName: String): String { private fun getObbDownloadPath(packageName: String): String {
return Environment.getExternalStorageDirectory() return Environment.getExternalStorageDirectory()
.toString() + "/Android/obb/" + packageName .toString() + "/Android/obb/" + packageName

View File

@@ -20,21 +20,14 @@
package com.aurora.store.view.ui.sheets package com.aurora.store.view.ui.sheets
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.aurora.extensions.isRAndAbove
import com.aurora.extensions.openInfo import com.aurora.extensions.openInfo
import com.aurora.extensions.toast import com.aurora.extensions.toast
import com.aurora.store.R import com.aurora.store.R
@@ -54,14 +47,16 @@ class AppMenuSheet : BaseBottomSheet() {
private val viewModel: SheetsViewModel by viewModels() private val viewModel: SheetsViewModel by viewModels()
private val args: AppMenuSheetArgs by navArgs() private val args: AppMenuSheetArgs by navArgs()
private val exportMimeType = "application/zip"
private val startForPermissions = private val requestDocumentCreation =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { registerForActivityResult(ActivityResultContracts.CreateDocument(exportMimeType)) {
if (it) { if (it != null) {
viewModel.copyApk(requireContext(), args.app.packageName) viewModel.copyApk(requireContext(), args.app.packageName, it)
} else { } else {
toast(R.string.permissions_denied) toast(R.string.failed_apk_export)
} }
dismissAllowingStateLoss()
} }
override fun onCreateContentView( override fun onCreateContentView(
@@ -106,48 +101,30 @@ class AppMenuSheet : BaseBottomSheet() {
requireContext().toast(R.string.toast_apk_blacklisted) requireContext().toast(R.string.toast_apk_blacklisted)
} }
dismissAllowingStateLoss()
EventBus.getDefault() EventBus.getDefault()
.post(BusEvent.Blacklisted(args.app.packageName, "")) .post(BusEvent.Blacklisted(args.app.packageName, ""))
} }
R.id.action_local -> { R.id.action_local -> {
export() requestDocumentCreation.launch("${args.app.packageName}.zip")
} }
R.id.action_uninstall -> { R.id.action_uninstall -> {
AppInstaller.uninstall(requireContext(), args.app.packageName) AppInstaller.uninstall(requireContext(), args.app.packageName)
dismissAllowingStateLoss()
} }
R.id.action_info -> { R.id.action_info -> {
requireContext().openInfo(args.app.packageName) requireContext().openInfo(args.app.packageName)
dismissAllowingStateLoss()
} }
} }
dismissAllowingStateLoss()
false false
} }
} }
} }
private fun export() {
if (isRAndAbove()) {
if (!Environment.isExternalStorageManager()) {
startActivity(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION))
} else {
viewModel.copyApk(requireContext(), args.app.packageName)
}
} else {
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
viewModel.copyApk(requireContext(), args.app.packageName)
} else {
startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
}
companion object { companion object {
const val TAG = "APP_MENU_SHEET" const val TAG = "APP_MENU_SHEET"
} }

View File

@@ -1,6 +1,7 @@
package com.aurora.store.viewmodel.sheets package com.aurora.store.viewmodel.sheets
import android.content.Context import android.content.Context
import android.net.Uri
import android.util.Log import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -39,9 +40,9 @@ class SheetsViewModel : ViewModel() {
} }
} }
fun copyApk(context: Context, packageName: String) { fun copyApk(context: Context, packageName: String, uri: Uri) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
ApkCopier(context, packageName).copy() ApkCopier.copy(context, packageName, uri)
} }
} }
} }

View File

@@ -405,4 +405,5 @@
<string name="device_doze_extra">Optionally you can choose to keep the optimizations enabled, but then you won\'t be able to take benefit of background downloads and automated updates, so choice is yours.</string> <string name="device_doze_extra">Optionally you can choose to keep the optimizations enabled, but then you won\'t be able to take benefit of background downloads and automated updates, so choice is yours.</string>
<string name="pref_self_update_title">Self-updates</string> <string name="pref_self_update_title">Self-updates</string>
<string name="pref_self_update_summary">Check and notify if a new stable version of Aurora Store is available</string> <string name="pref_self_update_summary">Check and notify if a new stable version of Aurora Store is available</string>
<string name="failed_apk_export">Failed to export APKs</string>
</resources> </resources>