diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c34a6474..a0f70ec1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") diff --git a/app/src/main/java/com/vanced/manager/core/downloader/util/DownloadPath.kt b/app/src/main/java/com/vanced/manager/core/downloader/util/DownloadPath.kt index 5aa02e5c..76511b77 100644 --- a/app/src/main/java/com/vanced/manager/core/downloader/util/DownloadPath.kt +++ b/app/src/main/java/com/vanced/manager/core/downloader/util/DownloadPath.kt @@ -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, diff --git a/app/src/main/java/com/vanced/manager/core/installer/base/AppInstaller.kt b/app/src/main/java/com/vanced/manager/core/installer/base/AppInstaller.kt index 24d02994..592290a9 100644 --- a/app/src/main/java/com/vanced/manager/core/installer/base/AppInstaller.kt +++ b/app/src/main/java/com/vanced/manager/core/installer/base/AppInstaller.kt @@ -4,4 +4,6 @@ abstract class AppInstaller { abstract fun install(appVersions: List?) + abstract fun installRoot(appVersions: List?) + } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/core/installer/impl/MicrogInstaller.kt b/app/src/main/java/com/vanced/manager/core/installer/impl/MicrogInstaller.kt index 6c673a75..993cce40 100644 --- a/app/src/main/java/com/vanced/manager/core/installer/impl/MicrogInstaller.kt +++ b/app/src/main/java/com/vanced/manager/core/installer/impl/MicrogInstaller.kt @@ -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?) { + throw IllegalAccessException("Vanced microG does not have a root installer") } } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/core/installer/impl/MusicInstaller.kt b/app/src/main/java/com/vanced/manager/core/installer/impl/MusicInstaller.kt index 825c29ec..2880a6a4 100644 --- a/app/src/main/java/com/vanced/manager/core/installer/impl/MusicInstaller.kt +++ b/app/src/main/java/com/vanced/manager/core/installer/impl/MusicInstaller.kt @@ -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?) { + } } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/core/installer/impl/VancedInstaller.kt b/app/src/main/java/com/vanced/manager/core/installer/impl/VancedInstaller.kt index 1d10cce1..08de16fd 100644 --- a/app/src/main/java/com/vanced/manager/core/installer/impl/VancedInstaller.kt +++ b/app/src/main/java/com/vanced/manager/core/installer/impl/VancedInstaller.kt @@ -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?) { - installSplitApp(apks!!, context) } } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/core/installer/service/AppInstallService.kt b/app/src/main/java/com/vanced/manager/core/installer/service/AppInstallService.kt index ed3d3183..559ccd12 100644 --- a/app/src/main/java/com/vanced/manager/core/installer/service/AppInstallService.kt +++ b/app/src/main/java/com/vanced/manager/core/installer/service/AppInstallService.kt @@ -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" } } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/core/installer/util/PM.kt b/app/src/main/java/com/vanced/manager/core/installer/util/PM.kt index c419e33f..285cefc9 100644 --- a/app/src/main/java/com/vanced/manager/core/installer/util/PM.kt +++ b/app/src/main/java/com/vanced/manager/core/installer/util/PM.kt @@ -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, 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, 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( diff --git a/app/src/main/java/com/vanced/manager/core/installer/util/PMRoot.kt b/app/src/main/java/com/vanced/manager/core/installer/util/PMRoot.kt new file mode 100644 index 00000000..9e35096f --- /dev/null +++ b/app/src/main/java/com/vanced/manager/core/installer/util/PMRoot.kt @@ -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 { + 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): PMRootStatus { + 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 { + 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 { + 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 { + 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 { + 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 + } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/core/installer/util/PMRootStatus.kt b/app/src/main/java/com/vanced/manager/core/installer/util/PMRootStatus.kt new file mode 100644 index 00000000..8da73942 --- /dev/null +++ b/app/src/main/java/com/vanced/manager/core/installer/util/PMRootStatus.kt @@ -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 { + data class Success(val value: V? = null) : PMRootStatus() + data class Error(val error: PMRootStatusType, val message: String) : PMRootStatus() +} + +inline fun > S.exec( + onError: (PMRootStatusType, String) -> Unit, + onSuccess: () -> Unit +) { + when (this) { + is PMRootStatus.Success<*> -> { + onSuccess() + } + is PMRootStatus.Error -> { + onError(error, message) + } + } +} + +inline fun > 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/core/installer/util/Patcher.kt b/app/src/main/java/com/vanced/manager/core/installer/util/Patcher.kt new file mode 100644 index 00000000..4d0fb903 --- /dev/null +++ b/app/src/main/java/com/vanced/manager/core/installer/util/Patcher.kt @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/core/io/ManagerSuFile.kt b/app/src/main/java/com/vanced/manager/core/io/ManagerSuFile.kt new file mode 100644 index 00000000..7c97db25 --- /dev/null +++ b/app/src/main/java/com/vanced/manager/core/io/ManagerSuFile.kt @@ -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(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 @@") + +} \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/core/util/SuShell.kt b/app/src/main/java/com/vanced/manager/core/util/SuShell.kt new file mode 100644 index 00000000..7173aa05 --- /dev/null +++ b/app/src/main/java/com/vanced/manager/core/util/SuShell.kt @@ -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 + diff --git a/app/src/main/java/com/vanced/manager/ui/MainActivity.kt b/app/src/main/java/com/vanced/manager/ui/MainActivity.kt index 3609ec67..61c26842 100644 --- a/app/src/main/java/com/vanced/manager/ui/MainActivity.kt +++ b/app/src/main/java/com/vanced/manager/ui/MainActivity.kt @@ -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() } diff --git a/app/src/main/java/com/vanced/manager/ui/SplashScreenActivity.kt b/app/src/main/java/com/vanced/manager/ui/SplashScreenActivity.kt index c2697d5c..1670d386 100644 --- a/app/src/main/java/com/vanced/manager/ui/SplashScreenActivity.kt +++ b/app/src/main/java/com/vanced/manager/ui/SplashScreenActivity.kt @@ -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() + } } } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/ui/viewmodel/InstallViewModel.kt b/app/src/main/java/com/vanced/manager/ui/viewmodel/InstallViewModel.kt index 1ec68716..f965d66e 100644 --- a/app/src/main/java/com/vanced/manager/ui/viewmodel/InstallViewModel.kt +++ b/app/src/main/java/com/vanced/manager/ui/viewmodel/InstallViewModel.kt @@ -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)) } }