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 FLOAT_EXTRA = "FLOAT_EXTRA"
const val STRING_APP = "STRING_APP" const val STRING_APP = "STRING_APP"
const val STRING_VERSION = "STRING_VERSION" const val STRING_VERSION = "STRING_VERSION"
const val PARCEL_DOWNLOAD = "PARCEL_DOWNLOAD"
const val STRING_EXTRA = "STRING_EXTRA" const val STRING_EXTRA = "STRING_EXTRA"
const val BROWSE_EXTRA = "BROWSE_EXTRA" const val BROWSE_EXTRA = "BROWSE_EXTRA"

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,9 @@ import androidx.core.content.FileProvider
import com.aurora.store.AuroraApplication import com.aurora.store.AuroraApplication
import com.aurora.store.BuildConfig import com.aurora.store.BuildConfig
import com.aurora.store.data.event.InstallerEvent 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.Log
import com.aurora.store.util.PathUtil
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import java.io.File import java.io.File
@@ -55,6 +57,15 @@ abstract class InstallerBase(protected var context: Context) : IInstaller {
EventBus.getDefault().post(event) 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 { open fun getUri(file: File): Uri {
return FileProvider.getUriForFile( return FileProvider.getUriForFile(
context, context,

View File

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

View File

@@ -22,6 +22,7 @@ package com.aurora.store.data.installer
import android.content.Context import android.content.Context
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.event.InstallerEvent 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.Log
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
@@ -30,25 +31,15 @@ import java.util.regex.Pattern
class RootInstaller(context: Context) : InstallerBase(context) { class RootInstaller(context: Context) : InstallerBase(context) {
override fun install(packageName: String, files: List<Any>) { override fun install(download: Download) {
if (isAlreadyQueued(packageName)) { if (isAlreadyQueued(download.packageName)) {
Log.i("$packageName already queued") Log.i("${download.packageName} already queued")
} else { } else {
if (Shell.getShell().isRoot) { if (Shell.getShell().isRoot) {
files.map { xInstall(download.packageName, getFiles(download.packageName, download.versionCode))
when (it) {
is File -> it
is String -> File(it)
else -> {
throw Exception("Invalid data, expecting listOf() File or String")
}
}
}.let {
xInstall(packageName, it)
}
} else { } else {
postError( postError(
packageName, download.packageName,
context.getString(R.string.installer_status_failure), context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_root_unavailable) 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.R
import com.aurora.store.data.event.BusEvent import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.event.InstallerEvent 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.Log
import com.aurora.store.util.PackageUtil import com.aurora.store.util.PackageUtil
import org.greenrobot.eventbus.EventBus 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" const val PRIVILEGED_EXTENSION_SERVICE_INTENT = "com.aurora.services.IPrivilegedService"
} }
override fun install(packageName: String, files: List<Any>) { override fun install(download: Download) {
when { when {
isAlreadyQueued(packageName) -> { isAlreadyQueued(download.packageName) -> {
Log.i("$packageName already queued") Log.i("${download.packageName} already queued")
} }
PackageUtil.isInstalled(context, PRIVILEGED_EXTENSION_PACKAGE_NAME) -> { PackageUtil.isInstalled(context, PRIVILEGED_EXTENSION_PACKAGE_NAME) -> {
Log.i("Received service install request for $packageName") Log.i("Received service install request for ${download.packageName}")
val uriList = files.map { val fileList = getFiles(download.packageName, download.versionCode)
when (it) { xInstall(
is File -> getUri(it) download.packageName,
is String -> getUri(File(it)) fileList.map { getUri(it) },
else -> { fileList.map { it.absolutePath }
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)
} }
else -> { else -> {
postError( postError(
packageName, download.packageName,
context.getString(R.string.installer_status_failure), context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_service_unavailable) 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.isSAndAbove
import com.aurora.extensions.isTAndAbove import com.aurora.extensions.isTAndAbove
import com.aurora.extensions.isUAndAbove import com.aurora.extensions.isUAndAbove
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.Log import com.aurora.store.util.Log
import kotlin.properties.Delegates import kotlin.properties.Delegates
@@ -37,15 +38,15 @@ class SessionInstaller(context: Context) : SessionInstallerBase(context) {
var sessionId by Delegates.notNull<Int>() var sessionId by Delegates.notNull<Int>()
override fun install(packageName: String, files: List<Any>) { override fun install(download: Download) {
if (isAlreadyQueued(packageName)) { if (isAlreadyQueued(download.packageName)) {
Log.i("$packageName already queued") Log.i("${download.packageName} already queued")
} else { } else {
Log.i("Received session install request for $packageName") Log.i("Received session install request for ${download.packageName}")
val packageInstaller = context.packageManager.packageInstaller val packageInstaller = context.packageManager.packageInstaller
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL).apply { val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
setAppPackageName(packageName) setAppPackageName(download.packageName)
if (isOAndAbove()) { if (isOAndAbove()) {
setInstallReason(PackageManager.INSTALL_REASON_USER) setInstallReason(PackageManager.INSTALL_REASON_USER)
} }
@@ -66,7 +67,12 @@ class SessionInstaller(context: Context) : SessionInstallerBase(context) {
sessionId = packageInstaller.createSession(sessionParams) sessionId = packageInstaller.createSession(sessionParams)
val session = packageInstaller.openSession(sessionId) 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.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller
import android.net.Uri
import androidx.core.content.FileProvider
import com.aurora.extensions.isSAndAbove import com.aurora.extensions.isSAndAbove
import com.aurora.store.BuildConfig
import com.aurora.store.data.receiver.InstallerStatusReceiver import com.aurora.store.data.receiver.InstallerStatusReceiver
import com.aurora.store.util.Log import com.aurora.store.util.Log
import java.io.File import java.io.File
@@ -38,26 +35,17 @@ abstract class SessionInstallerBase(context: Context) : InstallerBase(context) {
sessionId: Int, sessionId: Int,
session: PackageInstaller.Session, session: PackageInstaller.Session,
packageName: String, 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 { try {
Log.i("Writing splits to session for $packageName") Log.i("Writing splits to session for $packageName")
for (uri in uriList) { files.forEach {
context.contentResolver.openInputStream(uri)?.use { input -> it.inputStream().use { input ->
session.openWrite("${packageName}_${System.currentTimeMillis()}", 0, -1).use { session.openWrite("${packageName}_${System.currentTimeMillis()}", 0, -1).use { output ->
input.copyTo(it) input.copyTo(output)
session.fsync(it) 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.isOAndAbove
import com.aurora.extensions.isSAndAbove import com.aurora.extensions.isSAndAbove
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.Log import com.aurora.store.util.Log
import dev.rikka.tools.refine.Refine import dev.rikka.tools.refine.Refine
import rikka.shizuku.ShizukuBinderWrapper import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.SystemServiceHelper import rikka.shizuku.SystemServiceHelper
class ShizukuInstaller(context: Context) : SessionInstallerBase(context) { class ShizukuInstaller(context: Context) : SessionInstallerBase(context) {
companion object { companion object {
@@ -66,11 +66,11 @@ class ShizukuInstaller(context: Context) : SessionInstallerBase(context) {
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
override fun install(packageName: String, files: List<Any>) { override fun install(download: Download) {
if (isAlreadyQueued(packageName)) { if (isAlreadyQueued(download.packageName)) {
Log.i("$packageName already queued") Log.i("${download.packageName} already queued")
} else { } else {
Log.i("Received session install request for $packageName") Log.i("Received session install request for ${download.packageName}")
val (sessionId, session) = kotlin.runCatching { val (sessionId, session) = kotlin.runCatching {
val params = SessionParams(SessionParams.MODE_FULL_INSTALL) val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
@@ -94,14 +94,19 @@ class ShizukuInstaller(context: Context) : SessionInstallerBase(context) {
}.getOrElse { ex -> }.getOrElse { ex ->
ex.printStackTrace() ex.printStackTrace()
postError( postError(
packageName, download.packageName,
context.getString(R.string.installer_status_failure), context.getString(R.string.installer_status_failure),
context.getString(R.string.installer_shizuku_unavailable) context.getString(R.string.installer_shizuku_unavailable)
) )
return 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() { private suspend fun onSuccess() {
withContext(NonCancellable) { withContext(NonCancellable) {
try { try {
val downloadDir = PathUtil.getAppDownloadDir( appInstaller.getPreferredInstaller().install(download)
appContext, download.packageName, download.versionCode
)
appInstaller.getPreferredInstaller().install(
download.packageName,
downloadDir.listFiles()!!.filter { it.path.endsWith(".apk") }
)
} catch (exception: Exception) { } catch (exception: Exception) {
Log.e(TAG, "Failed to install ${download.packageName}", 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.R
import com.aurora.store.data.activity.InstallActivity import com.aurora.store.data.activity.InstallActivity
import com.aurora.store.data.model.DownloadStatus import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.room.download.Download
import java.net.URL import java.net.URL
import java.util.UUID import java.util.UUID
@@ -121,7 +122,7 @@ object NotificationUtil {
NotificationCompat.Action.Builder( NotificationCompat.Action.Builder(
R.drawable.ic_install, R.drawable.ic_install,
context.getString(R.string.action_install), context.getString(R.string.action_install),
getInstallIntent(context, download.packageName, download.versionCode) getInstallIntent(context, download)
).build() ).build()
) )
} }
@@ -207,14 +208,9 @@ object NotificationUtil {
.createPendingIntent() .createPendingIntent()
} }
private fun getInstallIntent( private fun getInstallIntent(context: Context, download: Download): PendingIntent {
context: Context,
packageName: String,
version: Int
): PendingIntent {
val intent = Intent(context, InstallActivity::class.java).apply { val intent = Intent(context, InstallActivity::class.java).apply {
putExtra(Constants.STRING_APP, packageName) putExtra(Constants.PARCEL_DOWNLOAD, download)
putExtra(Constants.STRING_VERSION, version)
} }
val flags = if (isMAndAbove()) { val flags = if (isMAndAbove()) {
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
@@ -222,7 +218,7 @@ object NotificationUtil {
PendingIntent.FLAG_CANCEL_CURRENT PendingIntent.FLAG_CANCEL_CURRENT
} }
return PendingIntent.getActivity( 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.data.model.DownloadStatus
import com.aurora.store.databinding.SheetDownloadMenuBinding import com.aurora.store.databinding.SheetDownloadMenuBinding
import com.aurora.store.util.DownloadWorkerUtil import com.aurora.store.util.DownloadWorkerUtil
import com.aurora.store.util.PathUtil
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
@@ -111,15 +110,7 @@ class DownloadMenuSheet : BaseBottomSheet() {
private fun install() { private fun install() {
try { try {
val downloadDir = PathUtil.getAppDownloadDir( appInstaller.getPreferredInstaller().install(args.download)
requireContext(),
args.download.packageName,
args.download.versionCode
)
appInstaller.getPreferredInstaller().install(
args.download.packageName,
downloadDir.listFiles()!!.filter { it.path.endsWith(".apk") }
)
} catch (exception: Exception) { } catch (exception: Exception) {
Log.e(TAG, "Failed to install ${args.download.packageName}", exception) Log.e(TAG, "Failed to install ${args.download.packageName}", exception)
if (exception is NullPointerException) { if (exception is NullPointerException) {