AppMenuSheet: Use ACTION_CREATE_DOCUMENT to export APK
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user