diff --git a/app/src/main/java/com/vanced/manager/ManagerApplication.kt b/app/src/main/java/com/vanced/manager/ManagerApplication.kt index b22a632e..647ab8f9 100644 --- a/app/src/main/java/com/vanced/manager/ManagerApplication.kt +++ b/app/src/main/java/com/vanced/manager/ManagerApplication.kt @@ -19,6 +19,7 @@ class ManagerApplication : Application() { datasourceModule, downloaderModule, installerModule, + managerModule, networkModule, repositoryModule, serviceModule, diff --git a/app/src/main/java/com/vanced/manager/di/DatasourceModule.kt b/app/src/main/java/com/vanced/manager/di/DatasourceModule.kt index 65164f05..d5eafe56 100644 --- a/app/src/main/java/com/vanced/manager/di/DatasourceModule.kt +++ b/app/src/main/java/com/vanced/manager/di/DatasourceModule.kt @@ -1,8 +1,6 @@ package com.vanced.manager.di import android.content.Context -import com.vanced.manager.repository.source.PkgInfoDatasource -import com.vanced.manager.repository.source.PkgInfoDatasourceImpl import com.vanced.manager.repository.source.PreferenceDatasource import com.vanced.manager.repository.source.PreferenceDatasourceImpl import org.koin.android.ext.koin.androidContext @@ -10,14 +8,6 @@ import org.koin.dsl.module val datasourceModule = module { - fun providePkgInfoDatasource( - context: Context - ): PkgInfoDatasource { - return PkgInfoDatasourceImpl( - packageManager = context.packageManager - ) - } - fun providePreferenceDatasource( context: Context ): PreferenceDatasource { @@ -29,6 +19,5 @@ val datasourceModule = module { ) } - single { providePkgInfoDatasource(androidContext()) } single { providePreferenceDatasource(androidContext()) } } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/di/InstallerModuke.kt b/app/src/main/java/com/vanced/manager/di/InstallerModuke.kt index 2fa146aa..005b4a34 100644 --- a/app/src/main/java/com/vanced/manager/di/InstallerModuke.kt +++ b/app/src/main/java/com/vanced/manager/di/InstallerModuke.kt @@ -4,36 +4,48 @@ import android.content.Context import com.vanced.manager.installer.impl.MicrogInstaller import com.vanced.manager.installer.impl.MusicInstaller import com.vanced.manager.installer.impl.VancedInstaller +import com.vanced.manager.repository.manager.NonrootPackageManager +import com.vanced.manager.repository.manager.RootPackageManager import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val installerModule = module { fun provideVancedInstaller( - context: Context + context: Context, + nonrootPackageManager: NonrootPackageManager, + rootPackageManager: RootPackageManager ): VancedInstaller { return VancedInstaller( - context = context + context = context, + nonrootPackageManager = nonrootPackageManager, + rootPackageManager = rootPackageManager ) } fun provideMusicInstaller( - context: Context + context: Context, + nonrootPackageManager: NonrootPackageManager, + rootPackageManager: RootPackageManager ): MusicInstaller { return MusicInstaller( - context = context + context = context, + nonrootPackageManager = nonrootPackageManager, + rootPackageManager = rootPackageManager ) } fun provideMicrogInstaller( - context: Context + context: Context, + nonrootPackageManager: NonrootPackageManager, ): MicrogInstaller { return MicrogInstaller( - context = context + context = context, + nonrootPackageManager = nonrootPackageManager ) } - single { provideVancedInstaller(androidContext()) } - single { provideMusicInstaller(androidContext()) } - single { provideMicrogInstaller(androidContext()) } + single { provideVancedInstaller(androidContext(), get(), get()) } + single { provideMusicInstaller(androidContext(), get(), get()) } + single { provideMicrogInstaller(androidContext(), get()) } } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/di/ManagerModule.kt b/app/src/main/java/com/vanced/manager/di/ManagerModule.kt new file mode 100644 index 00000000..604f6e49 --- /dev/null +++ b/app/src/main/java/com/vanced/manager/di/ManagerModule.kt @@ -0,0 +1,25 @@ +package com.vanced.manager.di + +import android.content.Context +import com.vanced.manager.repository.manager.NonrootPackageManager +import com.vanced.manager.repository.manager.RootPackageManager +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val managerModule = module { + + fun provideNonrootPackageManager( + context: Context + ): NonrootPackageManager { + return NonrootPackageManager( + context = context + ) + } + + fun provideRootPackageManager(): RootPackageManager { + return RootPackageManager() + } + + single { provideNonrootPackageManager(androidContext()) } + single { provideRootPackageManager() } +} \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/di/RepositoryModule.kt b/app/src/main/java/com/vanced/manager/di/RepositoryModule.kt index 103ad994..c9dc67e7 100644 --- a/app/src/main/java/com/vanced/manager/di/RepositoryModule.kt +++ b/app/src/main/java/com/vanced/manager/di/RepositoryModule.kt @@ -5,7 +5,8 @@ import com.vanced.manager.repository.AppRepository import com.vanced.manager.repository.AppRepositoryImpl import com.vanced.manager.repository.PreferenceRepository import com.vanced.manager.repository.PreferenceRepositoryImpl -import com.vanced.manager.repository.source.PkgInfoDatasource +import com.vanced.manager.repository.manager.NonrootPackageManager +import com.vanced.manager.repository.manager.RootPackageManager import com.vanced.manager.repository.source.PreferenceDatasource import org.koin.dsl.module @@ -13,11 +14,13 @@ val repositoryModule = module { fun provideGithubRepository( githubService: GithubService, - pkgInfoDatasource: PkgInfoDatasource + nonrootPackageManager: NonrootPackageManager, + rootPackageManager: RootPackageManager, ): AppRepository { return AppRepositoryImpl( githubService = githubService, - pkgInfoDatasource = pkgInfoDatasource + nonrootPackageManager = nonrootPackageManager, + rootPackageManager = rootPackageManager ) } @@ -29,6 +32,6 @@ val repositoryModule = module { ) } - single { provideGithubRepository(get(), get()) } + single { provideGithubRepository(get(), get(), get()) } single { providePreferenceRepository(get()) } } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/installer/base/AppInstaller.kt b/app/src/main/java/com/vanced/manager/installer/base/AppInstaller.kt index e5de5b53..771bf97b 100644 --- a/app/src/main/java/com/vanced/manager/installer/base/AppInstaller.kt +++ b/app/src/main/java/com/vanced/manager/installer/base/AppInstaller.kt @@ -1,11 +1,11 @@ package com.vanced.manager.installer.base -import com.vanced.manager.installer.util.PMRootResult +import com.vanced.manager.repository.manager.PackageManagerResult abstract class AppInstaller { - abstract fun install(appVersions: List?) + abstract suspend fun install(appVersions: List?) - abstract fun installRoot(appVersions: List?): PMRootResult + abstract suspend fun installRoot(appVersions: List?): PackageManagerResult } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/installer/impl/MicrogInstaller.kt b/app/src/main/java/com/vanced/manager/installer/impl/MicrogInstaller.kt index 6334dc77..599a0361 100644 --- a/app/src/main/java/com/vanced/manager/installer/impl/MicrogInstaller.kt +++ b/app/src/main/java/com/vanced/manager/installer/impl/MicrogInstaller.kt @@ -3,23 +3,22 @@ package com.vanced.manager.installer.impl import android.content.Context import com.vanced.manager.downloader.util.getMicrogPath import com.vanced.manager.installer.base.AppInstaller -import com.vanced.manager.installer.util.PM -import com.vanced.manager.installer.util.PMRootResult +import com.vanced.manager.repository.manager.NonrootPackageManager +import com.vanced.manager.repository.manager.PackageManagerResult import java.io.File class MicrogInstaller( - private val context: Context + private val context: Context, + private val nonrootPackageManager: NonrootPackageManager, ) : AppInstaller() { - override fun install( - appVersions: List? - ) { - val musicApk = File(getMicrogPath(context) + "/microg.apk") + override suspend fun install(appVersions: List?) { + val musicApk = File(getMicrogPath(context), "microg.apk") - PM.installApp(musicApk, context) + nonrootPackageManager.installApp(musicApk) } - override fun installRoot(appVersions: List?): PMRootResult { + override suspend fun installRoot(appVersions: List?): PackageManagerResult { throw IllegalAccessException("Vanced microG does not have a root installer") } diff --git a/app/src/main/java/com/vanced/manager/installer/impl/MusicInstaller.kt b/app/src/main/java/com/vanced/manager/installer/impl/MusicInstaller.kt index 3572b750..e2444fb9 100644 --- a/app/src/main/java/com/vanced/manager/installer/impl/MusicInstaller.kt +++ b/app/src/main/java/com/vanced/manager/installer/impl/MusicInstaller.kt @@ -5,23 +5,23 @@ import com.vanced.manager.domain.model.AppData import com.vanced.manager.downloader.util.getStockYoutubeMusicPath import com.vanced.manager.downloader.util.getVancedYoutubeMusicPath import com.vanced.manager.installer.base.AppInstaller -import com.vanced.manager.installer.util.PM -import com.vanced.manager.installer.util.PMRoot -import com.vanced.manager.installer.util.PMRootResult import com.vanced.manager.installer.util.RootPatchHelper import com.vanced.manager.preferences.holder.managerVariantPref import com.vanced.manager.preferences.holder.musicVersionPref import com.vanced.manager.preferences.holder.vancedVersionPref +import com.vanced.manager.repository.manager.NonrootPackageManager +import com.vanced.manager.repository.manager.PackageManagerResult +import com.vanced.manager.repository.manager.RootPackageManager import com.vanced.manager.util.getLatestOrProvidedAppVersion import java.io.File class MusicInstaller( - private val context: Context + private val context: Context, + private val rootPackageManager: RootPackageManager, + private val nonrootPackageManager: NonrootPackageManager, ) : AppInstaller() { - override fun install( - appVersions: List? - ) { + override suspend fun install(appVersions: List?) { val absoluteVersion = getLatestOrProvidedAppVersion(musicVersionPref, appVersions) val musicApk = File( @@ -32,33 +32,33 @@ class MusicInstaller( ) + "/music.apk" ) - PM.installApp(musicApk, context) + nonrootPackageManager.installApp(musicApk) } - override fun installRoot(appVersions: List?): PMRootResult { + override suspend fun installRoot(appVersions: List?): PackageManagerResult { val absoluteVersion = getLatestOrProvidedAppVersion(vancedVersionPref, appVersions) - val stockPath = getStockYoutubeMusicPath(absoluteVersion, context) + "/base.apk" - val vancedPath = getVancedYoutubeMusicPath(absoluteVersion, "root", context) + "/base.apk" + val stock = File(getStockYoutubeMusicPath(absoluteVersion, context), "base.apk") + val vanced = File(getVancedYoutubeMusicPath(absoluteVersion, "root", context), "base.apk") val prepareStock = RootPatchHelper.prepareStock( stockPackage = AppData.PACKAGE_ROOT_VANCED_YOUTUBE_MUSIC, stockVersion = absoluteVersion ) { - PMRoot.installApp(stockPath) + rootPackageManager.installApp(stock) } if (prepareStock.isError) return prepareStock val patchStock = RootPatchHelper.patchStock( - patchPath = vancedPath, + patchPath = vanced.absolutePath, stockPackage = AppData.PACKAGE_ROOT_VANCED_YOUTUBE_MUSIC, app = APP_KEY ) if (patchStock.isError) return patchStock - return PMRootResult.Success() + return PackageManagerResult.Success(null) } companion object { diff --git a/app/src/main/java/com/vanced/manager/installer/impl/VancedInstaller.kt b/app/src/main/java/com/vanced/manager/installer/impl/VancedInstaller.kt index e1763e38..cdbdf5af 100644 --- a/app/src/main/java/com/vanced/manager/installer/impl/VancedInstaller.kt +++ b/app/src/main/java/com/vanced/manager/installer/impl/VancedInstaller.kt @@ -5,41 +5,41 @@ import com.vanced.manager.domain.model.AppData import com.vanced.manager.downloader.util.getStockYoutubePath import com.vanced.manager.downloader.util.getVancedYoutubePath import com.vanced.manager.installer.base.AppInstaller -import com.vanced.manager.installer.util.PM -import com.vanced.manager.installer.util.PMRoot -import com.vanced.manager.installer.util.PMRootResult import com.vanced.manager.installer.util.RootPatchHelper import com.vanced.manager.preferences.holder.vancedVersionPref +import com.vanced.manager.repository.manager.NonrootPackageManager +import com.vanced.manager.repository.manager.PackageManagerResult +import com.vanced.manager.repository.manager.RootPackageManager import com.vanced.manager.util.getLatestOrProvidedAppVersion import java.io.File class VancedInstaller( - private val context: Context + private val context: Context, + private val rootPackageManager: RootPackageManager, + private val nonrootPackageManager: NonrootPackageManager, ) : AppInstaller() { - override fun install( - appVersions: List? - ) { + override suspend fun install(appVersions: List?) { val absoluteVersion = getLatestOrProvidedAppVersion(vancedVersionPref, appVersions) val apks = File(getVancedYoutubePath(absoluteVersion, "nonroot", context)) .listFiles() - PM.installSplitApp(apks!!, context) + nonrootPackageManager.installSplitApp(apks!!) } - override fun installRoot(appVersions: List?): PMRootResult { + override suspend fun installRoot(appVersions: List?): PackageManagerResult { val absoluteVersion = getLatestOrProvidedAppVersion(vancedVersionPref, appVersions) val stockApks = File(getStockYoutubePath(absoluteVersion, context)) - .listFiles()?.map { it.absolutePath } + .listFiles() val vancedBaseApk = getVancedYoutubePath(absoluteVersion, "root", context) + "/base.apk" val prepareStock = RootPatchHelper.prepareStock( stockPackage = AppData.PACKAGE_ROOT_VANCED_YOUTUBE, stockVersion = absoluteVersion, ) { - PMRoot.installSplitApp(stockApks!!) + rootPackageManager.installSplitApp(stockApks!!) } if (prepareStock.isError) return prepareStock @@ -52,7 +52,7 @@ class VancedInstaller( if (patchStock.isError) return patchStock - return PMRootResult.Success() + return PackageManagerResult.Success(null) } companion object { diff --git a/app/src/main/java/com/vanced/manager/installer/util/PMRoot.kt b/app/src/main/java/com/vanced/manager/installer/util/PMRoot.kt index 8e95a529..75a46b6b 100644 --- a/app/src/main/java/com/vanced/manager/installer/util/PMRoot.kt +++ b/app/src/main/java/com/vanced/manager/installer/util/PMRoot.kt @@ -3,6 +3,9 @@ package com.vanced.manager.installer.util import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.io.SuFile import com.topjohnwu.superuser.io.SuFileOutputStream +import com.vanced.manager.repository.manager.PackageManagerResult +import com.vanced.manager.repository.manager.PackageManagerStatus +import com.vanced.manager.repository.manager.getEnumForInstallFailed import com.vanced.manager.util.errString import com.vanced.manager.util.outString import java.io.File @@ -10,11 +13,11 @@ import java.io.IOException object PMRoot { - fun installApp(apkPath: String): PMRootResult { + fun installApp(apkPath: String): PackageManagerResult { val apk = File(apkPath) val tmpApk = copyApkToTemp(apk).getOrElse { exception -> - return PMRootResult.Error( - PMRootStatus.SESSION_FAILED_COPY, + return PackageManagerResult.Error( + PackageManagerStatus.SESSION_FAILED_COPY, exception.stackTraceToString() ) } @@ -25,28 +28,28 @@ object PMRoot { if (!install.isSuccess) { val errString = install.errString - return PMRootResult.Error(getEnumForInstallFailed(errString), errString) + return PackageManagerResult.Error(getEnumForInstallFailed(errString), errString) } - return PMRootResult.Success() + return PackageManagerResult.Success(null) } - fun installSplitApp(apkPaths: List): PMRootResult { + fun installSplitApp(apkPaths: List): PackageManagerResult { val installCreate = Shell.su("pm", "install-create", "-r").exec() if (!installCreate.isSuccess) - return PMRootResult.Error(PMRootStatus.SESSION_FAILED_CREATE, installCreate.errString) + return PackageManagerResult.Error(PackageManagerStatus.SESSION_FAILED_CREATE, installCreate.errString) val sessionId = installCreate.outString if (sessionId.toIntOrNull() == null) - return PMRootResult.Error(PMRootStatus.SESSION_INVALID_ID, installCreate.errString) + return PackageManagerResult.Error(PackageManagerStatus.SESSION_INVALID_ID, installCreate.errString) for (apkPath in apkPaths) { val apk = File(apkPath) val tmpApk = copyApkToTemp(apk).getOrElse { exception -> - return PMRootResult.Error( - PMRootStatus.SESSION_FAILED_COPY, + return PackageManagerResult.Error( + PackageManagerStatus.SESSION_FAILED_COPY, exception.stackTraceToString() ) } @@ -58,87 +61,87 @@ object PMRoot { tmpApk.delete() if (!installWrite.isSuccess) - return PMRootResult.Error(PMRootStatus.SESSION_FAILED_WRITE, installWrite.errString) + return PackageManagerResult.Error(PackageManagerStatus.SESSION_FAILED_WRITE, installWrite.errString) } val installCommit = Shell.su("pm", "install-commit", sessionId).exec() if (!installCommit.isSuccess) { val errString = installCommit.errString - return PMRootResult.Error(getEnumForInstallFailed(errString), errString) + return PackageManagerResult.Error(getEnumForInstallFailed(errString), errString) } - return PMRootResult.Success() + return PackageManagerResult.Success(null) } - fun uninstallApp(pkg: String): PMRootResult { + fun uninstallApp(pkg: String): PackageManagerResult { val uninstall = Shell.su("pm", "uninstall", pkg).exec() if (!uninstall.isSuccess) - return PMRootResult.Error(PMRootStatus.UNINSTALL_FAILED, uninstall.errString) + return PackageManagerResult.Error(PackageManagerStatus.UNINSTALL_FAILED, uninstall.errString) - return PMRootResult.Success() + return PackageManagerResult.Success(null) } - fun setInstallerPackage(targetPkg: String, installerPkg: String): PMRootResult { + fun setInstallerPackage(targetPkg: String, installerPkg: String): PackageManagerResult { val setInstaller = Shell.su("pm", "set-installer", targetPkg, installerPkg) .exec() if (!setInstaller.isSuccess) - return PMRootResult.Error( - PMRootStatus.ACTION_FAILED_SET_INSTALLER, + return PackageManagerResult.Error( + PackageManagerStatus.SET_FAILED_INSTALLER, setInstaller.errString ) - return PMRootResult.Success() + return PackageManagerResult.Success(null) } - fun forceStopApp(pkg: String): PMRootResult { + fun forceStopApp(pkg: String): PackageManagerResult { val stopApp = Shell.su("am", "force-stop", pkg).exec() if (!stopApp.isSuccess) - return PMRootResult.Error(PMRootStatus.ACTION_FAILED_FORCE_STOP_APP, stopApp.errString) + return PackageManagerResult.Error(PackageManagerStatus.APP_FAILED_FORCE_STOP, stopApp.errString) - return PMRootResult.Success() + return PackageManagerResult.Success(null) } - fun getPackageVersionName(pkg: String): PMRootResult { + fun getPackageVersionName(pkg: String): PackageManagerResult { val keyword = "versionName=" val dumpsys = Shell.su("dumpsys", "package", pkg, "|", "grep", keyword).exec() if (!dumpsys.isSuccess) - return PMRootResult.Error( - PMRootStatus.ACTION_FAILED_GET_PACKAGE_VERSION_NAME, + return PackageManagerResult.Error( + PackageManagerStatus.GET_FAILED_PACKAGE_VERSION_NAME, dumpsys.errString ) - return PMRootResult.Success(dumpsys.outString.removePrefix(keyword)) + return PackageManagerResult.Success(dumpsys.outString.removePrefix(keyword)) } - fun getPackageVersionCode(pkg: String): PMRootResult { + fun getPackageVersionCode(pkg: String): PackageManagerResult { val keyword = "versionCode=" val dumpsys = Shell.su("dumpsys", "package", pkg, "|", "grep", keyword).exec() if (!dumpsys.isSuccess) - return PMRootResult.Error( - PMRootStatus.ACTION_FAILED_GET_PACKAGE_VERSION_CODE, + return PackageManagerResult.Error( + PackageManagerStatus.GET_FAILED_PACKAGE_VERSION_CODE, dumpsys.errString ) - return PMRootResult.Success( + return PackageManagerResult.Success( dumpsys.outString.removePrefix(keyword).substringAfter("minSdk") .toLong() ) } - fun getPackageDir(pkg: String): PMRootResult { + fun getPackageDir(pkg: String): PackageManagerResult { val keyword = "path: " val dumpsys = Shell.su("dumpsys", "package", pkg, "|", "grep", keyword).exec() if (!dumpsys.isSuccess) - return PMRootResult.Error(PMRootStatus.ACTION_FAILED_GET_PACKAGE_DIR, dumpsys.errString) + return PackageManagerResult.Error(PackageManagerStatus.GET_FAILED_PACKAGE_DIR, dumpsys.errString) - return PMRootResult.Success(dumpsys.outString.removePrefix(keyword)) + return PackageManagerResult.Success(dumpsys.outString.removePrefix(keyword)) } } @@ -159,16 +162,4 @@ private fun copyApkToTemp(apk: File): Result { } return Result.success(tmpApk) -} - -private fun getEnumForInstallFailed(outString: String) = - when { - outString.contains("INSTALL_FAILED_ABORTED") -> PMRootStatus.INSTALL_FAILED_ABORTED - outString.contains("INSTALL_FAILED_ALREADY_EXISTS") -> PMRootStatus.INSTALL_FAILED_ALREADY_EXISTS - outString.contains("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE") -> PMRootStatus.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE - outString.contains("INSTALL_FAILED_INSUFFICIENT_STORAGE") -> PMRootStatus.INSTALL_FAILED_INSUFFICIENT_STORAGE - outString.contains("INSTALL_FAILED_INVALID_APK") -> PMRootStatus.INSTALL_FAILED_INVALID_APK - outString.contains("INSTALL_FAILED_VERSION_DOWNGRADE") -> PMRootStatus.INSTALL_FAILED_VERSION_DOWNGRADE - outString.contains("INSTALL_PARSE_FAILED_NO_CERTIFICATES") -> PMRootStatus.INSTALL_FAILED_PARSE_NO_CERTIFICATES - else -> PMRootStatus.INSTALL_FAILED_UNKNOWN - } \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/installer/util/Patcher.kt b/app/src/main/java/com/vanced/manager/installer/util/Patcher.kt index f9bfb13d..af523990 100644 --- a/app/src/main/java/com/vanced/manager/installer/util/Patcher.kt +++ b/app/src/main/java/com/vanced/manager/installer/util/Patcher.kt @@ -5,6 +5,8 @@ import com.topjohnwu.superuser.io.SuFile import com.topjohnwu.superuser.io.SuFileOutputStream import com.vanced.manager.io.ManagerSuFile import com.vanced.manager.io.SUIOException +import com.vanced.manager.repository.manager.PackageManagerResult +import com.vanced.manager.repository.manager.PackageManagerStatus import com.vanced.manager.util.errString import java.io.File import java.io.IOException @@ -15,7 +17,7 @@ object Patcher { app: String, stockPackage: String, stockPath: String, - ): PMRootResult { + ): PackageManagerResult { val postFsDataScriptPath = getAppPostFsScriptPath(app) val serviceDScriptPath = getAppServiceDScriptPath(app) @@ -24,22 +26,22 @@ object Patcher { val copyServiceDScript = copyScriptToDestination(postFsDataScript, postFsDataScriptPath) if (copyServiceDScript.isFailure) - return PMRootResult.Error( - PMRootStatus.SCRIPT_FAILED_SETUP_POST_FS, + return PackageManagerResult.Error( + PackageManagerStatus.SCRIPT_FAILED_SETUP_POST_FS, copyServiceDScript.exceptionOrNull()!!.stackTraceToString() ) val copyPostFsDataScript = copyScriptToDestination(serviceDScript, serviceDScriptPath) if (copyPostFsDataScript.isFailure) - return PMRootResult.Error( - PMRootStatus.SCRIPT_FAILED_SETUP_SERVICE_D, + return PackageManagerResult.Error( + PackageManagerStatus.SCRIPT_FAILED_SETUP_SERVICE_D, copyPostFsDataScript.exceptionOrNull()!!.stackTraceToString() ) - return PMRootResult.Success() + return PackageManagerResult.Success(null) } - fun movePatchToDataAdb(patchPath: String, app: String): PMRootResult { + fun movePatchToDataAdb(patchPath: String, app: String): PackageManagerResult { val newPatchPath = getAppPatchPath(app) val patchApk = File(patchPath) @@ -53,42 +55,42 @@ object Patcher { try { patchApk.copyTo(newPatchApk) } catch (e: IOException) { - return PMRootResult.Error(PMRootStatus.PATCH_FAILED_COPY, e.stackTraceToString()) + return PackageManagerResult.Error(PackageManagerStatus.PATCH_FAILED_COPY, e.stackTraceToString()) } val chmod = Shell.su("chmod", "644", newPatchPath).exec() if (!chmod.isSuccess) - return PMRootResult.Error(PMRootStatus.PATCH_FAILED_CHMOD, chmod.errString) + return PackageManagerResult.Error(PackageManagerStatus.PATCH_FAILED_CHMOD, chmod.errString) val chown = Shell.su("chown", "system:system", newPatchPath).exec() if (!chmod.isSuccess) - return PMRootResult.Error(PMRootStatus.PATCH_FAILED_CHOWN, chown.errString) + return PackageManagerResult.Error(PackageManagerStatus.PATCH_FAILED_CHOWN, chown.errString) - return PMRootResult.Success() + return PackageManagerResult.Success(null) } - fun chconPatch(app: String): PMRootResult { + fun chconPatch(app: String): PackageManagerResult { val chcon = Shell.su("chcon u:object_r:apk_data_file:s0 ${getAppPatchPath(app)}").exec() if (!chcon.isSuccess) - return PMRootResult.Error(PMRootStatus.PATCH_FAILED_CHCON, chcon.errString) + return PackageManagerResult.Error(PackageManagerStatus.PATCH_FAILED_CHCON, chcon.errString) - return PMRootResult.Success() + return PackageManagerResult.Success(null) } - fun linkPatch(app: String, stockPackage: String, stockPath: String): PMRootResult { + fun linkPatch(app: String, stockPackage: String, stockPath: String): PackageManagerResult { val umount = Shell.su("""for i in ${'$'}(ls /data/app/ | grep $stockPackage | tr " "); do umount -l "/data/app/${"$"}i/base.apk"; done """) .exec() if (!umount.isSuccess) - return PMRootResult.Error(PMRootStatus.LINK_FAILED_UNMOUNT, umount.errString) + return PackageManagerResult.Error(PackageManagerStatus.LINK_FAILED_UNMOUNT, umount.errString) val mount = Shell.su("su", "-mm", "-c", """"mount -o bind ${getAppPatchPath(app)} $stockPath"""") .exec() if (!mount.isSuccess) - return PMRootResult.Error(PMRootStatus.LINK_FAILED_MOUNT, mount.errString) + return PackageManagerResult.Error(PackageManagerStatus.LINK_FAILED_MOUNT, mount.errString) - return PMRootResult.Success() + return PackageManagerResult.Success(null) } fun destroyPatch(app: String) = @@ -132,11 +134,11 @@ private fun cleanPatchFiles( postFsPath: String, serviceDPath: String, patchPath: String, -): PMRootResult { +): PackageManagerResult { val files = mapOf( - postFsPath to PMRootStatus.SCRIPT_FAILED_DESTROY_POST_FS, - serviceDPath to PMRootStatus.SCRIPT_FAILED_DESTROY_SERVICE_D, - patchPath to PMRootStatus.PATCH_FAILED_DESTROY, + postFsPath to PackageManagerStatus.SCRIPT_FAILED_DESTROY_POST_FS, + serviceDPath to PackageManagerStatus.SCRIPT_FAILED_DESTROY_SERVICE_D, + patchPath to PackageManagerStatus.PATCH_FAILED_DESTROY, ) for ((filePath, errorStatusType) in files) { @@ -145,11 +147,11 @@ private fun cleanPatchFiles( if (exists()) delete() } } catch (e: SUIOException) { - return PMRootResult.Error(errorStatusType, e.stackTraceToString()) + return PackageManagerResult.Error(errorStatusType, e.stackTraceToString()) } } - return PMRootResult.Success() + return PackageManagerResult.Success(null) } private fun copyScriptToDestination( diff --git a/app/src/main/java/com/vanced/manager/installer/util/RootPatchHelper.kt b/app/src/main/java/com/vanced/manager/installer/util/RootPatchHelper.kt index a7c3b6b5..04ec578a 100644 --- a/app/src/main/java/com/vanced/manager/installer/util/RootPatchHelper.kt +++ b/app/src/main/java/com/vanced/manager/installer/util/RootPatchHelper.kt @@ -1,8 +1,11 @@ package com.vanced.manager.installer.util +import com.vanced.manager.repository.manager.PackageManagerResult +import com.vanced.manager.repository.manager.getOrElse + object RootPatchHelper { - fun cleanPatches(app: String): PMRootResult { + fun cleanPatches(app: String): PackageManagerResult { val cleanOldPatches = Patcher.destroyOldPatch(app) if (cleanOldPatches.isError) return cleanOldPatches @@ -11,14 +14,14 @@ object RootPatchHelper { if (cleanOldPatches.isError) return cleanPatches - return PMRootResult.Success() + return PackageManagerResult.Success(null) } inline fun prepareStock( stockPackage: String, stockVersion: String, - install: () -> PMRootResult - ): PMRootResult { + install: () -> PackageManagerResult + ): PackageManagerResult { val stockYoutubeVersion = PMRoot.getPackageVersionName(stockPackage) .getOrElse { null } if (stockYoutubeVersion != stockVersion) { @@ -31,14 +34,14 @@ object RootPatchHelper { return installStock } - return PMRootResult.Success() + return PackageManagerResult.Success(null) } fun patchStock( patchPath: String, stockPackage: String, app: String - ): PMRootResult { + ): PackageManagerResult { val movePatch = Patcher.movePatchToDataAdb(patchPath, app) if (movePatch.isError) return movePatch @@ -51,14 +54,14 @@ object RootPatchHelper { .getOrElse { error -> return error }!! val setupScript = Patcher.setupScript(app, stockPackage, stockPackageDir) - if (setupScript is PMRootResult.Error) + if (setupScript is PackageManagerResult.Error) return setupScript val linkPatch = Patcher.linkPatch(app, stockPackage, stockPackageDir) - if (linkPatch is PMRootResult.Error) + if (linkPatch is PackageManagerResult.Error) return linkPatch - return PMRootResult.Success() + return PackageManagerResult.Success(null) } } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/repository/AppRepository.kt b/app/src/main/java/com/vanced/manager/repository/AppRepository.kt index 964e1663..5c96efce 100644 --- a/app/src/main/java/com/vanced/manager/repository/AppRepository.kt +++ b/app/src/main/java/com/vanced/manager/repository/AppRepository.kt @@ -6,7 +6,9 @@ import com.vanced.manager.domain.model.AppState import com.vanced.manager.domain.model.AppType import com.vanced.manager.network.GithubService import com.vanced.manager.network.dto.GithubReleaseDto -import com.vanced.manager.repository.source.PkgInfoDatasource +import com.vanced.manager.repository.manager.NonrootPackageManager +import com.vanced.manager.repository.manager.PackageManager +import com.vanced.manager.repository.manager.RootPackageManager interface AppRepository { @@ -26,15 +28,18 @@ interface AppRepository { class AppRepositoryImpl( private val githubService: GithubService, - private val pkgInfoDatasource: PkgInfoDatasource, + private val nonrootPackageManager: NonrootPackageManager, + private val rootPackageManager: RootPackageManager, ) : AppRepository { override suspend fun getVancedYoutubeNonroot(): App { val githubRelease = githubService.getVancedYoutubeRelease() val remoteVersionCode = githubRelease.getVersionCode() val remoteVersionName = githubRelease.getVersionName() - val installedVersionCode = pkgInfoDatasource.getVersionCode(AppData.PACKAGE_VANCED_YOUTUBE) - val installedVersionName = pkgInfoDatasource.getVersionName(AppData.PACKAGE_VANCED_YOUTUBE) + val installedVersionCode = + nonrootPackageManager.getVersionCode(AppData.PACKAGE_VANCED_YOUTUBE).getValueOrNull() + val installedVersionName = + nonrootPackageManager.getVersionName(AppData.PACKAGE_VANCED_YOUTUBE).getValueOrNull() return App( name = AppData.NAME_VANCED_YOUTUBE, iconResId = AppData.ICON_VANCED_YOUTUBE, @@ -55,9 +60,9 @@ class AppRepositoryImpl( val remoteVersionCode = githubRelease.getVersionCode() val remoteVersionName = githubRelease.getVersionName() val installedVersionCode = - pkgInfoDatasource.getVersionCode(AppData.PACKAGE_ROOT_VANCED_YOUTUBE) + rootPackageManager.getVersionCode(AppData.PACKAGE_ROOT_VANCED_YOUTUBE).getValueOrNull() val installedVersionName = - pkgInfoDatasource.getVersionName(AppData.PACKAGE_ROOT_VANCED_YOUTUBE) + rootPackageManager.getVersionName(AppData.PACKAGE_ROOT_VANCED_YOUTUBE).getValueOrNull() return App( name = AppData.NAME_VANCED_YOUTUBE, iconResId = AppData.ICON_VANCED_YOUTUBE, @@ -78,9 +83,9 @@ class AppRepositoryImpl( val remoteVersionCode = githubRelease.getVersionCode() val remoteVersionName = githubRelease.getVersionName() val installedVersionCode = - pkgInfoDatasource.getVersionCode(AppData.PACKAGE_VANCED_YOUTUBE_MUSIC) + nonrootPackageManager.getVersionCode(AppData.PACKAGE_VANCED_YOUTUBE_MUSIC).getValueOrNull() val installedVersionName = - pkgInfoDatasource.getVersionName(AppData.PACKAGE_VANCED_YOUTUBE_MUSIC) + nonrootPackageManager.getVersionName(AppData.PACKAGE_VANCED_YOUTUBE_MUSIC).getValueOrNull() return App( name = AppData.NAME_VANCED_YOUTUBE_MUSIC, iconResId = AppData.ICON_VANCED_YOUTUBE_MUSIC, @@ -101,9 +106,9 @@ class AppRepositoryImpl( val remoteVersionCode = githubRelease.getVersionCode() val remoteVersionName = githubRelease.getVersionName() val installedVersionCode = - pkgInfoDatasource.getVersionCode(AppData.PACKAGE_ROOT_VANCED_YOUTUBE_MUSIC) + rootPackageManager.getVersionCode(AppData.PACKAGE_ROOT_VANCED_YOUTUBE_MUSIC).getValueOrNull() val installedVersionName = - pkgInfoDatasource.getVersionName(AppData.PACKAGE_ROOT_VANCED_YOUTUBE_MUSIC) + rootPackageManager.getVersionName(AppData.PACKAGE_ROOT_VANCED_YOUTUBE_MUSIC).getValueOrNull() return App( name = AppData.NAME_VANCED_YOUTUBE_MUSIC, iconResId = AppData.ICON_VANCED_YOUTUBE_MUSIC, @@ -123,8 +128,10 @@ class AppRepositoryImpl( val githubRelease = githubService.getVancedMicrogRelease() val remoteVersionCode = githubRelease.getVersionCode() val remoteVersionName = githubRelease.getVersionName() - val installedVersionCode = pkgInfoDatasource.getVersionCode(AppData.PACKAGE_VANCED_MICROG) - val installedVersionName = pkgInfoDatasource.getVersionName(AppData.PACKAGE_VANCED_MICROG) + val installedVersionCode = + nonrootPackageManager.getVersionCode(AppData.PACKAGE_VANCED_MICROG).getValueOrNull() + val installedVersionName = + nonrootPackageManager.getVersionName(AppData.PACKAGE_VANCED_MICROG).getValueOrNull() return App( name = AppData.NAME_VANCED_MICROG, iconResId = AppData.ICON_VANCED_MICROG, @@ -144,8 +151,10 @@ class AppRepositoryImpl( val githubRelease = githubService.getVancedManagerRelease() val remoteVersionCode = githubRelease.getVersionCode() val remoteVersionName = githubRelease.getVersionName() - val installedVersionCode = pkgInfoDatasource.getVersionCode(AppData.PACKAGE_VANCED_MANAGER) - val installedVersionName = pkgInfoDatasource.getVersionName(AppData.PACKAGE_VANCED_MANAGER) + val installedVersionCode = + nonrootPackageManager.getVersionCode(AppData.PACKAGE_VANCED_MANAGER).getValueOrNull() + val installedVersionName = + nonrootPackageManager.getVersionName(AppData.PACKAGE_VANCED_MANAGER).getValueOrNull() return App( name = AppData.NAME_VANCED_MANAGER, iconResId = AppData.ICON_VANCED_MANAGER, diff --git a/app/src/main/java/com/vanced/manager/repository/manager/PackageManager.kt b/app/src/main/java/com/vanced/manager/repository/manager/PackageManager.kt new file mode 100644 index 00000000..3928afe5 --- /dev/null +++ b/app/src/main/java/com/vanced/manager/repository/manager/PackageManager.kt @@ -0,0 +1,486 @@ +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 + + suspend fun getVersionName(packageName: String): PackageManagerResult + + suspend fun getInstallationDir(packageName: String): PackageManagerResult + + suspend fun setInstaller(targetPackage: String, installerPackage: String): PackageManagerResult + + suspend fun forceStop(packageName: String): PackageManagerResult + + suspend fun installApp(apk: File): PackageManagerResult + + suspend fun installSplitApp(apks: Array): PackageManagerResult + + suspend fun uninstallApp(packageName: String): PackageManagerResult + +} + +class NonrootPackageManager( + private val context: Context +) : PackageManager { + + @SuppressLint("WrongConstant") + @Suppress("DEPRECATION") + override suspend fun getVersionCode(packageName: String): PackageManagerResult { + 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 { + 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 { + 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 { + return PackageManagerResult.Error( + status = PackageManagerStatus.SET_FAILED_INSTALLER, + message = "Unsupported" + ) + } + + override suspend fun forceStop(packageName: String): PackageManagerResult { + return PackageManagerResult.Error( + status = PackageManagerStatus.APP_FAILED_FORCE_STOP, + message = "Unsupported" + ) + } + + override suspend fun installApp(apk: File): PackageManagerResult { + return createInstallationSession { + writeApkToSession(apk) + } + } + + override suspend fun installSplitApp(apks: Array): PackageManagerResult { + return createInstallationSession { + for (apk in apks) { + writeApkToSession(apk) + } + } + } + + override suspend fun uninstallApp(packageName: String): PackageManagerResult { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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): PackageManagerResult { + 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 { + 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 { + data class Success(val value: V?) : PackageManagerResult() + data class Error(val status: PackageManagerStatus, val message: String) : PackageManagerResult() + + fun getValueOrNull(): V? = getOrElse { null } + + val isError + get() = this is Error + + val isSuccess + get() = this is Success +} + +inline fun PackageManagerResult.getOrElse( + onError: (PackageManagerResult.Error) -> R? +): R? { + return when (this) { + is PackageManagerResult.Success -> this.value + is PackageManagerResult.Error -> onError(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/repository/source/PkgInfoDatasource.kt b/app/src/main/java/com/vanced/manager/repository/source/PkgInfoDatasource.kt deleted file mode 100644 index b9e57d6b..00000000 --- a/app/src/main/java/com/vanced/manager/repository/source/PkgInfoDatasource.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.vanced.manager.repository.source - -import android.annotation.SuppressLint -import android.content.pm.PackageManager -import android.os.Build - -interface PkgInfoDatasource { - - fun getVersionCode(packageName: String): Int? - - fun getVersionName(packageName: String): String? - -} - -class PkgInfoDatasourceImpl( - private val packageManager: PackageManager -) : PkgInfoDatasource { - - private companion object { - const val FLAG_NOTHING = 0 - const val VERSION_IGNORE_MAJOR = 0xFFFFFFFF - } - - @SuppressLint("WrongConstant") - override fun getVersionCode(packageName: String): Int? { - return try { - val packageInfo = packageManager.getPackageInfo(packageName, FLAG_NOTHING) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - packageInfo.longVersionCode.and(VERSION_IGNORE_MAJOR).toInt() - } else { - @Suppress("DEPRECATION") - packageInfo.versionCode - } - } catch (e: PackageManager.NameNotFoundException) { - null - } - } - - @Suppress("DEPRECATION") - @SuppressLint("WrongConstant") - override fun getVersionName(packageName: String): String? { - return try { - packageManager - .getPackageInfo(packageName, FLAG_NOTHING) - .versionName - } catch (e: PackageManager.NameNotFoundException) { - null - } - } -} \ 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 e04ba03e..859a0513 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 @@ -14,11 +14,11 @@ import com.vanced.manager.downloader.impl.VancedDownloader import com.vanced.manager.installer.impl.MicrogInstaller import com.vanced.manager.installer.impl.MusicInstaller import com.vanced.manager.installer.impl.VancedInstaller -import com.vanced.manager.installer.util.PMRootResult import com.vanced.manager.network.util.MICROG_NAME import com.vanced.manager.network.util.MUSIC_NAME import com.vanced.manager.network.util.VANCED_NAME import com.vanced.manager.preferences.holder.managerVariantPref +import com.vanced.manager.repository.manager.PackageManagerResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -117,7 +117,7 @@ class InstallViewModel( } } - private fun installApp( + private suspend fun installApp( appName: String, appVersions: List?, ) { @@ -127,11 +127,11 @@ class InstallViewModel( if (isRoot) { when (val installStatus = installer.installRoot(appVersions)) { - is PMRootResult.Success -> { + is PackageManagerResult.Success -> { status = Status.Installed log(Log.Success("Successfully installed")) } - is PMRootResult.Error -> { + is PackageManagerResult.Error -> { status = Status.Failure log(Log.Error("Failed to install app", installStatus.message)) } diff --git a/app/src/main/java/com/vanced/manager/util/SuShell.kt b/app/src/main/java/com/vanced/manager/util/SuShell.kt index 38a5945c..73b6f072 100644 --- a/app/src/main/java/com/vanced/manager/util/SuShell.kt +++ b/app/src/main/java/com/vanced/manager/util/SuShell.kt @@ -2,6 +2,7 @@ package com.vanced.manager.util import com.topjohnwu.superuser.Shell import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine val Shell.Result.outString @@ -13,8 +14,26 @@ val Shell.Result.errString val isMagiskInstalled get() = Shell.rootAccess() && Shell.su("magisk", "-c").exec().isSuccess -suspend fun Shell.Job.await() = - suspendCoroutine { continuation -> - submit(/*continuation.context.asExecutor(),*/ continuation::resume) +suspend fun Shell.Job.await(): Shell.Result { + return suspendCoroutine { continuation -> + submit { + continuation.resume(it) + } } +} + +class SuException(val stderrOut: String) : Exception(stderrOut) + +@Throws(SuException::class) +suspend fun Shell.Job.awaitOutputOrThrow(): String { + return suspendCoroutine { continuation -> + submit { + if (it.isSuccess) { + continuation.resume(it.outString) + } else { + continuation.resumeWithException(SuException(it.errString)) + } + } + } +}