VancedManager/app/src/main/java/com/vanced/manager/repository/manager/PackageManager.kt

486 lines
17 KiB
Kotlin

package com.vanced.manager.repository.manager
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
import com.topjohnwu.superuser.io.SuFileOutputStream
import com.vanced.manager.installer.service.AppInstallService
import com.vanced.manager.installer.service.AppUninstallService
import com.vanced.manager.util.SuException
import com.vanced.manager.util.awaitOutputOrThrow
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import kotlin.jvm.Throws
interface PackageManager {
suspend fun getVersionCode(packageName: String): PackageManagerResult<Int>
suspend fun getVersionName(packageName: String): PackageManagerResult<String>
suspend fun getInstallationDir(packageName: String): PackageManagerResult<String>
suspend fun setInstaller(targetPackage: String, installerPackage: String): PackageManagerResult<Nothing>
suspend fun forceStop(packageName: String): PackageManagerResult<Nothing>
suspend fun installApp(apk: File): PackageManagerResult<Nothing>
suspend fun installSplitApp(apks: Array<File>): PackageManagerResult<Nothing>
suspend fun uninstallApp(packageName: String): PackageManagerResult<Nothing>
}
class NonrootPackageManager(
private val context: Context
) : PackageManager {
@SuppressLint("WrongConstant")
@Suppress("DEPRECATION")
override suspend fun getVersionCode(packageName: String): PackageManagerResult<Int> {
return try {
val packageInfo = context.packageManager.getPackageInfo(packageName, FLAG_NOTHING)
val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode.and(VERSION_IGNORE_MAJOR).toInt()
} else {
packageInfo.versionCode
}
PackageManagerResult.Success(versionCode)
} catch (e: android.content.pm.PackageManager.NameNotFoundException) {
PackageManagerResult.Error(
status = PackageManagerStatus.GET_FAILED_PACKAGE_VERSION_CODE,
message = e.stackTraceToString()
)
}
}
@SuppressLint("WrongConstant")
override suspend fun getVersionName(packageName: String): PackageManagerResult<String> {
return try {
val versionName = context.packageManager
.getPackageInfo(packageName, FLAG_NOTHING)
.versionName
PackageManagerResult.Success(versionName)
} catch (e: android.content.pm.PackageManager.NameNotFoundException) {
PackageManagerResult.Error(
status = PackageManagerStatus.GET_FAILED_PACKAGE_VERSION_NAME,
message = e.stackTraceToString()
)
}
}
@SuppressLint("WrongConstant")
override suspend fun getInstallationDir(packageName: String): PackageManagerResult<String> {
return try {
val installationDir = context.packageManager
.getPackageInfo(packageName, FLAG_NOTHING)
.applicationInfo
.sourceDir
PackageManagerResult.Success(installationDir)
} catch (e: android.content.pm.PackageManager.NameNotFoundException) {
PackageManagerResult.Error(
status = PackageManagerStatus.GET_FAILED_PACKAGE_DIR,
message = e.stackTraceToString()
)
}
}
override suspend fun setInstaller(
targetPackage: String,
installerPackage: String
): PackageManagerResult<Nothing> {
return PackageManagerResult.Error(
status = PackageManagerStatus.SET_FAILED_INSTALLER,
message = "Unsupported"
)
}
override suspend fun forceStop(packageName: String): PackageManagerResult<Nothing> {
return PackageManagerResult.Error(
status = PackageManagerStatus.APP_FAILED_FORCE_STOP,
message = "Unsupported"
)
}
override suspend fun installApp(apk: File): PackageManagerResult<Nothing> {
return createInstallationSession {
writeApkToSession(apk)
}
}
override suspend fun installSplitApp(apks: Array<File>): PackageManagerResult<Nothing> {
return createInstallationSession {
for (apk in apks) {
writeApkToSession(apk)
}
}
}
override suspend fun uninstallApp(packageName: String): PackageManagerResult<Nothing> {
val packageInstaller = context.packageManager.packageInstaller
val pendingIntent = PendingIntent.getService(
context,
0,
Intent(context, AppUninstallService::class.java),
intentFlags
).intentSender
packageInstaller.uninstall(packageName, pendingIntent)
return PackageManagerResult.Success(null)
}
private inline fun createInstallationSession(
block: PackageInstaller.Session.() -> Unit
): PackageManagerResult<Nothing> {
val packageInstaller = context.packageManager.packageInstaller
val sessionParams = PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setInstallReason(android.content.pm.PackageManager.INSTALL_REASON_USER)
}
}
val pendingIntent = PendingIntent.getService(
context,
0,
Intent(context, AppInstallService::class.java),
intentFlags
).intentSender
val sessionId: Int
try {
sessionId = packageInstaller.createSession(sessionParams)
} catch (e: IOException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SESSION_FAILED_CREATE,
message = e.stackTraceToString()
)
}
val session: PackageInstaller.Session
try {
session = packageInstaller.openSession(sessionId)
} catch (e: IOException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SESSION_FAILED_OPEN,
message = e.stackTraceToString()
)
} catch (e: SecurityException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SESSION_FAILED_OPEN,
message = e.stackTraceToString()
)
}
try {
session.use {
it.block()
it.commit(pendingIntent)
}
} catch (e: IOException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SESSION_FAILED_WRITE,
message = e.stackTraceToString()
)
} catch (e: SecurityException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SESSION_FAILED_COMMIT,
message = e.stackTraceToString()
)
}
return PackageManagerResult.Success(null)
}
private fun PackageInstaller.Session.writeApkToSession(apk: File) {
apk.inputStream().use { inputStream ->
openWrite(apk.name, 0, apk.length()).use { outputStream ->
inputStream.copyTo(outputStream, byteArraySize)
fsync(outputStream)
}
}
}
private val intentFlags: Int
get() {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_MUTABLE
else
0
}
private companion object {
const val byteArraySize = 1024 * 1024
const val FLAG_NOTHING = 0
const val VERSION_IGNORE_MAJOR = 0xFFFFFFFF
}
}
class RootPackageManager : PackageManager {
override suspend fun getVersionCode(packageName: String): PackageManagerResult<Int> {
return try {
val keyword = "versionCode="
val dumpsys = Shell.su("dumpsys", "package", packageName, "|", "grep", keyword).awaitOutputOrThrow()
val versionCode = dumpsys.removePrefix(keyword).substringAfter("minSdk").toInt()
PackageManagerResult.Success(versionCode)
} catch (e: SuException) {
PackageManagerResult.Error(
status = PackageManagerStatus.GET_FAILED_PACKAGE_VERSION_CODE,
message = e.stderrOut
)
} catch (e: java.lang.NumberFormatException) {
PackageManagerResult.Error(
status = PackageManagerStatus.GET_FAILED_PACKAGE_VERSION_CODE,
message = e.stackTraceToString()
)
}
}
override suspend fun getVersionName(packageName: String): PackageManagerResult<String> {
return try {
val keyword = "versionName="
val dumpsys = Shell.su("dumpsys", "package", packageName, "|", "grep", keyword).awaitOutputOrThrow()
val versionName = dumpsys.removePrefix(keyword)
PackageManagerResult.Success(versionName)
} catch (e: SuException) {
PackageManagerResult.Error(
status = PackageManagerStatus.GET_FAILED_PACKAGE_VERSION_NAME,
message = e.stderrOut
)
}
}
override suspend fun getInstallationDir(packageName: String): PackageManagerResult<String> {
return try {
val keyword = "path: "
val dumpsys = Shell.su("dumpsys", "package", packageName, "|", "grep", keyword).awaitOutputOrThrow()
val installationDir = dumpsys.removePrefix(keyword)
PackageManagerResult.Success(installationDir)
} catch (e: SuException) {
PackageManagerResult.Error(
status = PackageManagerStatus.GET_FAILED_PACKAGE_DIR,
message = e.stderrOut
)
}
}
override suspend fun setInstaller(
targetPackage: String,
installerPackage: String
): PackageManagerResult<Nothing> {
try {
Shell.su("pm", "set-installer", targetPackage, installerPackage).awaitOutputOrThrow()
} catch (e: SuException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SET_FAILED_INSTALLER,
message = e.stderrOut
)
}
return PackageManagerResult.Success(null)
}
override suspend fun forceStop(packageName: String): PackageManagerResult<Nothing> {
return try {
Shell.su("am", "force-stop", packageName).awaitOutputOrThrow()
PackageManagerResult.Success(null)
} catch (e: SuException) {
PackageManagerResult.Error(
status = PackageManagerStatus.APP_FAILED_FORCE_STOP,
message = e.stderrOut
)
}
}
override suspend fun installApp(apk: File): PackageManagerResult<Nothing> {
var tempApk: File? = null
try {
tempApk = copyApkToTemp(apk)
Shell.su("pm", "install", "-r", tempApk.absolutePath).awaitOutputOrThrow()
} catch (e: IOException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SESSION_FAILED_COPY,
message = e.stackTraceToString()
)
} catch (e: SuException) {
return PackageManagerResult.Error(
status = getEnumForInstallFailed(e.stderrOut),
message = e.stderrOut
)
} finally {
tempApk?.delete()
}
return PackageManagerResult.Success(null)
}
override suspend fun installSplitApp(apks: Array<File>): PackageManagerResult<Nothing> {
val sessionId: Int
try {
val installCreate = Shell.su("pm", "install-create", "-r").awaitOutputOrThrow()
sessionId = installCreate.toInt()
} catch (e: SuException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SESSION_FAILED_CREATE,
message = e.stderrOut
)
} catch (e: NumberFormatException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SESSION_INVALID_ID,
message = e.stackTraceToString()
)
}
for (apk in apks) {
var tempApk: File? = null
try {
tempApk = copyApkToTemp(apk)
Shell.su("pm", "install-write", sessionId.toString(), tempApk.name, tempApk.absolutePath).awaitOutputOrThrow()
} catch (e: SuException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SESSION_FAILED_WRITE,
message = e.stderrOut
)
} catch (e: IOException) {
return PackageManagerResult.Error(
status = PackageManagerStatus.SESSION_FAILED_COPY,
message = e.stackTraceToString()
)
} finally {
tempApk?.delete()
}
}
try {
Shell.su("pm", "install-commit", sessionId.toString()).awaitOutputOrThrow()
} catch (e: SuException) {
return PackageManagerResult.Error(
status = getEnumForInstallFailed(e.stderrOut),
message = e.stderrOut
)
}
return PackageManagerResult.Success(null)
}
override suspend fun uninstallApp(packageName: String): PackageManagerResult<Nothing> {
return try {
Shell.su("pm", "uninstall", packageName).awaitOutputOrThrow()
PackageManagerResult.Success(null)
} catch (e: SuException) {
PackageManagerResult.Error(
status = PackageManagerStatus.UNINSTALL_FAILED,
message = e.stderrOut
)
}
}
@Throws(
IOException::class,
FileNotFoundException::class
)
private fun copyApkToTemp(apk: File): SuFile {
val tmpPath = "/data/local/tmp/${apk.name}"
val tmpApk = SuFile(tmpPath).apply {
createNewFile()
}
SuFileInputStream.open(tmpApk).use { inputStream ->
SuFileOutputStream.open(tmpApk).use { outputStream ->
inputStream.copyTo(outputStream)
outputStream.flush()
}
}
return tmpApk
}
}
enum class PackageManagerStatus {
SET_FAILED_INSTALLER,
GET_FAILED_PACKAGE_DIR,
GET_FAILED_PACKAGE_VERSION_NAME,
GET_FAILED_PACKAGE_VERSION_CODE,
APP_FAILED_FORCE_STOP,
SESSION_FAILED_CREATE,
SESSION_FAILED_COMMIT,
SESSION_FAILED_WRITE,
SESSION_FAILED_COPY,
SESSION_FAILED_OPEN,
SESSION_INVALID_ID,
INSTALL_FAILED_ABORTED,
INSTALL_FAILED_ALREADY_EXISTS,
INSTALL_FAILED_CPU_ABI_INCOMPATIBLE,
INSTALL_FAILED_INSUFFICIENT_STORAGE,
INSTALL_FAILED_INVALID_APK,
INSTALL_FAILED_VERSION_DOWNGRADE,
INSTALL_FAILED_PARSE_NO_CERTIFICATES,
INSTALL_FAILED_UNKNOWN,
UNINSTALL_FAILED,
LINK_FAILED_UNMOUNT,
LINK_FAILED_MOUNT,
PATCH_FAILED_COPY,
PATCH_FAILED_CHMOD,
PATCH_FAILED_CHOWN,
PATCH_FAILED_CHCON,
PATCH_FAILED_DESTROY,
SCRIPT_FAILED_SETUP_POST_FS,
SCRIPT_FAILED_SETUP_SERVICE_D,
SCRIPT_FAILED_DESTROY_POST_FS,
SCRIPT_FAILED_DESTROY_SERVICE_D,
}
fun getEnumForInstallFailed(outString: String): PackageManagerStatus {
return when {
outString.contains("INSTALL_FAILED_ABORTED") -> PackageManagerStatus.INSTALL_FAILED_ABORTED
outString.contains("INSTALL_FAILED_ALREADY_EXISTS") -> PackageManagerStatus.INSTALL_FAILED_ALREADY_EXISTS
outString.contains("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE") -> PackageManagerStatus.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE
outString.contains("INSTALL_FAILED_INSUFFICIENT_STORAGE") -> PackageManagerStatus.INSTALL_FAILED_INSUFFICIENT_STORAGE
outString.contains("INSTALL_FAILED_INVALID_APK") -> PackageManagerStatus.INSTALL_FAILED_INVALID_APK
outString.contains("INSTALL_FAILED_VERSION_DOWNGRADE") -> PackageManagerStatus.INSTALL_FAILED_VERSION_DOWNGRADE
outString.contains("INSTALL_PARSE_FAILED_NO_CERTIFICATES") -> PackageManagerStatus.INSTALL_FAILED_PARSE_NO_CERTIFICATES
else -> PackageManagerStatus.INSTALL_FAILED_UNKNOWN
}
}
sealed class PackageManagerResult<out V> {
data class Success<out V>(val value: V?) : PackageManagerResult<V>()
data class Error(val status: PackageManagerStatus, val message: String) : PackageManagerResult<Nothing>()
fun getValueOrNull(): V? = getOrElse { null }
val isError
get() = this is Error
val isSuccess
get() = this is Success
}
inline fun <R, T : R> PackageManagerResult<T>.getOrElse(
onError: (PackageManagerResult.Error) -> R?
): R? {
return when (this) {
is PackageManagerResult.Success -> this.value
is PackageManagerResult.Error -> onError(this)
}
}