further implement root installer
This commit is contained in:
parent
20e67bbb7c
commit
f2107b1477
|
@ -1,83 +1,67 @@
|
|||
package com.vanced.manager.core.downloader.base
|
||||
|
||||
import com.vanced.manager.core.downloader.util.DownloadStatus
|
||||
import com.vanced.manager.core.io.writeFile
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.awaitResponse
|
||||
import java.io.FileOutputStream
|
||||
|
||||
typealias DownloadCall = Call<ResponseBody>
|
||||
|
||||
abstract class AppDownloader {
|
||||
|
||||
data class DownloadFile(
|
||||
val fileName: String,
|
||||
val call: DownloadCall,
|
||||
val call: Call<ResponseBody>,
|
||||
)
|
||||
|
||||
private lateinit var call: DownloadCall
|
||||
sealed class DownloadStatus {
|
||||
object Success : DownloadStatus()
|
||||
data class Error(val error: String, val fileName: String) : DownloadStatus()
|
||||
|
||||
val isSuccess
|
||||
get() = this is Success
|
||||
|
||||
val isError
|
||||
get() = this is Error
|
||||
}
|
||||
|
||||
abstract suspend fun download(
|
||||
appVersions: List<String>?,
|
||||
onStatus: (DownloadStatus) -> Unit
|
||||
)
|
||||
onProgress: (Float) -> Unit,
|
||||
onFile: (String) -> Unit
|
||||
): DownloadStatus
|
||||
|
||||
abstract suspend fun downloadRoot(
|
||||
appVersions: List<String>?,
|
||||
onProgress: (Float) -> Unit,
|
||||
onFile: (String) -> Unit
|
||||
): DownloadStatus
|
||||
|
||||
abstract fun getSavedFilePath(): String
|
||||
|
||||
suspend fun downloadFiles(
|
||||
downloadFiles: Array<DownloadFile>,
|
||||
onFile: (String) -> Unit,
|
||||
suspend inline fun downloadFiles(
|
||||
files: Array<DownloadFile>,
|
||||
onProgress: (Float) -> Unit,
|
||||
onError: (error: String, fileName: String) -> Unit,
|
||||
onSuccess: () -> Unit
|
||||
) {
|
||||
for (downloadFile in downloadFiles) {
|
||||
onFile: (String) -> Unit
|
||||
): DownloadStatus {
|
||||
for (file in files) {
|
||||
try {
|
||||
this.call = downloadFile.call
|
||||
onFile(file.fileName)
|
||||
|
||||
onFile(downloadFile.fileName)
|
||||
|
||||
val response = call.awaitResponse()
|
||||
val response = file.call.awaitResponse()
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
if (body != null) {
|
||||
writeFile(body, downloadFile.fileName, onProgress)
|
||||
}
|
||||
response.body()?.writeFile(getSavedFilePath() + "/${file.fileName}", onProgress)
|
||||
continue
|
||||
}
|
||||
|
||||
val error = response.errorBody()?.toString()
|
||||
if (error != null) {
|
||||
onError(error, downloadFile.fileName)
|
||||
return
|
||||
return DownloadStatus.Error(error, file.fileName)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
onError(e.stackTraceToString(), downloadFile.fileName)
|
||||
return
|
||||
return DownloadStatus.Error(e.stackTraceToString(), file.fileName)
|
||||
}
|
||||
}
|
||||
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
private inline fun writeFile(
|
||||
body: ResponseBody,
|
||||
fileName: String,
|
||||
onProgress: (Float) -> Unit
|
||||
) {
|
||||
val inputStream = body.byteStream()
|
||||
val outputStream = FileOutputStream(getSavedFilePath() + "/$fileName")
|
||||
val totalBytes = body.contentLength()
|
||||
val fileReader = ByteArray(4096)
|
||||
var downloadedBytes = 0L
|
||||
var read: Int
|
||||
while (inputStream.read(fileReader).also { read = it } != -1) {
|
||||
outputStream.write(fileReader, 0, read)
|
||||
downloadedBytes += read
|
||||
onProgress((downloadedBytes * 100 / totalBytes).toFloat())
|
||||
}
|
||||
inputStream.close()
|
||||
outputStream.close()
|
||||
return DownloadStatus.Success
|
||||
}
|
||||
|
||||
}
|
|
@ -3,7 +3,6 @@ package com.vanced.manager.core.downloader.impl
|
|||
import android.content.Context
|
||||
import com.vanced.manager.core.downloader.api.MicrogAPI
|
||||
import com.vanced.manager.core.downloader.base.AppDownloader
|
||||
import com.vanced.manager.core.downloader.util.DownloadStatus
|
||||
import com.vanced.manager.core.downloader.util.getMicrogPath
|
||||
import java.io.File
|
||||
|
||||
|
@ -14,33 +13,31 @@ class MicrogDownloader(
|
|||
|
||||
override suspend fun download(
|
||||
appVersions: List<String>?,
|
||||
onStatus: (DownloadStatus) -> Unit
|
||||
) {
|
||||
downloadFiles(
|
||||
downloadFiles = arrayOf(
|
||||
onProgress: (Float) -> Unit,
|
||||
onFile: (String) -> Unit
|
||||
): DownloadStatus {
|
||||
val downloadStatus = downloadFiles(
|
||||
files = arrayOf(
|
||||
DownloadFile(
|
||||
call = microgAPI.getFile(),
|
||||
fileName = "microg.apk"
|
||||
)
|
||||
),
|
||||
onProgress = { progress ->
|
||||
onStatus(DownloadStatus.Progress(progress))
|
||||
},
|
||||
onFile = { fileName ->
|
||||
onStatus(DownloadStatus.File(fileName))
|
||||
},
|
||||
onSuccess = {
|
||||
onStatus(DownloadStatus.StartInstall)
|
||||
},
|
||||
onError = { error, fileName ->
|
||||
onStatus(
|
||||
DownloadStatus.Error(
|
||||
displayError = "Failed to download $fileName",
|
||||
stacktrace = error
|
||||
)
|
||||
)
|
||||
}
|
||||
onProgress = onProgress,
|
||||
onFile = onFile
|
||||
)
|
||||
if (downloadStatus.isError)
|
||||
return downloadStatus
|
||||
|
||||
return DownloadStatus.Success
|
||||
}
|
||||
|
||||
override suspend fun downloadRoot(
|
||||
appVersions: List<String>?,
|
||||
onProgress: (Float) -> Unit,
|
||||
onFile: (String) -> Unit
|
||||
): DownloadStatus {
|
||||
throw IllegalAccessException("Vanced microG does not have a root downloader")
|
||||
}
|
||||
|
||||
override fun getSavedFilePath(): String {
|
||||
|
|
|
@ -3,8 +3,7 @@ package com.vanced.manager.core.downloader.impl
|
|||
import android.content.Context
|
||||
import com.vanced.manager.core.downloader.api.MusicAPI
|
||||
import com.vanced.manager.core.downloader.base.AppDownloader
|
||||
import com.vanced.manager.core.downloader.util.DownloadStatus
|
||||
import com.vanced.manager.core.downloader.util.getVancedMusicPath
|
||||
import com.vanced.manager.core.downloader.util.getVancedYoutubeMusicPath
|
||||
import com.vanced.manager.core.preferences.holder.managerVariantPref
|
||||
import com.vanced.manager.core.preferences.holder.musicVersionPref
|
||||
import com.vanced.manager.core.util.getLatestOrProvidedAppVersion
|
||||
|
@ -19,12 +18,13 @@ class MusicDownloader(
|
|||
|
||||
override suspend fun download(
|
||||
appVersions: List<String>?,
|
||||
onStatus: (DownloadStatus) -> Unit
|
||||
) {
|
||||
onProgress: (Float) -> Unit,
|
||||
onFile: (String) -> Unit
|
||||
): DownloadStatus {
|
||||
absoluteVersion = getLatestOrProvidedAppVersion(musicVersionPref, appVersions)
|
||||
|
||||
downloadFiles(
|
||||
downloadFiles = arrayOf(
|
||||
val downloadStatus = downloadFiles(
|
||||
files = arrayOf(
|
||||
DownloadFile(
|
||||
call = musicAPI.getFiles(
|
||||
version = absoluteVersion,
|
||||
|
@ -33,28 +33,25 @@ class MusicDownloader(
|
|||
fileName = "music.apk"
|
||||
)
|
||||
),
|
||||
onProgress = { progress ->
|
||||
onStatus(DownloadStatus.Progress(progress))
|
||||
},
|
||||
onFile = { fileName ->
|
||||
onStatus(DownloadStatus.File(fileName))
|
||||
},
|
||||
onSuccess = {
|
||||
onStatus(DownloadStatus.StartInstall)
|
||||
},
|
||||
onError = { error, fileName ->
|
||||
onStatus(
|
||||
DownloadStatus.Error(
|
||||
displayError = "Failed to download $fileName",
|
||||
stacktrace = error
|
||||
)
|
||||
)
|
||||
}
|
||||
onProgress = onProgress,
|
||||
onFile = onFile
|
||||
)
|
||||
if (downloadStatus.isError)
|
||||
return downloadStatus
|
||||
|
||||
return DownloadStatus.Success
|
||||
}
|
||||
|
||||
override suspend fun downloadRoot(
|
||||
appVersions: List<String>?,
|
||||
onProgress: (Float) -> Unit,
|
||||
onFile: (String) -> Unit
|
||||
): DownloadStatus {
|
||||
return DownloadStatus.Success
|
||||
}
|
||||
|
||||
override fun getSavedFilePath(): String {
|
||||
val directory = File(getVancedMusicPath(absoluteVersion, managerVariantPref, context))
|
||||
val directory = File(getVancedYoutubeMusicPath(absoluteVersion, managerVariantPref, context))
|
||||
|
||||
if (!directory.exists())
|
||||
directory.mkdirs()
|
||||
|
|
|
@ -3,7 +3,6 @@ package com.vanced.manager.core.downloader.impl
|
|||
import android.content.Context
|
||||
import com.vanced.manager.core.downloader.api.VancedAPI
|
||||
import com.vanced.manager.core.downloader.base.AppDownloader
|
||||
import com.vanced.manager.core.downloader.util.DownloadStatus
|
||||
import com.vanced.manager.core.downloader.util.getVancedYoutubePath
|
||||
import com.vanced.manager.core.preferences.holder.managerVariantPref
|
||||
import com.vanced.manager.core.preferences.holder.vancedLanguagesPref
|
||||
|
@ -22,8 +21,9 @@ class VancedDownloader(
|
|||
|
||||
override suspend fun download(
|
||||
appVersions: List<String>?,
|
||||
onStatus: (DownloadStatus) -> Unit
|
||||
) {
|
||||
onProgress: (Float) -> Unit,
|
||||
onFile: (String) -> Unit
|
||||
): DownloadStatus {
|
||||
absoluteVersion = getLatestOrProvidedAppVersion(vancedVersionPref, appVersions)
|
||||
|
||||
val files = arrayOf(
|
||||
|
@ -42,26 +42,23 @@ class VancedDownloader(
|
|||
)
|
||||
}
|
||||
|
||||
downloadFiles(
|
||||
downloadFiles = files,
|
||||
onProgress = { progress ->
|
||||
onStatus(DownloadStatus.Progress(progress))
|
||||
},
|
||||
onFile = { fileName ->
|
||||
onStatus(DownloadStatus.File(fileName))
|
||||
},
|
||||
onSuccess = {
|
||||
onStatus(DownloadStatus.StartInstall)
|
||||
},
|
||||
onError = { error, fileName ->
|
||||
onStatus(
|
||||
DownloadStatus.Error(
|
||||
displayError = "Failed to download $fileName",
|
||||
stacktrace = error
|
||||
)
|
||||
)
|
||||
}
|
||||
val downloadStatus = downloadFiles(
|
||||
files = files,
|
||||
onProgress = onProgress,
|
||||
onFile = onFile,
|
||||
)
|
||||
if (downloadStatus.isError)
|
||||
return downloadStatus
|
||||
|
||||
return DownloadStatus.Success
|
||||
}
|
||||
|
||||
override suspend fun downloadRoot(
|
||||
appVersions: List<String>?,
|
||||
onProgress: (Float) -> Unit,
|
||||
onFile: (String) -> Unit
|
||||
): DownloadStatus {
|
||||
return DownloadStatus.Success
|
||||
}
|
||||
|
||||
override fun getSavedFilePath(): String {
|
||||
|
|
|
@ -6,19 +6,34 @@ fun getVancedYoutubePath(
|
|||
version: String,
|
||||
variant: String,
|
||||
context: Context
|
||||
) = context.getExternalFilesDir("vanced_youtube")!!.path + "/$version/$variant"
|
||||
) = context.getExternalFilesDirPath("vanced_youtube") + "/$version/$variant"
|
||||
|
||||
fun getVancedYoutubeMusicPath(
|
||||
version: String,
|
||||
variant: String,
|
||||
context: Context
|
||||
) = context.getExternalFilesDirPath("vanced_music") + "/$version/$variant"
|
||||
|
||||
fun getMicrogPath(
|
||||
context: Context
|
||||
) = context.getExternalFilesDirPath("microg")
|
||||
|
||||
fun getStockYoutubePath(
|
||||
version: String,
|
||||
context: Context
|
||||
) = context.getExternalFilesDir("stock_youtube")!!.path + "/$version"
|
||||
) = context.getExternalFilesDirPath("stock_youtube") + "/$version"
|
||||
|
||||
fun getVancedMusicPath(
|
||||
fun getStockYoutubeMusicPath(
|
||||
version: String,
|
||||
variant: String,
|
||||
context: Context
|
||||
) = context.getExternalFilesDir("vanced_music")!!.path + "/$version/$variant"
|
||||
) = context.getExternalFilesDirPath("stock_youtube_music") + "/$version"
|
||||
|
||||
fun getMicrogPath(
|
||||
context: Context
|
||||
) = context.getExternalFilesDir("microg")!!.path
|
||||
private fun Context.getExternalFilesDirPath(
|
||||
type: String
|
||||
): String {
|
||||
val filesDir = getExternalFilesDir(type)!! //fuck null safety, amirite?
|
||||
if (!filesDir.exists())
|
||||
filesDir.mkdirs()
|
||||
|
||||
return filesDir.path
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package com.vanced.manager.core.downloader.util
|
||||
|
||||
sealed class DownloadStatus {
|
||||
|
||||
object StartInstall : DownloadStatus()
|
||||
|
||||
data class File(val fileName: String) : DownloadStatus()
|
||||
|
||||
data class Progress(val progress: Float) : DownloadStatus()
|
||||
|
||||
data class Error(
|
||||
val displayError: String,
|
||||
val stacktrace: String,
|
||||
) : DownloadStatus()
|
||||
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
package com.vanced.manager.core.installer.base
|
||||
|
||||
import com.vanced.manager.core.installer.util.PMRootResult
|
||||
|
||||
abstract class AppInstaller {
|
||||
|
||||
abstract fun install(appVersions: List<String>?)
|
||||
|
||||
abstract fun installRoot(appVersions: List<String>?)
|
||||
abstract fun installRoot(appVersions: List<String>?): PMRootResult<Nothing>
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ 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.PM
|
||||
import com.vanced.manager.core.installer.util.PMRootResult
|
||||
import java.io.File
|
||||
|
||||
class MicrogInstaller(
|
||||
|
@ -18,7 +19,7 @@ class MicrogInstaller(
|
|||
PM.installApp(musicApk, context)
|
||||
}
|
||||
|
||||
override fun installRoot(appVersions: List<String>?) {
|
||||
override fun installRoot(appVersions: List<String>?): PMRootResult<Nothing> {
|
||||
throw IllegalAccessException("Vanced microG does not have a root installer")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package com.vanced.manager.core.installer.impl
|
||||
|
||||
import android.content.Context
|
||||
import com.vanced.manager.core.downloader.util.getVancedMusicPath
|
||||
import com.vanced.manager.core.downloader.util.getStockYoutubeMusicPath
|
||||
import com.vanced.manager.core.downloader.util.getVancedYoutubeMusicPath
|
||||
import com.vanced.manager.core.installer.base.AppInstaller
|
||||
import com.vanced.manager.core.installer.util.PM
|
||||
import com.vanced.manager.core.installer.util.*
|
||||
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.VANCED_MUSIC_PACKAGE_ROOT
|
||||
import com.vanced.manager.core.util.getLatestOrProvidedAppVersion
|
||||
import java.io.File
|
||||
|
||||
|
@ -18,13 +21,39 @@ class MusicInstaller(
|
|||
) {
|
||||
val absoluteVersion = getLatestOrProvidedAppVersion(musicVersionPref, appVersions)
|
||||
|
||||
val musicApk = File(getVancedMusicPath(absoluteVersion, managerVariantPref, context) + "/music.apk")
|
||||
val musicApk = File(getVancedYoutubeMusicPath(absoluteVersion, managerVariantPref, context) + "/music.apk")
|
||||
|
||||
PM.installApp(musicApk, context)
|
||||
}
|
||||
|
||||
override fun installRoot(appVersions: List<String>?) {
|
||||
override fun installRoot(appVersions: List<String>?): PMRootResult<Nothing> {
|
||||
val absoluteVersion = getLatestOrProvidedAppVersion(vancedVersionPref, appVersions)
|
||||
|
||||
val stockPath = getStockYoutubeMusicPath(absoluteVersion, context) + "/base.apk"
|
||||
val vancedPath = getVancedYoutubeMusicPath(absoluteVersion, "root", context) + "/base.apk"
|
||||
|
||||
val prepareStock = RootPatchHelper.prepareStock(
|
||||
stockPackage = VANCED_MUSIC_PACKAGE_ROOT,
|
||||
stockVersion = absoluteVersion
|
||||
) {
|
||||
PMRoot.installApp(stockPath)
|
||||
}
|
||||
if (prepareStock.isError)
|
||||
return prepareStock
|
||||
|
||||
val patchStock = RootPatchHelper.patchStock(
|
||||
patchPath = vancedPath,
|
||||
stockPackage = VANCED_MUSIC_PACKAGE_ROOT,
|
||||
app = APP_KEY
|
||||
)
|
||||
if (patchStock.isError)
|
||||
return patchStock
|
||||
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val APP_KEY = "youtube_music_vanced"
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
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.*
|
||||
import com.vanced.manager.core.preferences.holder.vancedVersionPref
|
||||
import com.vanced.manager.core.util.Message
|
||||
import com.vanced.manager.core.util.VANCED_YOUTUBE_PACKAGE_ROOT
|
||||
import com.vanced.manager.core.util.getLatestOrProvidedAppVersion
|
||||
import java.io.File
|
||||
|
||||
|
@ -26,8 +25,34 @@ class VancedInstaller(
|
|||
PM.installSplitApp(apks!!, context)
|
||||
}
|
||||
|
||||
override fun installRoot(appVersions: List<String>?) {
|
||||
override fun installRoot(appVersions: List<String>?): PMRootResult<Nothing> {
|
||||
val absoluteVersion = getLatestOrProvidedAppVersion(vancedVersionPref, appVersions)
|
||||
|
||||
val stockApks = File(getStockYoutubePath(absoluteVersion, context))
|
||||
.listFiles()?.map { it.absolutePath }
|
||||
val vancedBaseApk = getVancedYoutubePath(absoluteVersion, "root", context) + "/base.apk"
|
||||
|
||||
val prepareStock = RootPatchHelper.prepareStock(
|
||||
stockPackage = VANCED_YOUTUBE_PACKAGE_ROOT,
|
||||
stockVersion = absoluteVersion,
|
||||
) {
|
||||
PMRoot.installSplitApp(stockApks!!)
|
||||
}
|
||||
if (prepareStock.isError)
|
||||
return prepareStock
|
||||
|
||||
val patchStock = RootPatchHelper.patchStock(
|
||||
patchPath = vancedBaseApk,
|
||||
stockPackage = VANCED_YOUTUBE_PACKAGE_ROOT,
|
||||
app = APP_KEY
|
||||
)
|
||||
if (patchStock.isError)
|
||||
return patchStock
|
||||
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val APP_KEY = "youtube_vanced"
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ import android.os.Build
|
|||
import com.vanced.manager.core.installer.service.AppInstallService
|
||||
import com.vanced.manager.core.installer.service.AppUninstallService
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
private const val byteArraySize = 1024 * 1024 // Because 1,048,576 is not readable
|
||||
|
||||
|
@ -19,7 +18,7 @@ object PM {
|
|||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val session =
|
||||
packageInstaller.openSession(packageInstaller.createSession(sessionParams))
|
||||
writeApkToSession(apk, session)
|
||||
session.writeApk(apk)
|
||||
session.commit(context.installIntentSender)
|
||||
session.close()
|
||||
}
|
||||
|
@ -29,7 +28,7 @@ object PM {
|
|||
val session =
|
||||
packageInstaller.openSession(packageInstaller.createSession(sessionParams))
|
||||
for (apk in apks) {
|
||||
writeApkToSession(apk, session)
|
||||
session.writeApk(apk)
|
||||
}
|
||||
session.commit(context.installIntentSender)
|
||||
session.close()
|
||||
|
@ -41,17 +40,13 @@ object PM {
|
|||
}
|
||||
}
|
||||
|
||||
private fun writeApkToSession(
|
||||
apk: File,
|
||||
session: PackageInstaller.Session
|
||||
) {
|
||||
val inputStream = FileInputStream(apk)
|
||||
val outputStream = session.openWrite(apk.name, 0, apk.length())
|
||||
inputStream.copyTo(outputStream, byteArraySize)
|
||||
session.fsync(outputStream)
|
||||
inputStream.close()
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
private fun PackageInstaller.Session.writeApk(apk: File) {
|
||||
apk.inputStream().use { inputStream ->
|
||||
openWrite(apk.name, 0, apk.length()).use { outputStream ->
|
||||
inputStream.copyTo(outputStream, byteArraySize)
|
||||
fsync(outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val intentFlags
|
||||
|
|
|
@ -10,10 +10,10 @@ import java.io.IOException
|
|||
|
||||
object PMRoot {
|
||||
|
||||
fun installApp(apkPath: String): PMRootStatus<Nothing> {
|
||||
fun installApp(apkPath: String): PMRootResult<Nothing> {
|
||||
val apk = File(apkPath)
|
||||
val tmpApk = copyApkToTemp(apk) { error ->
|
||||
return PMRootStatus.Error(PMRootStatusType.SESSION_FAILED_COPY, error)
|
||||
val tmpApk = copyApkToTemp(apk).getOrElse { exception ->
|
||||
return PMRootResult.Error(PMRootStatus.SESSION_FAILED_COPY, exception.stackTraceToString())
|
||||
}
|
||||
|
||||
val install = Shell.su("pm", "install", "-r", tmpApk.absolutePath).exec()
|
||||
|
@ -22,27 +22,27 @@ object PMRoot {
|
|||
|
||||
if (!install.isSuccess) {
|
||||
val errString = install.errString
|
||||
return PMRootStatus.Error(getEnumForInstallFailed(errString), errString)
|
||||
return PMRootResult.Error(getEnumForInstallFailed(errString), errString)
|
||||
}
|
||||
|
||||
return PMRootStatus.Success()
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
fun installSplitApp(apkPaths: List<String>): PMRootStatus<Nothing> {
|
||||
fun installSplitApp(apkPaths: List<String>): PMRootResult<Nothing> {
|
||||
val installCreate = Shell.su("pm", "install-create", "-r").exec()
|
||||
|
||||
if (!installCreate.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.SESSION_FAILED_CREATE, installCreate.errString)
|
||||
return PMRootResult.Error(PMRootStatus.SESSION_FAILED_CREATE, installCreate.errString)
|
||||
|
||||
val sessionId = installCreate.outString
|
||||
|
||||
if (sessionId.toIntOrNull() == null)
|
||||
return PMRootStatus.Error(PMRootStatusType.SESSION_INVALID_ID, installCreate.errString)
|
||||
return PMRootResult.Error(PMRootStatus.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 tmpApk = copyApkToTemp(apk).getOrElse { exception ->
|
||||
return PMRootResult.Error(PMRootStatus.SESSION_FAILED_COPY, exception.stackTraceToString())
|
||||
}
|
||||
|
||||
val installWrite =
|
||||
|
@ -52,62 +52,82 @@ object PMRoot {
|
|||
tmpApk.delete()
|
||||
|
||||
if (!installWrite.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.SESSION_FAILED_WRITE, installWrite.errString)
|
||||
return PMRootResult.Error(PMRootStatus.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 PMRootResult.Error(getEnumForInstallFailed(errString), errString)
|
||||
}
|
||||
|
||||
return PMRootStatus.Success()
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
fun uninstallApp(pkg: String): PMRootStatus<Nothing> {
|
||||
fun uninstallApp(pkg: String): PMRootResult<Nothing> {
|
||||
val uninstall = Shell.su("pm", "uninstall", pkg).exec()
|
||||
|
||||
if (!uninstall.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.UNINSTALL_FAILED, uninstall.errString)
|
||||
return PMRootResult.Error(PMRootStatus.UNINSTALL_FAILED, uninstall.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
fun setInstallerPackage(targetPkg: String, installerPkg: String): PMRootStatus<Nothing> {
|
||||
fun setInstallerPackage(targetPkg: String, installerPkg: String): PMRootResult<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 PMRootResult.Error(PMRootStatus.ACTION_FAILED_SET_INSTALLER, setInstaller.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
fun forceStopApp(pkg: String): PMRootStatus<Nothing> {
|
||||
fun forceStopApp(pkg: String): PMRootResult<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 PMRootResult.Error(PMRootStatus.ACTION_FAILED_FORCE_STOP_APP, stopApp.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
fun getPackageDir(pkg: String): PMRootStatus<String> {
|
||||
val delimeter = "path: "
|
||||
val dumpsys = Shell.su("dumpsys", "package", pkg, "|", "grep", delimeter).exec()
|
||||
fun getPackageVersionName(pkg: String): PMRootResult<String> {
|
||||
val keyword = "versionName="
|
||||
val dumpsys = Shell.su("dumpsys", "package", pkg, "|", "grep", keyword).exec()
|
||||
|
||||
if (!dumpsys.isSuccess)
|
||||
return PMRootStatus.Error(PMRootStatusType.ACTION_FAILED_GET_PACKAGE_DIR, dumpsys.errString)
|
||||
return PMRootResult.Error(PMRootStatus.ACTION_FAILED_GET_PACKAGE_VERSION_NAME,
|
||||
dumpsys.errString)
|
||||
|
||||
return PMRootStatus.Success(dumpsys.outString.removePrefix(delimeter))
|
||||
return PMRootResult.Success(dumpsys.outString.removePrefix(keyword))
|
||||
}
|
||||
|
||||
fun getPackageVersionCode(pkg: String): PMRootResult<Long> {
|
||||
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,
|
||||
dumpsys.errString)
|
||||
|
||||
return PMRootResult.Success(dumpsys.outString.removePrefix(keyword).substringAfter("minSdk")
|
||||
.toLong())
|
||||
}
|
||||
|
||||
fun getPackageDir(pkg: String): PMRootResult<String> {
|
||||
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 PMRootResult.Success(dumpsys.outString.removePrefix(keyword))
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun copyApkToTemp(
|
||||
apk: File,
|
||||
onError: (String) -> Unit
|
||||
): SuFile {
|
||||
private fun copyApkToTemp(apk: File, ): Result<SuFile> {
|
||||
val tmpPath = "/data/local/tmp/${apk.name}"
|
||||
|
||||
val tmpApk = SuFile(tmpPath).apply {
|
||||
|
@ -120,20 +140,20 @@ private inline fun copyApkToTemp(
|
|||
it.flush()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
onError(e.stackTraceToString())
|
||||
return Result.failure(e)
|
||||
}
|
||||
|
||||
return tmpApk
|
||||
return Result.success(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
|
||||
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
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
package com.vanced.manager.core.installer.util
|
||||
|
||||
enum class PMRootStatusType {
|
||||
enum class PMRootStatus {
|
||||
ACTION_FAILED_SET_INSTALLER,
|
||||
ACTION_FAILED_GET_PACKAGE_DIR,
|
||||
ACTION_FAILED_GET_PACKAGE_VERSION_NAME,
|
||||
ACTION_FAILED_GET_PACKAGE_VERSION_CODE,
|
||||
ACTION_FAILED_FORCE_STOP_APP,
|
||||
|
||||
INSTALL_SUCCESSFUL,
|
||||
|
@ -38,35 +40,20 @@ enum class PMRootStatusType {
|
|||
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>()
|
||||
sealed class PMRootResult<out V> {
|
||||
data class Success<out V>(val value: V? = null) : PMRootResult<V>()
|
||||
data class Error(val error: PMRootStatus, val message: String) : PMRootResult<Nothing>()
|
||||
|
||||
val isError
|
||||
get() = this is Error
|
||||
|
||||
val isSuccess
|
||||
get() = this is Success
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
inline fun <R, T : R> PMRootResult<T>.getOrElse(onError: (PMRootResult.Error) -> R): R? {
|
||||
return when (this) {
|
||||
is PMRootResult.Error -> onError(this)
|
||||
is PMRootResult.Success -> return this.value
|
||||
}
|
||||
}
|
|
@ -13,27 +13,29 @@ object Patcher {
|
|||
|
||||
fun setupScript(
|
||||
app: String,
|
||||
pkg: String,
|
||||
stockPackage: String,
|
||||
stockPath: String,
|
||||
): PMRootStatus<Nothing> {
|
||||
): PMRootResult<Nothing> {
|
||||
val postFsDataScriptPath = getAppPostFsScriptPath(app)
|
||||
val serviceDScriptPath = getAppServiceDScriptPath(app)
|
||||
|
||||
val postFsDataScript = getPostFsDataScript(pkg)
|
||||
val postFsDataScript = getPostFsDataScript(stockPackage)
|
||||
val serviceDScript = getServiceDScript(getAppPatchPath(app), stockPath)
|
||||
|
||||
copyScriptToDestination(postFsDataScriptPath, postFsDataScript) { error ->
|
||||
return PMRootStatus.Error(PMRootStatusType.SCRIPT_FAILED_SETUP_POST_FS, error)
|
||||
}
|
||||
val copyServiceDScript = copyScriptToDestination(postFsDataScript, postFsDataScriptPath)
|
||||
if (copyServiceDScript.isFailure)
|
||||
return PMRootResult.Error(PMRootStatus.SCRIPT_FAILED_SETUP_POST_FS,
|
||||
copyServiceDScript.exceptionOrNull()!!.stackTraceToString())
|
||||
|
||||
copyScriptToDestination(serviceDScriptPath, serviceDScript) { error ->
|
||||
return PMRootStatus.Error(PMRootStatusType.SCRIPT_FAILED_SETUP_SERVICE_D, error)
|
||||
}
|
||||
val copyPostFsDataScript = copyScriptToDestination(serviceDScript, serviceDScriptPath)
|
||||
if (copyPostFsDataScript.isFailure)
|
||||
return PMRootResult.Error(PMRootStatus.SCRIPT_FAILED_SETUP_SERVICE_D,
|
||||
copyPostFsDataScript.exceptionOrNull()!!.stackTraceToString())
|
||||
|
||||
return PMRootStatus.Success()
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
fun movePatchToDataAdb(patchPath: String, app: String): PMRootStatus<Nothing> {
|
||||
fun movePatchToDataAdb(patchPath: String, app: String): PMRootResult<Nothing> {
|
||||
val newPatchPath = getAppPatchPath(app)
|
||||
|
||||
val patchApk = File(patchPath)
|
||||
|
@ -47,38 +49,38 @@ object Patcher {
|
|||
try {
|
||||
patchApk.copyTo(newPatchApk)
|
||||
} catch (e: IOException) {
|
||||
return PMRootStatus.Error(PMRootStatusType.PATCH_FAILED_COPY, e.stackTraceToString())
|
||||
return PMRootResult.Error(PMRootStatus.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)
|
||||
return PMRootResult.Error(PMRootStatus.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 PMRootResult.Error(PMRootStatus.PATCH_FAILED_CHOWN, chown.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
fun chconPatch(app: String): PMRootStatus<Nothing> {
|
||||
fun chconPatch(app: String): PMRootResult<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 PMRootResult.Error(PMRootStatus.PATCH_FAILED_CHCON, chcon.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
return PMRootResult.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()
|
||||
fun linkPatch(app: String, stockPackage: String, stockPath: String): PMRootResult<Nothing> {
|
||||
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 PMRootStatus.Error(PMRootStatusType.LINK_FAILED_UNMOUNT, umount.errString)
|
||||
return PMRootResult.Error(PMRootStatus.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 PMRootResult.Error(PMRootStatus.LINK_FAILED_MOUNT, mount.errString)
|
||||
|
||||
return PMRootStatus.Success()
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
fun destroyPatch(app: String) =
|
||||
|
@ -112,42 +114,41 @@ private fun getServiceDScript(patchPath: String, stockPath: String) =
|
|||
mount -o bind $patchPath $stockPath
|
||||
""".trimIndent()
|
||||
|
||||
private fun getPostFsDataScript(pkg: String) =
|
||||
private fun getPostFsDataScript(stockPackage: String) =
|
||||
"""
|
||||
#!/system/bin/sh
|
||||
while read line; do echo \${'$'}{line} | grep $pkg | awk '{print \${'$'}2}' | xargs umount -l; done< /proc/mounts
|
||||
while read line; do echo \${'$'}{line} | grep $stockPackage | awk '{print \${'$'}2}' | xargs umount -l; done< /proc/mounts
|
||||
""".trimIndent()
|
||||
|
||||
private fun cleanPatchFiles(
|
||||
postFsPath: String,
|
||||
serviceDPath: String,
|
||||
patchPath: String,
|
||||
): PMRootStatus<Nothing> {
|
||||
): PMRootResult<Nothing> {
|
||||
val files = mapOf(
|
||||
postFsPath to PMRootStatusType.SCRIPT_FAILED_DESTROY_POST_FS,
|
||||
serviceDPath to PMRootStatusType.SCRIPT_FAILED_DESTROY_SERVICE_D,
|
||||
patchPath to PMRootStatusType.PATCH_FAILED_DESTROY,
|
||||
postFsPath to PMRootStatus.SCRIPT_FAILED_DESTROY_POST_FS,
|
||||
serviceDPath to PMRootStatus.SCRIPT_FAILED_DESTROY_SERVICE_D,
|
||||
patchPath to PMRootStatus.PATCH_FAILED_DESTROY,
|
||||
)
|
||||
|
||||
for ((filePath, statusType) in files) {
|
||||
for ((filePath, errorStatusType) in files) {
|
||||
try {
|
||||
with(ManagerSuFile(filePath)) {
|
||||
if (exists()) delete()
|
||||
}
|
||||
} catch (e: SUIOException) {
|
||||
return PMRootStatus.Error(statusType, e.stackTraceToString())
|
||||
return PMRootResult.Error(errorStatusType, e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
|
||||
return PMRootStatus.Success()
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
private inline fun copyScriptToDestination(
|
||||
scriptDestination: String,
|
||||
private fun copyScriptToDestination(
|
||||
script: String,
|
||||
onError: (String) -> Unit
|
||||
) {
|
||||
val scriptFile = SuFile(scriptDestination)
|
||||
destination: String,
|
||||
): Result<Nothing?> {
|
||||
val scriptFile = SuFile(destination)
|
||||
.apply {
|
||||
if (!exists()) createNewFile()
|
||||
}
|
||||
|
@ -159,9 +160,11 @@ private inline fun copyScriptToDestination(
|
|||
}
|
||||
val chmod = Shell.su("chmod", "744", scriptFile.absolutePath).exec()
|
||||
if (!chmod.isSuccess) {
|
||||
onError(chmod.errString)
|
||||
return Result.failure(Exception(chmod.errString))
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
onError(e.stackTraceToString())
|
||||
return Result.failure(e)
|
||||
}
|
||||
|
||||
return Result.success(null)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package com.vanced.manager.core.installer.util
|
||||
|
||||
object RootPatchHelper {
|
||||
|
||||
fun cleanPatches(app: String): PMRootResult<Nothing> {
|
||||
val cleanOldPatches = Patcher.destroyOldPatch(app)
|
||||
if (cleanOldPatches.isError)
|
||||
return cleanOldPatches
|
||||
|
||||
val cleanPatches = Patcher.destroyPatch(app)
|
||||
if (cleanOldPatches.isError)
|
||||
return cleanPatches
|
||||
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
inline fun prepareStock(
|
||||
stockPackage: String,
|
||||
stockVersion: String,
|
||||
install: () -> PMRootResult<Nothing>
|
||||
): PMRootResult<Nothing> {
|
||||
val stockYoutubeVersion = PMRoot.getPackageVersionName(stockPackage)
|
||||
.getOrElse { null }
|
||||
if (stockYoutubeVersion != stockVersion) {
|
||||
val uninstallStock = PMRoot.uninstallApp(stockPackage)
|
||||
if (uninstallStock.isError)
|
||||
return uninstallStock
|
||||
|
||||
val installStock = install()
|
||||
if (installStock.isError)
|
||||
return installStock
|
||||
}
|
||||
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
fun patchStock(
|
||||
patchPath: String,
|
||||
stockPackage: String,
|
||||
app: String
|
||||
): PMRootResult<Nothing> {
|
||||
val movePatch = Patcher.movePatchToDataAdb(patchPath, app)
|
||||
if (movePatch.isError)
|
||||
return movePatch
|
||||
|
||||
val chconPatch = Patcher.chconPatch(app)
|
||||
if (chconPatch.isError)
|
||||
return chconPatch
|
||||
|
||||
val stockPackageDir = PMRoot.getPackageDir(stockPackage)
|
||||
.getOrElse { error -> return error }!!
|
||||
|
||||
val setupScript = Patcher.setupScript(app, stockPackage, stockPackageDir)
|
||||
if (setupScript is PMRootResult.Error)
|
||||
return setupScript
|
||||
|
||||
val linkPatch = Patcher.linkPatch(app, stockPackage, stockPackageDir)
|
||||
if (linkPatch is PMRootResult.Error)
|
||||
return linkPatch
|
||||
|
||||
return PMRootResult.Success()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.vanced.manager.core.io
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
inline fun ResponseBody.writeFile(
|
||||
filePath: String,
|
||||
onProgress: (Float) -> Unit
|
||||
) {
|
||||
byteStream().use { inputStream ->
|
||||
FileOutputStream(filePath).use { outputStream ->
|
||||
val totalBytes = contentLength()
|
||||
inputStream.copyTo(outputStream, 8192) { bytes ->
|
||||
onProgress((bytes * 100 / totalBytes).toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun InputStream.copyTo(
|
||||
outputStream: OutputStream,
|
||||
bufferSize: Int,
|
||||
onProgress: (Long) -> Unit
|
||||
) {
|
||||
val buffer = ByteArray(bufferSize)
|
||||
var bytesCopied: Long = 0
|
||||
var bytes = read(buffer)
|
||||
while (bytes >= 0) {
|
||||
outputStream.write(buffer, 0, bytes)
|
||||
bytesCopied += bytes
|
||||
bytes = read(buffer)
|
||||
onProgress(bytesCopied)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.vanced.manager.core.util
|
||||
|
||||
const val VANCED_YOUTUBE_PACKAGE = "com.vanced.android.youtube"
|
||||
const val VANCED_YOUTUBE_PACKAGE_ROOT = "com.google.android.youtube"
|
||||
|
||||
const val VANCED_MUSIC_PACKAGE = "com.vanced.android.apps.youtube.music"
|
||||
const val VANCED_MUSIC_PACKAGE_ROOT = "com.google.android.apps.youtube.music"
|
|
@ -0,0 +1,14 @@
|
|||
package com.vanced.manager.core.util
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.Executor
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
fun CoroutineContext.asExecutor(): Executor = object : Executor {
|
||||
private val scope = CoroutineScope(this@asExecutor)
|
||||
|
||||
override fun execute(command: Runnable) {
|
||||
scope.launch { command.run() }
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package com.vanced.manager.core.util
|
||||
|
||||
sealed class Message {
|
||||
data class Success(val message: String) : Message()
|
||||
data class Warning(val message: String) : Message()
|
||||
data class Error(val message: String) : Message()
|
||||
}
|
||||
|
||||
val downloadLogs = mutableListOf<Message>()
|
|
@ -1,37 +0,0 @@
|
|||
package com.vanced.manager.core.util
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
|
||||
data class LogContent(
|
||||
val body: AnnotatedString,
|
||||
)
|
||||
|
||||
val logs = mutableListOf<LogContent>()
|
||||
|
||||
fun log(tag: String, message: String) {
|
||||
Log.i(tag, message)
|
||||
logs.add(
|
||||
LogContent(
|
||||
body = buildAnnotatedString {
|
||||
withStyle(
|
||||
SpanStyle(
|
||||
color = Color(0xFF2E73FF),
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
) {
|
||||
append("$tag:")
|
||||
}
|
||||
append("")
|
||||
withStyle(SpanStyle(color = Color.Magenta)) {
|
||||
append(message)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.vanced.manager.core.util
|
||||
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
val Shell.Result.outString
|
||||
get() = out.joinToString("\n")
|
||||
|
@ -9,5 +11,10 @@ val Shell.Result.errString
|
|||
get() = err.joinToString("\n")
|
||||
|
||||
val isMagiskInstalled
|
||||
get() = Shell.su("magisk", "-c").exec().isSuccess
|
||||
get() = Shell.rootAccess() && Shell.su("magisk", "-c").exec().isSuccess
|
||||
|
||||
suspend fun Shell.Job.await() =
|
||||
suspendCoroutine<Shell.Result> { continuation ->
|
||||
submit(/*continuation.context.asExecutor(),*/ continuation::resume)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
package com.vanced.manager.core.util
|
||||
|
||||
const val VANCED_PACKAGE = ""
|
|
@ -16,7 +16,7 @@ import com.vanced.manager.ui.component.topappbar.ManagerTopAppBar
|
|||
import com.vanced.manager.ui.resources.managerString
|
||||
import com.vanced.manager.ui.util.Screen
|
||||
import com.vanced.manager.ui.widget.layout.managerCategory
|
||||
import com.vanced.manager.ui.widget.screens.settings.*
|
||||
import com.vanced.manager.ui.widget.settings.*
|
||||
|
||||
@ExperimentalMaterial3Api
|
||||
@Composable
|
||||
|
|
|
@ -7,13 +7,15 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vanced.manager.core.downloader.base.AppDownloader
|
||||
import com.vanced.manager.core.downloader.impl.MicrogDownloader
|
||||
import com.vanced.manager.core.downloader.impl.MusicDownloader
|
||||
import com.vanced.manager.core.downloader.impl.VancedDownloader
|
||||
import com.vanced.manager.core.downloader.util.DownloadStatus
|
||||
import com.vanced.manager.core.installer.impl.MicrogInstaller
|
||||
import com.vanced.manager.core.installer.impl.MusicInstaller
|
||||
import com.vanced.manager.core.installer.impl.VancedInstaller
|
||||
import com.vanced.manager.core.installer.util.PMRootResult
|
||||
import com.vanced.manager.core.preferences.holder.managerVariantPref
|
||||
import com.vanced.manager.network.util.MICROG_NAME
|
||||
import com.vanced.manager.network.util.MUSIC_NAME
|
||||
import com.vanced.manager.network.util.VANCED_NAME
|
||||
|
@ -30,6 +32,9 @@ class InstallViewModel(
|
|||
private val microgInstaller: MicrogInstaller,
|
||||
) : ViewModel() {
|
||||
|
||||
private val isRoot
|
||||
get() = managerVariantPref == "root"
|
||||
|
||||
sealed class Log {
|
||||
data class Info(val infoText: String) : Log()
|
||||
data class Success(val successText: String) : Log()
|
||||
|
@ -72,16 +77,6 @@ class InstallViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
logs.clear()
|
||||
status = Status.Idle
|
||||
|
@ -93,21 +88,29 @@ class InstallViewModel(
|
|||
) {
|
||||
val downloader = getDownloader(appName)
|
||||
|
||||
downloader.download(appVersions) { downloadStatus ->
|
||||
when (downloadStatus) {
|
||||
is DownloadStatus.File -> log(Log.Info("Downloading ${downloadStatus.fileName}"))
|
||||
is DownloadStatus.Error -> log(
|
||||
Log.Error(
|
||||
displayText = downloadStatus.displayError,
|
||||
stacktrace = downloadStatus.stacktrace
|
||||
)
|
||||
)
|
||||
is DownloadStatus.Progress -> status =
|
||||
Status.Progress(downloadStatus.progress / 100)
|
||||
is DownloadStatus.StartInstall -> {
|
||||
log(Log.Success("Successfully downloaded $appName"))
|
||||
installApp(appName, appVersions)
|
||||
}
|
||||
val onProgress: (Float) -> Unit = { progress ->
|
||||
status = Status.Progress(progress / 100)
|
||||
}
|
||||
val onFile: (String) -> Unit = { file ->
|
||||
log(Log.Info("Downloading $file"))
|
||||
}
|
||||
|
||||
val download =
|
||||
if (isRoot)
|
||||
downloader.downloadRoot(appVersions, onProgress, onFile)
|
||||
else
|
||||
downloader.download(appVersions, onProgress, onFile)
|
||||
|
||||
when (download) {
|
||||
is AppDownloader.DownloadStatus.Success -> {
|
||||
log(Log.Success("Successfully downloaded $appName"))
|
||||
installApp(appName, appVersions)
|
||||
}
|
||||
is AppDownloader.DownloadStatus.Error -> {
|
||||
log(Log.Error(
|
||||
displayText = "Failed to download ${download.fileName}",
|
||||
stacktrace = download.error
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +123,20 @@ class InstallViewModel(
|
|||
|
||||
status = Status.Installing
|
||||
|
||||
installer.install(appVersions)
|
||||
if (isRoot) {
|
||||
when (val installStatus = installer.installRoot(appVersions)) {
|
||||
is PMRootResult.Success -> {
|
||||
status = Status.Installed
|
||||
log(Log.Success("Successfully installed"))
|
||||
}
|
||||
is PMRootResult.Error -> {
|
||||
status = Status.Failure
|
||||
log(Log.Error("Failed to install app", installStatus.message))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
installer.install(appVersions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDownloader(
|
||||
|
|
|
@ -10,7 +10,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vanced.manager.core.installer.util.uninstallPackage
|
||||
import com.vanced.manager.core.installer.util.PM
|
||||
import com.vanced.manager.core.preferences.holder.managerVariantPref
|
||||
import com.vanced.manager.domain.model.App
|
||||
import com.vanced.manager.network.util.MICROG_NAME
|
||||
|
@ -80,7 +80,7 @@ class MainViewModel(
|
|||
fun uninstallApp(
|
||||
appPackage: String,
|
||||
) {
|
||||
uninstallPackage(appPackage, app)
|
||||
PM.uninstallPackage(appPackage, app)
|
||||
}
|
||||
|
||||
private suspend fun fetchData(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.vanced.manager.ui.widget.screens.settings
|
||||
package com.vanced.manager.ui.widget.settings
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.vanced.manager.ui.widget.screens.settings
|
||||
package com.vanced.manager.ui.widget.settings
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vanced.manager.R
|
|
@ -1,4 +1,4 @@
|
|||
package com.vanced.manager.ui.widget.screens.settings
|
||||
package com.vanced.manager.ui.widget.settings
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
|
@ -1,9 +1,10 @@
|
|||
package com.vanced.manager.ui.widget.screens.settings
|
||||
package com.vanced.manager.ui.widget.settings
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import com.vanced.manager.R
|
||||
import com.vanced.manager.core.preferences.RadioButtonPreference
|
||||
import com.vanced.manager.core.preferences.holder.managerVariantPref
|
||||
import com.vanced.manager.core.util.isMagiskInstalled
|
||||
import com.vanced.manager.ui.component.preference.SingleSelectDialogPreference
|
||||
import com.vanced.manager.ui.resources.managerString
|
||||
|
||||
|
@ -36,6 +37,9 @@ fun SettingsManagerVariantItem() {
|
|||
selectedKey = managerVariantPref
|
||||
},
|
||||
onItemClick = {
|
||||
if (it == "root" && !isMagiskInstalled)
|
||||
return@SingleSelectDialogPreference
|
||||
|
||||
selectedKey = it
|
||||
},
|
||||
onSave = {
|
|
@ -1,4 +1,4 @@
|
|||
package com.vanced.manager.ui.widget.screens.settings
|
||||
package com.vanced.manager.ui.widget.settings
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vanced.manager.core.preferences.managerBooleanPreference
|
|
@ -1,4 +1,4 @@
|
|||
package com.vanced.manager.ui.widget.screens.settings
|
||||
package com.vanced.manager.ui.widget.settings
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import com.vanced.manager.R
|
Loading…
Reference in New Issue