IInstaller: Use Download object for installing apps

Contains all required data for us to install an app making things easier
to work with.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-01-24 20:19:28 +05:30
parent 1ebe86a22b
commit c562f4351e
14 changed files with 93 additions and 151 deletions

View File

@@ -24,6 +24,7 @@ object Constants {
const val FLOAT_EXTRA = "FLOAT_EXTRA"
const val STRING_APP = "STRING_APP"
const val STRING_VERSION = "STRING_VERSION"
const val PARCEL_DOWNLOAD = "PARCEL_DOWNLOAD"
const val STRING_EXTRA = "STRING_EXTRA"
const val BROWSE_EXTRA = "BROWSE_EXTRA"

View File

@@ -4,9 +4,10 @@ import android.content.pm.PackageInstaller
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.IntentCompat
import com.aurora.Constants
import com.aurora.store.data.installer.SessionInstaller
import com.aurora.store.util.PathUtil
import com.aurora.store.data.room.download.Download
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@@ -19,9 +20,10 @@ class InstallActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val packageName = intent.extras?.getString(Constants.STRING_APP) ?: String()
val versionCode = intent.extras?.getInt(Constants.STRING_VERSION)
if (packageName.isNotBlank() && versionCode != null) {
val download =
IntentCompat.getParcelableExtra(intent, Constants.PARCEL_DOWNLOAD, Download::class.java)
if (download != null) {
val callback = object : PackageInstaller.SessionCallback() {
override fun onCreated(sessionId: Int) {}
@@ -39,15 +41,14 @@ class InstallActivity : AppCompatActivity() {
}
}
packageManager.packageInstaller.registerSessionCallback(callback)
install(packageName, versionCode)
install(download)
}
}
private fun install(packageName: String, versionCode: Int) {
private fun install(download: Download) {
sessionInstaller = SessionInstaller(this)
try {
val files = PathUtil.getAppDownloadDir(this, packageName, versionCode).listFiles()
sessionInstaller.install(packageName, files!!.filter { it.path.endsWith(".apk") })
sessionInstaller.install(download)
} catch (exception: Exception) {
Log.e(TAG, "Failed to install $packageName")
finish()

View File

@@ -3,6 +3,7 @@ package com.aurora.store.data.installer
import android.content.Context
import android.content.Intent
import androidx.core.content.FileProvider
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.Log
import java.io.*
import java.util.zip.ZipEntry
@@ -14,20 +15,13 @@ class AMInstaller(context: Context) : InstallerBase(context) {
const val AM_DEBUG_PACKAGE_NAME = "io.github.muntashirakon.AppManager.debug"
}
override fun install(packageName: String, files: List<Any>) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
override fun install(download: Download) {
if (isAlreadyQueued(download.packageName)) {
Log.i("${download.packageName} already queued")
} else {
Log.i("Received AM install request for $packageName")
val fileList = files.map {
when (it) {
is File -> it.absolutePath
is String -> File(it).absolutePath
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}
Log.i("Received AM install request for ${download.packageName}")
val fileList =
getFiles(download.packageName, download.versionCode).map { it.absolutePath }
when {
fileList.size == 1 -> {
xInstall(File(fileList.first()))
@@ -44,7 +38,7 @@ class AMInstaller(context: Context) : InstallerBase(context) {
}
private fun xInstall(file: File) {
val intent: Intent = Intent(Intent.ACTION_VIEW)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(
FileProvider.getUriForFile(
context,

View File

@@ -19,8 +19,10 @@
package com.aurora.store.data.installer
import com.aurora.store.data.room.download.Download
interface IInstaller {
fun install(packageName: String, files: List<Any>)
fun install(download: Download)
fun clearQueue()
fun isAlreadyQueued(packageName: String): Boolean
fun removeFromInstallQueue(packageName: String)

View File

@@ -25,7 +25,9 @@ import androidx.core.content.FileProvider
import com.aurora.store.AuroraApplication
import com.aurora.store.BuildConfig
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.Log
import com.aurora.store.util.PathUtil
import org.greenrobot.eventbus.EventBus
import java.io.File
@@ -55,6 +57,15 @@ abstract class InstallerBase(protected var context: Context) : IInstaller {
EventBus.getDefault().post(event)
}
open fun getFiles(packageName: String, versionCode: Int, sharedLibPackageName: String = ""): List<File> {
val downloadDir = if (sharedLibPackageName.isNotBlank()) {
PathUtil.getLibDownloadDir(context, packageName, versionCode, sharedLibPackageName)
} else {
PathUtil.getAppDownloadDir(context, packageName, versionCode)
}
return downloadDir.listFiles()!!.filter { it.path.endsWith(".apk") }
}
open fun getUri(file: File): Uri {
return FileProvider.getUriForFile(
context,

View File

@@ -23,28 +23,19 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.Log
import java.io.File
@Deprecated("Deprecated in favour of SessionInstaller")
class NativeInstaller(context: Context) : InstallerBase(context) {
override fun install(packageName: String, files: List<Any>) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
override fun install(download: Download) {
if (isAlreadyQueued(download.packageName)) {
Log.i("${download.packageName} already queued")
} else {
Log.i("Received native install request for $packageName")
files.map {
when (it) {
is File -> it
is String -> File(it)
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}.forEach {
xInstall(it)
}
Log.i("Received native install request for ${download.packageName}")
getFiles(download.packageName, download.versionCode).forEach { xInstall(it) }
}
}

View File

@@ -22,6 +22,7 @@ package com.aurora.store.data.installer
import android.content.Context
import com.aurora.store.R
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.Log
import com.topjohnwu.superuser.Shell
import org.greenrobot.eventbus.EventBus
@@ -30,25 +31,15 @@ import java.util.regex.Pattern
class RootInstaller(context: Context) : InstallerBase(context) {
override fun install(packageName: String, files: List<Any>) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
override fun install(download: Download) {
if (isAlreadyQueued(download.packageName)) {
Log.i("${download.packageName} already queued")
} else {
if (Shell.getShell().isRoot) {
files.map {
when (it) {
is File -> it
is String -> File(it)
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}.let {
xInstall(packageName, it)
}
xInstall(download.packageName, getFiles(download.packageName, download.versionCode))
} else {
postError(
packageName,
download.packageName,
context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_root_unavailable)
)

View File

@@ -37,6 +37,7 @@ import com.aurora.store.BuildConfig
import com.aurora.store.R
import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.Log
import com.aurora.store.util.PackageUtil
import org.greenrobot.eventbus.EventBus
@@ -58,39 +59,25 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
const val PRIVILEGED_EXTENSION_SERVICE_INTENT = "com.aurora.services.IPrivilegedService"
}
override fun install(packageName: String, files: List<Any>) {
override fun install(download: Download) {
when {
isAlreadyQueued(packageName) -> {
Log.i("$packageName already queued")
isAlreadyQueued(download.packageName) -> {
Log.i("${download.packageName} already queued")
}
PackageUtil.isInstalled(context, PRIVILEGED_EXTENSION_PACKAGE_NAME) -> {
Log.i("Received service install request for $packageName")
val uriList = files.map {
when (it) {
is File -> getUri(it)
is String -> getUri(File(it))
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}
val fileList = files.map {
when (it) {
is File -> it.absolutePath
is String -> File(it).absolutePath
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}
xInstall(packageName, uriList, fileList)
Log.i("Received service install request for ${download.packageName}")
val fileList = getFiles(download.packageName, download.versionCode)
xInstall(
download.packageName,
fileList.map { getUri(it) },
fileList.map { it.absolutePath }
)
}
else -> {
postError(
packageName,
download.packageName,
context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_service_unavailable)
)

View File

@@ -29,6 +29,7 @@ import com.aurora.extensions.isOAndAbove
import com.aurora.extensions.isSAndAbove
import com.aurora.extensions.isTAndAbove
import com.aurora.extensions.isUAndAbove
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.Log
import kotlin.properties.Delegates
@@ -37,15 +38,15 @@ class SessionInstaller(context: Context) : SessionInstallerBase(context) {
var sessionId by Delegates.notNull<Int>()
override fun install(packageName: String, files: List<Any>) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
override fun install(download: Download) {
if (isAlreadyQueued(download.packageName)) {
Log.i("${download.packageName} already queued")
} else {
Log.i("Received session install request for $packageName")
Log.i("Received session install request for ${download.packageName}")
val packageInstaller = context.packageManager.packageInstaller
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
setAppPackageName(packageName)
setAppPackageName(download.packageName)
if (isOAndAbove()) {
setInstallReason(PackageManager.INSTALL_REASON_USER)
}
@@ -66,7 +67,12 @@ class SessionInstaller(context: Context) : SessionInstallerBase(context) {
sessionId = packageInstaller.createSession(sessionParams)
val session = packageInstaller.openSession(sessionId)
xInstall(sessionId, session, packageName, files)
xInstall(
sessionId,
session,
download.packageName,
getFiles(download.packageName, download.versionCode)
)
}
}
}

View File

@@ -24,10 +24,7 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.net.Uri
import androidx.core.content.FileProvider
import com.aurora.extensions.isSAndAbove
import com.aurora.store.BuildConfig
import com.aurora.store.data.receiver.InstallerStatusReceiver
import com.aurora.store.util.Log
import java.io.File
@@ -38,26 +35,17 @@ abstract class SessionInstallerBase(context: Context) : InstallerBase(context) {
sessionId: Int,
session: PackageInstaller.Session,
packageName: String,
files: List<Any>
files: List<File>
) {
val uriList = files.map {
when (it) {
is File -> getUri(it)
is String -> getUri(File(it))
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}
try {
Log.i("Writing splits to session for $packageName")
for (uri in uriList) {
context.contentResolver.openInputStream(uri)?.use { input ->
session.openWrite("${packageName}_${System.currentTimeMillis()}", 0, -1).use {
input.copyTo(it)
session.fsync(it)
files.forEach {
it.inputStream().use { input ->
session.openWrite("${packageName}_${System.currentTimeMillis()}", 0, -1).use { output ->
input.copyTo(output)
session.fsync(output)
}
}
}
@@ -93,20 +81,4 @@ abstract class SessionInstallerBase(context: Context) : InstallerBase(context) {
)
}
}
override fun getUri(file: File): Uri {
val uri = FileProvider.getUriForFile(
context,
"${BuildConfig.APPLICATION_ID}.fileProvider",
file
)
context.grantUriPermission(
BuildConfig.APPLICATION_ID,
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
return uri
}
}

View File

@@ -29,12 +29,12 @@ import androidx.annotation.RequiresApi
import com.aurora.extensions.isOAndAbove
import com.aurora.extensions.isSAndAbove
import com.aurora.store.R
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.Log
import dev.rikka.tools.refine.Refine
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.SystemServiceHelper
class ShizukuInstaller(context: Context) : SessionInstallerBase(context) {
companion object {
@@ -66,11 +66,11 @@ class ShizukuInstaller(context: Context) : SessionInstallerBase(context) {
}
@RequiresApi(Build.VERSION_CODES.O)
override fun install(packageName: String, files: List<Any>) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
override fun install(download: Download) {
if (isAlreadyQueued(download.packageName)) {
Log.i("${download.packageName} already queued")
} else {
Log.i("Received session install request for $packageName")
Log.i("Received session install request for ${download.packageName}")
val (sessionId, session) = kotlin.runCatching {
val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
@@ -94,14 +94,19 @@ class ShizukuInstaller(context: Context) : SessionInstallerBase(context) {
}.getOrElse { ex ->
ex.printStackTrace()
postError(
packageName,
download.packageName,
context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_shizuku_unavailable)
)
return
}
xInstall(sessionId, session, packageName, files)
xInstall(
sessionId,
session,
download.packageName,
getFiles(download.packageName, download.versionCode)
)
}
}
}

View File

@@ -161,13 +161,7 @@ class DownloadWorker @AssistedInject constructor(
private suspend fun onSuccess() {
withContext(NonCancellable) {
try {
val downloadDir = PathUtil.getAppDownloadDir(
appContext, download.packageName, download.versionCode
)
appInstaller.getPreferredInstaller().install(
download.packageName,
downloadDir.listFiles()!!.filter { it.path.endsWith(".apk") }
)
appInstaller.getPreferredInstaller().install(download)
} catch (exception: Exception) {
Log.e(TAG, "Failed to install ${download.packageName}", exception)
}

View File

@@ -26,6 +26,7 @@ import com.aurora.store.data.room.download.Download as AuroraDownload
import com.aurora.store.R
import com.aurora.store.data.activity.InstallActivity
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.room.download.Download
import java.net.URL
import java.util.UUID
@@ -121,7 +122,7 @@ object NotificationUtil {
NotificationCompat.Action.Builder(
R.drawable.ic_install,
context.getString(R.string.action_install),
getInstallIntent(context, download.packageName, download.versionCode)
getInstallIntent(context, download)
).build()
)
}
@@ -207,14 +208,9 @@ object NotificationUtil {
.createPendingIntent()
}
private fun getInstallIntent(
context: Context,
packageName: String,
version: Int
): PendingIntent {
private fun getInstallIntent(context: Context, download: Download): PendingIntent {
val intent = Intent(context, InstallActivity::class.java).apply {
putExtra(Constants.STRING_APP, packageName)
putExtra(Constants.STRING_VERSION, version)
putExtra(Constants.PARCEL_DOWNLOAD, download)
}
val flags = if (isMAndAbove()) {
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
@@ -222,7 +218,7 @@ object NotificationUtil {
PendingIntent.FLAG_CANCEL_CURRENT
}
return PendingIntent.getActivity(
context, packageName.hashCode(), intent, flags
context, download.packageName.hashCode(), intent, flags
)
}
}

View File

@@ -34,7 +34,6 @@ import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.databinding.SheetDownloadMenuBinding
import com.aurora.store.util.DownloadWorkerUtil
import com.aurora.store.util.PathUtil
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.NonCancellable
@@ -111,15 +110,7 @@ class DownloadMenuSheet : BaseBottomSheet() {
private fun install() {
try {
val downloadDir = PathUtil.getAppDownloadDir(
requireContext(),
args.download.packageName,
args.download.versionCode
)
appInstaller.getPreferredInstaller().install(
args.download.packageName,
downloadDir.listFiles()!!.filter { it.path.endsWith(".apk") }
)
appInstaller.getPreferredInstaller().install(args.download)
} catch (exception: Exception) {
Log.e(TAG, "Failed to install ${args.download.packageName}", exception)
if (exception is NullPointerException) {