mirror of
https://github.com/YTVanced/VancedManager
synced 2024-12-01 07:23:02 +00:00
added root package manager and patcher classes
This commit is contained in:
parent
22ac84e448
commit
cbd48dce72
16 changed files with 513 additions and 46 deletions
|
@ -123,6 +123,11 @@ dependencies {
|
|||
|
||||
implementation("com.github.skydoves:orchestra-colorpicker:1.1.0")
|
||||
|
||||
val libsuVersion = "3.1.2"
|
||||
implementation("com.github.topjohnwu.libsu:core:$libsuVersion")
|
||||
implementation("com.github.topjohnwu.libsu:io:$libsuVersion")
|
||||
implementation("com.github.topjohnwu.libsu:busybox:$libsuVersion")
|
||||
|
||||
val koinVersion = "3.1.3"
|
||||
implementation("io.insert-koin:koin-android:$koinVersion")
|
||||
implementation("io.insert-koin:koin-androidx-compose:$koinVersion")
|
||||
|
|
|
@ -8,6 +8,11 @@ fun getVancedYoutubePath(
|
|||
context: Context
|
||||
) = context.getExternalFilesDir("vanced_youtube")!!.path + "/$version/$variant"
|
||||
|
||||
fun getStockYoutubePath(
|
||||
version: String,
|
||||
context: Context
|
||||
) = context.getExternalFilesDir("stock_youtube")!!.path + "/$version"
|
||||
|
||||
fun getVancedMusicPath(
|
||||
version: String,
|
||||
variant: String,
|
||||
|
|
|
@ -4,4 +4,6 @@ abstract class AppInstaller {
|
|||
|
||||
abstract fun install(appVersions: List<String>?)
|
||||
|
||||
abstract fun installRoot(appVersions: List<String>?)
|
||||
|
||||
}
|
|
@ -3,7 +3,7 @@ package com.vanced.manager.core.installer.impl
|
|||
import android.content.Context
|
||||
import com.vanced.manager.core.downloader.util.getMicrogPath
|
||||
import com.vanced.manager.core.installer.base.AppInstaller
|
||||
import com.vanced.manager.core.installer.util.installApp
|
||||
import com.vanced.manager.core.installer.util.PM
|
||||
import java.io.File
|
||||
|
||||
class MicrogInstaller(
|
||||
|
@ -15,7 +15,11 @@ class MicrogInstaller(
|
|||
) {
|
||||
val musicApk = File(getMicrogPath(context) + "/microg.apk")
|
||||
|
||||
installApp(musicApk, context)
|
||||
PM.installApp(musicApk, context)
|
||||
}
|
||||
|
||||
override fun installRoot(appVersions: List<String>?) {
|
||||
throw IllegalAccessException("Vanced microG does not have a root installer")
|
||||
}
|
||||
|
||||
}
|
|
@ -3,10 +3,9 @@ package com.vanced.manager.core.installer.impl
|
|||
import android.content.Context
|
||||
import com.vanced.manager.core.downloader.util.getVancedMusicPath
|
||||
import com.vanced.manager.core.installer.base.AppInstaller
|
||||
import com.vanced.manager.core.installer.util.installApp
|
||||
import com.vanced.manager.core.installer.util.PM
|
||||
import com.vanced.manager.core.preferences.holder.managerVariantPref
|
||||
import com.vanced.manager.core.preferences.holder.musicVersionPref
|
||||
import com.vanced.manager.core.preferences.holder.vancedVersionPref
|
||||
import com.vanced.manager.core.util.getLatestOrProvidedAppVersion
|
||||
import java.io.File
|
||||
|
||||
|
@ -21,7 +20,11 @@ class MusicInstaller(
|
|||
|
||||
val musicApk = File(getVancedMusicPath(absoluteVersion, managerVariantPref, context) + "/music.apk")
|
||||
|
||||
installApp(musicApk, context)
|
||||
PM.installApp(musicApk, context)
|
||||
}
|
||||
|
||||
override fun installRoot(appVersions: List<String>?) {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
package com.vanced.manager.core.installer.impl
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.vanced.manager.core.downloader.util.getStockYoutubePath
|
||||
import com.vanced.manager.core.downloader.util.getVancedYoutubePath
|
||||
import com.vanced.manager.core.installer.base.AppInstaller
|
||||
import com.vanced.manager.core.installer.util.installSplitApp
|
||||
import com.vanced.manager.core.preferences.holder.managerVariantPref
|
||||
import com.vanced.manager.core.installer.util.*
|
||||
import com.vanced.manager.core.preferences.holder.vancedVersionPref
|
||||
import com.vanced.manager.core.util.Message
|
||||
import com.vanced.manager.core.util.getLatestOrProvidedAppVersion
|
||||
import java.io.File
|
||||
|
||||
|
@ -18,12 +20,14 @@ class VancedInstaller(
|
|||
) {
|
||||
val absoluteVersion = getLatestOrProvidedAppVersion(vancedVersionPref, appVersions)
|
||||
|
||||
val apks = File(getVancedYoutubePath(absoluteVersion, managerVariantPref, context))
|
||||
.listFiles { file ->
|
||||
file.extension == "apk"
|
||||
}
|
||||
val apks = File(getVancedYoutubePath(absoluteVersion, "nonroot", context))
|
||||
.listFiles()
|
||||
|
||||
PM.installSplitApp(apks!!, context)
|
||||
}
|
||||
|
||||
override fun installRoot(appVersions: List<String>?) {
|
||||
|
||||
installSplitApp(apks!!, context)
|
||||
}
|
||||
|
||||
}
|
|
@ -26,7 +26,7 @@ class AppInstallService : Service() {
|
|||
sendBroadcast(Intent().apply {
|
||||
action = APP_INSTALL_ACTION
|
||||
putExtra(EXTRA_INSTALL_STATUS, extraStatus)
|
||||
putExtra(EXTRA_INSTALL_EXTRA, extraStatusMessage)
|
||||
putExtra(EXTRA_INSTALL_STATUS_MESSAGE, extraStatusMessage)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class AppInstallService : Service() {
|
|||
const val APP_INSTALL_ACTION = "APP_INSTALL_ACTION"
|
||||
|
||||
const val EXTRA_INSTALL_STATUS = "EXTRA_INSTALL_STATUS"
|
||||
const val EXTRA_INSTALL_EXTRA = "EXTRA_INSTALL_EXTRA"
|
||||
const val EXTRA_INSTALL_STATUS_MESSAGE = "EXTRA_INSTALL_STATUS_MESSAGE"
|
||||
}
|
||||
|
||||
}
|
|
@ -13,29 +13,32 @@ import java.io.FileInputStream
|
|||
|
||||
private const val byteArraySize = 1024 * 1024 // Because 1,048,576 is not readable
|
||||
|
||||
fun installApp(apk: File, context: Context) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val session =
|
||||
packageInstaller.openSession(packageInstaller.createSession(sessionParams))
|
||||
writeApkToSession(apk, session)
|
||||
session.commit(context.installIntentSender)
|
||||
session.close()
|
||||
}
|
||||
object PM {
|
||||
|
||||
fun installSplitApp(apks: Array<File>, context: Context) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val session =
|
||||
packageInstaller.openSession(packageInstaller.createSession(sessionParams))
|
||||
for (apk in apks) {
|
||||
fun installApp(apk: File, context: Context) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val session =
|
||||
packageInstaller.openSession(packageInstaller.createSession(sessionParams))
|
||||
writeApkToSession(apk, session)
|
||||
session.commit(context.installIntentSender)
|
||||
session.close()
|
||||
}
|
||||
session.commit(context.installIntentSender)
|
||||
session.close()
|
||||
}
|
||||
|
||||
fun uninstallPackage(pkg: String, context: Context) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
packageInstaller.uninstall(pkg, context.uninstallIntentSender)
|
||||
fun installSplitApp(apks: Array<File>, context: Context) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val session =
|
||||
packageInstaller.openSession(packageInstaller.createSession(sessionParams))
|
||||
for (apk in apks) {
|
||||
writeApkToSession(apk, session)
|
||||
}
|
||||
session.commit(context.installIntentSender)
|
||||
session.close()
|
||||
}
|
||||
|
||||
fun uninstallPackage(pkg: String, context: Context) {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
packageInstaller.uninstall(pkg, context.uninstallIntentSender)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeApkToSession(
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
package com.vanced.manager.core.installer.util
|
||||
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream
|
||||
import com.vanced.manager.core.util.errString
|
||||
import com.vanced.manager.core.util.outString
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
object PMRoot {
|
||||
|
||||
fun installApp(apkPath: String): PMRootStatus<Nothing> {
|
||||
val apk = File(apkPath)
|
||||
val tmpApk = copyApkToTemp(apk) { error ->
|
||||
return PMRootStatus.Error(PMRootStatusType.SESSION_FAILED_COPY, error)
|
||||
}
|
||||
|
||||
val install = Shell.su("pm", "install", "-r", tmpApk.absolutePath).exec()
|
||||
|
||||
tmpApk.delete()
|
||||
|
||||
if (!install.isSuccess) {
|
||||
val errString = install.errString
|
||||
return PMRootStatus.Error(getEnumForInstallFailed(errString), errString)
|
||||
}
|
||||
|
||||
return PMRootStatus.Success()
|
||||
}
|
||||
|
||||
fun installSplitApp(apkPaths: List<String>): PMRootStatus<Nothing> {
|
||||
val installCreate = Shell.su("pm", "install-create", "-r").exec()
|
||||
|
||||
if (!installCreate.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.SESSION_FAILED_CREATE, installCreate.errString)
|
||||
|
||||
val sessionId = installCreate.outString
|
||||
|
||||
if (sessionId.toIntOrNull() == null)
|
||||
return PMRootStatus.Error(PMRootStatusType.SESSION_INVALID_ID, installCreate.errString)
|
||||
|
||||
for (apkPath in apkPaths) {
|
||||
val apk = File(apkPath)
|
||||
val tmpApk = copyApkToTemp(apk) { error ->
|
||||
return PMRootStatus.Error(PMRootStatusType.SESSION_FAILED_COPY, error)
|
||||
}
|
||||
|
||||
val installWrite =
|
||||
Shell.su("pm", "install-write", sessionId, tmpApk.name, tmpApk.absolutePath)
|
||||
.exec()
|
||||
|
||||
tmpApk.delete()
|
||||
|
||||
if (!installWrite.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.SESSION_FAILED_WRITE, installWrite.errString)
|
||||
}
|
||||
|
||||
val installCommit = Shell.su("pm", "install-commit", sessionId).exec()
|
||||
|
||||
if (!installCommit.isSuccess) {
|
||||
val errString = installCommit.errString
|
||||
return PMRootStatus.Error(getEnumForInstallFailed(errString), errString)
|
||||
}
|
||||
|
||||
return PMRootStatus.Success()
|
||||
}
|
||||
|
||||
fun uninstallApp(pkg: String): PMRootStatus<Nothing> {
|
||||
val uninstall = Shell.su("pm", "uninstall", pkg).exec()
|
||||
|
||||
if (!uninstall.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.UNINSTALL_FAILED, uninstall.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
}
|
||||
|
||||
fun setInstallerPackage(targetPkg: String, installerPkg: String): PMRootStatus<Nothing> {
|
||||
val setInstaller = Shell.su("pm", "set-installer", targetPkg, installerPkg)
|
||||
.exec()
|
||||
|
||||
if (!setInstaller.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.ACTION_FAILED_SET_INSTALLER, setInstaller.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
}
|
||||
|
||||
fun forceStopApp(pkg: String): PMRootStatus<Nothing> {
|
||||
val stopApp = Shell.su("am", "force-stop", pkg).exec()
|
||||
|
||||
if (!stopApp.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.ACTION_FAILED_FORCE_STOP_APP, stopApp.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
}
|
||||
|
||||
fun getPackageDir(pkg: String): PMRootStatus<String> {
|
||||
val delimeter = "path: "
|
||||
val dumpsys = Shell.su("dumpsys", "package", pkg, "|", "grep", delimeter).exec()
|
||||
|
||||
if (!dumpsys.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.ACTION_FAILED_GET_PACKAGE_DIR, dumpsys.errString)
|
||||
|
||||
return PMRootStatus.Success(dumpsys.outString.removePrefix(delimeter))
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun copyApkToTemp(
|
||||
apk: File,
|
||||
onError: (String) -> Unit
|
||||
): SuFile {
|
||||
val tmpPath = "/data/local/tmp/${apk.name}"
|
||||
|
||||
val tmpApk = SuFile(tmpPath).apply {
|
||||
createNewFile()
|
||||
}
|
||||
|
||||
try {
|
||||
SuFileOutputStream.open(tmpApk).use {
|
||||
it.write(apk.readBytes())
|
||||
it.flush()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
onError(e.stackTraceToString())
|
||||
}
|
||||
|
||||
return tmpApk
|
||||
}
|
||||
|
||||
private fun getEnumForInstallFailed(outString: String) =
|
||||
when {
|
||||
outString.contains("INSTALL_FAILED_ABORTED") -> PMRootStatusType.INSTALL_FAILED_ABORTED
|
||||
outString.contains("INSTALL_FAILED_ALREADY_EXISTS") -> PMRootStatusType.INSTALL_FAILED_ALREADY_EXISTS
|
||||
outString.contains("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE") -> PMRootStatusType.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE
|
||||
outString.contains("INSTALL_FAILED_INSUFFICIENT_STORAGE") -> PMRootStatusType.INSTALL_FAILED_INSUFFICIENT_STORAGE
|
||||
outString.contains("INSTALL_FAILED_INVALID_APK") -> PMRootStatusType.INSTALL_FAILED_INVALID_APK
|
||||
outString.contains("INSTALL_FAILED_VERSION_DOWNGRADE") -> PMRootStatusType.INSTALL_FAILED_VERSION_DOWNGRADE
|
||||
outString.contains("INSTALL_PARSE_FAILED_NO_CERTIFICATES") -> PMRootStatusType.INSTALL_FAILED_PARSE_NO_CERTIFICATES
|
||||
else -> PMRootStatusType.INSTALL_FAILED_UNKNOWN
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package com.vanced.manager.core.installer.util
|
||||
|
||||
enum class PMRootStatusType {
|
||||
ACTION_FAILED_SET_INSTALLER,
|
||||
ACTION_FAILED_GET_PACKAGE_DIR,
|
||||
ACTION_FAILED_FORCE_STOP_APP,
|
||||
|
||||
INSTALL_SUCCESSFUL,
|
||||
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,
|
||||
|
||||
LINK_FAILED_UNMOUNT,
|
||||
LINK_FAILED_MOUNT,
|
||||
|
||||
PATCH_FAILED_COPY,
|
||||
PATCH_FAILED_CHMOD,
|
||||
PATCH_FAILED_CHOWN,
|
||||
PATCH_FAILED_CHCON,
|
||||
PATCH_FAILED_DESTROY,
|
||||
|
||||
SESSION_FAILED_CREATE,
|
||||
SESSION_FAILED_WRITE,
|
||||
SESSION_FAILED_COPY,
|
||||
SESSION_INVALID_ID,
|
||||
|
||||
SCRIPT_FAILED_SETUP_POST_FS,
|
||||
SCRIPT_FAILED_SETUP_SERVICE_D,
|
||||
SCRIPT_FAILED_DESTROY_POST_FS,
|
||||
SCRIPT_FAILED_DESTROY_SERVICE_D,
|
||||
|
||||
UNINSTALL_SUCCESSFUL,
|
||||
UNINSTALL_FAILED,
|
||||
}
|
||||
|
||||
sealed class PMRootStatus<out V> {
|
||||
data class Success<out V>(val value: V? = null) : PMRootStatus<V>()
|
||||
data class Error(val error: PMRootStatusType, val message: String) : PMRootStatus<Nothing>()
|
||||
}
|
||||
|
||||
inline fun <V, S : PMRootStatus<V>> S.exec(
|
||||
onError: (PMRootStatusType, String) -> Unit,
|
||||
onSuccess: () -> Unit
|
||||
) {
|
||||
when (this) {
|
||||
is PMRootStatus.Success<*> -> {
|
||||
onSuccess()
|
||||
}
|
||||
is PMRootStatus.Error -> {
|
||||
onError(error, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified V, S : PMRootStatus<V>> S.execWithValue(
|
||||
onError: (PMRootStatusType, String) -> Unit,
|
||||
onSuccess: (value: V?) -> Unit
|
||||
) {
|
||||
when (this) {
|
||||
is PMRootStatus.Success<*> -> {
|
||||
onSuccess(value as V?)
|
||||
}
|
||||
is PMRootStatus.Error -> {
|
||||
onError(error, message)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package com.vanced.manager.core.installer.util
|
||||
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream
|
||||
import com.vanced.manager.core.util.errString
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
object Patcher {
|
||||
|
||||
fun setupScript(
|
||||
app: String,
|
||||
pkg: String,
|
||||
stockPath: String,
|
||||
): PMRootStatus<Nothing> {
|
||||
val postFsDataScriptPath = getAppPostFsScriptPath(app)
|
||||
val serviceDScriptPath = getAppServiceDScriptPath(app)
|
||||
|
||||
val postFsDataScript = getPostFsDataScript(pkg)
|
||||
val serviceDScript = getServiceDScript(getAppPatchPath(app), stockPath)
|
||||
|
||||
copyScriptToDestination(postFsDataScriptPath, postFsDataScript) { error ->
|
||||
return PMRootStatus.Error(PMRootStatusType.SCRIPT_FAILED_SETUP_POST_FS, error)
|
||||
}
|
||||
|
||||
copyScriptToDestination(serviceDScriptPath, serviceDScript) { error ->
|
||||
return PMRootStatus.Error(PMRootStatusType.SCRIPT_FAILED_SETUP_SERVICE_D, error)
|
||||
}
|
||||
|
||||
return PMRootStatus.Success()
|
||||
}
|
||||
|
||||
fun movePatchToDataAdb(patchPath: String, app: String): PMRootStatus<Nothing> {
|
||||
val newPatchPath = getAppPatchPath(app)
|
||||
|
||||
val patchApk = File(patchPath)
|
||||
val newPatchApk = SuFile(newPatchPath).apply {
|
||||
if (exists())
|
||||
delete()
|
||||
|
||||
createNewFile()
|
||||
}
|
||||
|
||||
try {
|
||||
patchApk.copyTo(newPatchApk)
|
||||
} catch (e: IOException) {
|
||||
return PMRootStatus.Error(PMRootStatusType.PATCH_FAILED_COPY, e.stackTraceToString())
|
||||
}
|
||||
|
||||
val chmod = Shell.su("chmod", "644", newPatchPath).exec()
|
||||
if (!chmod.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.PATCH_FAILED_CHMOD, chmod.errString)
|
||||
|
||||
val chown = Shell.su("chown", "system:system", newPatchPath).exec()
|
||||
if (!chmod.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.PATCH_FAILED_CHOWN, chown.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
}
|
||||
|
||||
fun chconPatch(app: String): PMRootStatus<Nothing> {
|
||||
val chcon = Shell.su("chcon u:object_r:apk_data_file:s0 ${getAppPatchPath(app)}").exec()
|
||||
if (!chcon.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.PATCH_FAILED_CHCON, chcon.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
}
|
||||
|
||||
fun linkPatch(app: String, pkg: String, stockPath: String): PMRootStatus<Nothing> {
|
||||
val umount = Shell.su("""for i in ${'$'}(ls /data/app/ | grep $pkg | tr " "); do umount -l "/data/app/${"$"}i/base.apk"; done """).exec()
|
||||
if (!umount.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.LINK_FAILED_UNMOUNT, umount.errString)
|
||||
|
||||
val mount = Shell.su("su", "-mm", "-c", """"mount -o bind ${getAppPatchPath(app)} $stockPath"""").exec()
|
||||
if (!mount.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.LINK_FAILED_MOUNT, mount.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
}
|
||||
|
||||
fun destroyPatch(app: String) =
|
||||
cleanPatchFiles(
|
||||
postFsPath = getAppPostFsScriptPath(app),
|
||||
serviceDPath = getAppServiceDScriptPath(app),
|
||||
patchPath = getAppPatchPath(app)
|
||||
)
|
||||
|
||||
//TODO
|
||||
fun destroyOldPatch(app: String) =
|
||||
cleanPatchFiles(
|
||||
postFsPath = "",
|
||||
serviceDPath = "",
|
||||
patchPath = ""
|
||||
)
|
||||
|
||||
//TODO return proper error if destroying was unsuccessful
|
||||
private fun cleanPatchFiles(
|
||||
postFsPath: String,
|
||||
serviceDPath: String,
|
||||
patchPath: String,
|
||||
): PMRootStatus<Nothing> {
|
||||
val postFs = SuFile(postFsPath)
|
||||
if (postFs.exists() && !postFs.delete())
|
||||
return PMRootStatus.Error(PMRootStatusType.SCRIPT_FAILED_DESTROY_POST_FS, "")
|
||||
|
||||
val serviceD = SuFile(serviceDPath)
|
||||
if (serviceD.exists() && !serviceD.delete())
|
||||
return PMRootStatus.Error(PMRootStatusType.SCRIPT_FAILED_DESTROY_SERVICE_D, "")
|
||||
|
||||
val patch = SuFile(patchPath)
|
||||
if (patch.exists() && !patch.delete())
|
||||
return PMRootStatus.Error(PMRootStatusType.PATCH_FAILED_DESTROY, "")
|
||||
|
||||
return PMRootStatus.Success()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAppPatchPath(app: String) = "${getAppPatchFolderPath(app)}/base.apk"
|
||||
private fun getAppPatchFolderPath(app: String) = "/data/adb/vanced_manager/$app"
|
||||
private fun getAppPostFsScriptPath(app: String) = "/data/adb/post-fs-data.d/$app.sh"
|
||||
private fun getAppServiceDScriptPath(app: String) = "/data/adb/service.d/$app.sh"
|
||||
|
||||
//TODO support dynamic sleep timer
|
||||
private fun getServiceDScript(patchPath: String, stockPath: String) =
|
||||
"""
|
||||
#!/system/bin/sh
|
||||
while [ "${'$'}(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 1; done
|
||||
sleep 1
|
||||
chcon u:object_r:apk_data_file:s0 $patchPath
|
||||
mount -o bind $patchPath $stockPath
|
||||
""".trimIndent()
|
||||
|
||||
private fun getPostFsDataScript(pkg: String) =
|
||||
"""
|
||||
#!/system/bin/sh
|
||||
while read line; do echo \${'$'}{line} | grep $pkg | awk '{print \${'$'}2}' | xargs umount -l; done< /proc/mounts
|
||||
""".trimIndent()
|
||||
|
||||
private inline fun copyScriptToDestination(
|
||||
scriptDestination: String,
|
||||
script: String,
|
||||
onError: (String) -> Unit
|
||||
) {
|
||||
val scriptFile = SuFile(scriptDestination)
|
||||
.apply {
|
||||
if (!exists())
|
||||
createNewFile()
|
||||
}
|
||||
|
||||
try {
|
||||
SuFileOutputStream.open(scriptFile).use {
|
||||
it.write(script.toByteArray())
|
||||
it.flush()
|
||||
}
|
||||
val chmod = Shell.su("chmod", "744", scriptFile.absolutePath).exec()
|
||||
if (!chmod.isSuccess) {
|
||||
onError(chmod.errString)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
onError(e.stackTraceToString())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.vanced.manager.core.io
|
||||
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
import com.vanced.manager.core.util.errString
|
||||
import com.vanced.manager.core.util.outString
|
||||
import java.io.File
|
||||
|
||||
class ManagerSuFile : SuFile {
|
||||
|
||||
sealed class SuFileResult {
|
||||
data class Success<out V>(val result: V? = null) : SuFileResult()
|
||||
data class Error(val error: String) : SuFileResult()
|
||||
}
|
||||
|
||||
constructor(pathName: String) : super(pathName)
|
||||
|
||||
constructor(parent: String, child: String) : super(parent, child)
|
||||
|
||||
constructor(parent: File, child: String) : super(parent, child)
|
||||
|
||||
private fun cmd(input: String): SuFileResult {
|
||||
val cmd = Shell.su(input.replace("@@", escapedPath)).exec()
|
||||
if (!cmd.isSuccess)
|
||||
return SuFileResult.Error(cmd.errString)
|
||||
|
||||
return SuFileResult.Success(cmd.outString)
|
||||
}
|
||||
|
||||
fun deleteResult() = cmd("rm -f @@ || rmdir -f @@")
|
||||
|
||||
}
|
13
app/src/main/java/com/vanced/manager/core/util/SuShell.kt
Normal file
13
app/src/main/java/com/vanced/manager/core/util/SuShell.kt
Normal file
|
@ -0,0 +1,13 @@
|
|||
package com.vanced.manager.core.util
|
||||
|
||||
import com.topjohnwu.superuser.Shell
|
||||
|
||||
val Shell.Result.outString
|
||||
get() = out.joinToString("\n")
|
||||
|
||||
val Shell.Result.errString
|
||||
get() = err.joinToString("\n")
|
||||
|
||||
val isMagiskInstalled
|
||||
get() = Shell.su("magisk", "-c").exec().isSuccess
|
||||
|
|
@ -41,7 +41,7 @@ class MainActivity : ComponentActivity() {
|
|||
AppInstallService.APP_INSTALL_ACTION -> {
|
||||
installViewModel.postInstallStatus(
|
||||
pmStatus = intent.getIntExtra(AppInstallService.EXTRA_INSTALL_STATUS, -999),
|
||||
extra = intent.getStringExtra(AppInstallService.EXTRA_INSTALL_EXTRA)!!,
|
||||
extra = intent.getStringExtra(AppInstallService.EXTRA_INSTALL_STATUS_MESSAGE)!!,
|
||||
)
|
||||
mainViewModel.fetch()
|
||||
}
|
||||
|
|
|
@ -3,16 +3,30 @@ package com.vanced.manager.ui
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import com.topjohnwu.superuser.BusyBoxInstaller
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.vanced.manager.BuildConfig
|
||||
|
||||
class SplashScreenActivity : ComponentActivity() {
|
||||
|
||||
init {
|
||||
Shell.enableVerboseLogging = BuildConfig.DEBUG;
|
||||
Shell.setDefaultBuilder(
|
||||
Shell.Builder
|
||||
.create()
|
||||
.setInitializers(BusyBoxInstaller::class.java)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
startActivity(
|
||||
Intent(this, MainActivity::class.java)
|
||||
)
|
||||
finish()
|
||||
Shell.getShell {
|
||||
startActivity(
|
||||
Intent(this, MainActivity::class.java)
|
||||
)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -63,14 +63,22 @@ class InstallViewModel(
|
|||
}
|
||||
|
||||
fun postInstallStatus(pmStatus: Int, extra: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (pmStatus == PackageInstaller.STATUS_SUCCESS) {
|
||||
status = Status.Installed
|
||||
log(Log.Success("Successfully installed"))
|
||||
} else {
|
||||
status = Status.Failure
|
||||
log(Log.Error("Failed to install app", extra))
|
||||
}
|
||||
if (pmStatus == PackageInstaller.STATUS_SUCCESS) {
|
||||
status = Status.Installed
|
||||
log(Log.Success("Successfully installed"))
|
||||
} else {
|
||||
status = Status.Failure
|
||||
log(Log.Error("Failed to install app", extra))
|
||||
}
|
||||
}
|
||||
|
||||
fun postInstallStatusRoot(pmStatus: Int, extra: String) {
|
||||
if (pmStatus == PackageInstaller.STATUS_SUCCESS) {
|
||||
status = Status.Installed
|
||||
log(Log.Success("Successfully installed"))
|
||||
} else {
|
||||
status = Status.Failure
|
||||
log(Log.Error("Failed to install app", extra))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue