diff --git a/app/build.gradle b/app/build.gradle index 5ad08724..b5db7590 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,15 +7,15 @@ apply plugin: 'com.google.firebase.firebase-perf' apply plugin: 'com.google.firebase.crashlytics' android { - compileSdkVersion 30 - buildToolsVersion "30.0.1" + compileSdkVersion 29 + buildToolsVersion "29.0.3" defaultConfig { applicationId "com.vanced.manager" minSdkVersion 21 - targetSdkVersion 30 - versionCode 12 - versionName "1.2.0 (Niko)" + targetSdkVersion 29 + versionCode 13 + versionName "1.2.1 (Niko)" vectorDrawables.useSupportLibrary = true } @@ -85,6 +85,7 @@ dependencies { implementation 'com.github.kittinunf.fuel:fuel-coroutines:2.2.3' implementation 'com.github.kittinunf.fuel:fuel-json:2.2.3' implementation 'com.github.topjohnwu.libsu:core:3.0.1' + implementation 'com.github.topjohnwu.libsu:io:3.0.1' implementation 'com.google.firebase:firebase-messaging:20.2.4' implementation 'com.google.firebase:firebase-perf:19.0.8' implementation 'com.mindorks.android:prdownloader:0.6.0' diff --git a/app/src/main/java/com/vanced/manager/core/App.kt b/app/src/main/java/com/vanced/manager/core/App.kt index 38450be3..57c308c0 100644 --- a/app/src/main/java/com/vanced/manager/core/App.kt +++ b/app/src/main/java/com/vanced/manager/core/App.kt @@ -14,15 +14,15 @@ class App: Application() { super.onCreate() PRDownloader.initialize(this) - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + //if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { Crowdin.init(this, CrowdinConfig.Builder() .withDistributionHash("36c51aed3180a4f43073d28j4s6") .withNetworkType(NetworkType.WIFI) .build() ) - } - + //} + } /* diff --git a/app/src/main/java/com/vanced/manager/core/downloader/VancedDownloadService.kt b/app/src/main/java/com/vanced/manager/core/downloader/VancedDownloadService.kt index 537e29da..65175c3f 100644 --- a/app/src/main/java/com/vanced/manager/core/downloader/VancedDownloadService.kt +++ b/app/src/main/java/com/vanced/manager/core/downloader/VancedDownloadService.kt @@ -57,10 +57,14 @@ class VancedDownloadService: Service() { Build.SUPPORTED_ABIS.contains("arm64-v8a") -> "arm64_v8a" else -> "armeabi_v7a" } + val themePath = "$installUrl/apks/v$vancedVer/$variant/Theme" val url = when (type) { "arch" -> "$installUrl/apks/v$vancedVer/$variant/Arch/split_config.$arch.apk" - "theme" -> "$installUrl/apks/v$vancedVer/$variant/Theme/$theme.apk" + "hash" -> "$themePath/hash.json" + "theme" -> "$themePath/$theme.apk" + "stock" -> "$themePath/stock.apk" + "dpi" -> "$themePath/dpi.apk" "lang" -> "$installUrl/apks/v$vancedVer/$variant/Language/split_config.${lang?.get(count)}.apk" else -> throw NotImplementedError("This type of APK is NOT valid. What the hell did you even do?") } @@ -80,7 +84,10 @@ class VancedDownloadService: Service() { override fun onDownloadComplete() { when (type) { "arch" -> downloadSplits("theme") - "theme" -> downloadSplits("lang") + "theme" -> if(variant=="root") downloadSplits("stock") else downloadSplits("lang") + "stock" -> downloadSplits("dpi") + "dpi" -> downloadSplits("hash") + "hash" -> downloadSplits("lang") "lang" -> { count++ if (count < lang?.count()!!) @@ -88,6 +95,7 @@ class VancedDownloadService: Service() { else prepareInstall(variant!!) } + } } diff --git a/app/src/main/java/com/vanced/manager/core/installer/RootSplitInstallerService.kt b/app/src/main/java/com/vanced/manager/core/installer/RootSplitInstallerService.kt index 0f91b214..a16c909b 100644 --- a/app/src/main/java/com/vanced/manager/core/installer/RootSplitInstallerService.kt +++ b/app/src/main/java/com/vanced/manager/core/installer/RootSplitInstallerService.kt @@ -2,36 +2,104 @@ package com.vanced.manager.core.installer import android.app.Service import android.content.Intent +import android.content.pm.PackageInfo import android.os.Build import android.os.IBinder import android.util.Log import androidx.annotation.Nullable import androidx.annotation.WorkerThread +import androidx.core.net.toUri import androidx.localbroadcastmanager.content.LocalBroadcastManager +import com.beust.klaxon.JsonObject +import com.beust.klaxon.Parser import com.topjohnwu.superuser.Shell +import com.topjohnwu.superuser.io.SuFile +import com.vanced.manager.BuildConfig import com.vanced.manager.ui.fragments.HomeFragment import com.vanced.manager.utils.AppUtils.sendFailure import com.vanced.manager.utils.FileInfo +import com.vanced.manager.utils.InternetTools.getJsonInt +import com.vanced.manager.utils.PackageHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import java.io.ByteArrayOutputStream import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.security.MessageDigest import java.text.SimpleDateFormat import java.util.* import java.util.regex.Pattern import kotlin.collections.ArrayList + class RootSplitInstallerService: Service() { + private var hashjson: FileInfo? = null + private var vancedVersionCode: Int = 0 + val yPkg = "com.google.android.youtube" + private val localBroadcastManager by lazy { LocalBroadcastManager.getInstance(this) } + + suspend fun getVer() + { + vancedVersionCode = getJsonInt("vanced.json","versionCode", application) + } + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + + Shell.enableVerboseLogging = BuildConfig.DEBUG + Shell.setDefaultBuilder( + Shell.Builder.create() + .setFlags(Shell.FLAG_REDIRECT_STDERR) + .setTimeout(10) + ) + + runBlocking { getVer() } Shell.getShell { - CoroutineScope(Dispatchers.IO).launch { + var job = CoroutineScope(Dispatchers.IO).launch{ val apkFilesPath = getExternalFilesDir("apks")?.path val fileInfoList = apkFilesPath?.let { it1 -> getFileInfoList(it1) } if (fileInfoList != null) { - installSplitApkFiles(fileInfoList) + var modApk: FileInfo? = null + for (fil in fileInfoList) + { + if(fil.name == "dark.apk" || fil.name == "black.apk") + { + modApk = fil + } + if(fil.name == "hash.json") + { + hashjson = fil + } + } + if (modApk != null && hashjson != null) { + + val hash = parseJson(modApk.name.split(".")[0], hashjson!!) + if(overwriteBase(modApk, fileInfoList, vancedVersionCode,hash)) + { + with(localBroadcastManager) { + sendBroadcast(Intent(HomeFragment.REFRESH_HOME)) + sendBroadcast(Intent(HomeFragment.VANCED_INSTALLED)) + } + } + else + { + sendFailure(listOf("Install Failed").toMutableList(), applicationContext) + } + } + else + { + sendFailure(listOf("modApk Is Null Missing (dark.apk/black.apk) In apks Folder").toMutableList(), applicationContext) + } + //installSplitApkFiles(fileInfoList) + } + else + { + sendFailure(listOf("Files are missing, Failed Download?").toMutableList(), applicationContext) } } @@ -40,8 +108,16 @@ class RootSplitInstallerService: Service() { return START_NOT_STICKY } + private fun parseJson(s: String, hashjson: FileInfo): String + { + val jsonData = SuFile.open(hashjson.file!!.absolutePath).readText(Charsets.UTF_8) + val jsonObject = Parser.default().parse(StringBuilder(jsonData)) as JsonObject + return jsonObject.string(s)!! + } + + @WorkerThread - private fun installSplitApkFiles(apkFiles: ArrayList) { + private fun installSplitApkFiles(apkFiles: ArrayList) : Boolean { var sessionId: Int? Log.d("AppLog", "installing split apk files:$apkFiles") run { @@ -52,31 +128,32 @@ class RootSplitInstallerService: Service() { sessionId = Integer.parseInt(sessionIdMatcher.group(1)!!) } apkFiles.forEach { apkFile -> - Log.d("AppLog", "installing APK : ${apkFile.name} ${apkFile.fileSize} ") - val command = arrayOf("su", "-c", "pm", "install-write", "-S", "${apkFile.fileSize}", "$sessionId", apkFile.name) - val process: Process = Runtime.getRuntime().exec(command) - val inputPipe = apkFile.getInputStream() - try { - process.outputStream.use { outputStream -> inputPipe.copyTo(outputStream) } - } catch (e: Exception) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - process.destroyForcibly() - else - process.destroy() + if(apkFile.name != "black.apk" && apkFile.name != "dark.apk" && apkFile.name != "hash.json") + { + Log.d("AppLog", "installing APK : ${apkFile.name} ${apkFile.fileSize} ") + val command = arrayOf("su", "-c", "pm", "install-write", "-S", "${apkFile.fileSize}", "$sessionId", apkFile.name) + val process: Process = Runtime.getRuntime().exec(command) + val inputPipe = apkFile.getInputStream() + try { + process.outputStream.use { outputStream -> inputPipe.copyTo(outputStream) } + } catch (e: Exception) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + process.destroyForcibly() + else + process.destroy() - throw RuntimeException(e) + throw RuntimeException(e) + } + process.waitFor() } - process.waitFor() } Log.d("AppLog", "committing...") val installResult = Shell.su("pm install-commit $sessionId").exec() if (installResult.isSuccess) { - with(localBroadcastManager) { - sendBroadcast(Intent(HomeFragment.REFRESH_HOME)) - sendBroadcast(Intent(HomeFragment.VANCED_INSTALLED)) - } + return true } else sendFailure(installResult.out, this) + return false } private fun SimpleDateFormat.tryParse(str: String) = try { @@ -128,4 +205,227 @@ class RootSplitInstallerService: Service() { override fun onBind(intent: Intent?): IBinder? { return null } + + //install Vanced + private fun overwriteBase(apkFile: FileInfo,baseApkFiles: ArrayList, versionCode: Int,hash: String): Boolean + { + if(checkVersion(versionCode,baseApkFiles)) + { + val path = getVPath() + apkFile.file?.let { + val apath = it.absolutePath + if(sha256Check(apath,hash)) + { + if(path?.let { it1 -> moveAPK(apath, it1) }!!) + { + val fpath = SuFile.open(path).parent!! + return chConV(path) + } + } + else + { + sendFailure(listOf("Download Went Corrupt, Retry or clear VanM Data").toMutableList(), applicationContext) + + } + + } + } + return false + } + //do sha256 check on downloaded apk + private fun sha256Check(apath: String, hash: String): Boolean { + val sfile = SuFile.open(apath) + return checkSHA256(hash,sfile) + } + + //check version and perform action based on result + private fun checkVersion(versionCode: Int, baseApkFiles: ArrayList): Boolean { + val path = getVPath() + if (path != null) { + if(path.contains("/data/app/")) + { + when(getPkgVerCode(yPkg)?.let { compareVersion(it,versionCode) }) + { + 1 -> {return fixHigherVer(baseApkFiles) } + -1 -> {return fixLowerVer(baseApkFiles) } + } + return true + } + else + { + return fixNoInstall(baseApkFiles) + } + } + return fixNoInstall(baseApkFiles) + } + + + + private fun getPkgVerCode(pkg: String): Int? { + val pm = packageManager + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + pm.getPackageInfo(pkg, 0)?.longVersionCode?.and(0xFFFFFFFF)?.toInt() + else + pm.getPackageInfo(pkg, 0)?.versionCode + + } + + private fun getPkgInfo(pkg: String): PackageInfo? + { + return try { + val m = packageManager + val info = m.getPackageInfo(pkg, 0) + info + } catch (e:Exception) { + null + } + } + + private fun compareVersion(pkgVerCode: Int, versionCode: Int): Int + { + return if(pkgVerCode > versionCode) + 1 + else if (pkgVerCode < versionCode) + -1 + else + 0 + } + + //uninstall current update and install base that works with patch + private fun fixHigherVer(apkFiles: ArrayList) : Boolean { + + if(PackageHelper.uninstallApk(yPkg, applicationContext)) + { + return installSplitApkFiles(apkFiles) + } + with(localBroadcastManager) {sendFailure(listOf("Failed Uninstall Of Installed Version, Try Manually").toMutableList(), applicationContext)} + return false + } + + //install newer stock youtube + private fun fixLowerVer(apkFiles: ArrayList): Boolean { + return installSplitApkFiles(apkFiles) + } + + //install stock youtube since no install was found + private fun fixNoInstall(baseApkFiles: ArrayList): Boolean { + return installSplitApkFiles(baseApkFiles) + } + + //set chcon to apk_data_file + private fun chConV(path: String): Boolean { + val response = Shell.su("chcon -R u:object_r:apk_data_file:s0 $path").exec() + //val response = Shell.su("chcon -R u:object_r:system_file:s0 $path").exec() + return if(response.isSuccess) { + true + } else { + sendFailure(response.out, applicationContext) + false + } + } + + //move patch to data/app + private fun moveAPK(apkFile: String, path: String) : Boolean { + + val apkinF = SuFile.open(apkFile) + val apkoutF = SuFile.open(path) + + if(apkinF.exists()) + { + try { + Shell.su("am force-stop $yPkg").exec() + + //Shell.su("rm -r SuFile.open(path).parent") + + copy(apkinF,apkoutF) + Shell.su("chmod 644 $path").exec().isSuccess + return if(Shell.su("chown system:system $path").exec().isSuccess) { + true + } else { + sendFailure(listOf("Failed To Chown, Try Again").toMutableList(), applicationContext) + false + } + + } + catch (e: IOException) + { + sendFailure(listOf("${e.message}").toMutableList(), applicationContext) + return false + } + } + else { + sendFailure(listOf("Input File Missing").toMutableList(), applicationContext) + return false + } + } + + + @Throws(IOException::class) + fun copy(src: File?, dst: File?) { + val cmd = Shell.su("mv ${src!!.absolutePath} ${dst!!.absolutePath}").exec().isSuccess + Log.d("ZLog", cmd.toString()) + } + + //get path of the installed youtube + private fun getVPath(): String? { + return try { + val p = getPkgInfo(yPkg) + p?.applicationInfo?.sourceDir + } catch (e: Exception) { + null + } + + } + + private fun checkSHA256(sha256: String, updateFile: File?): Boolean { + try { + // get the raw file data of the photo + val mInputPFD = contentResolver.openFileDescriptor(updateFile!!.toUri() , "r") + val mContentFileDescriptor = mInputPFD!!.fileDescriptor + val fIS = FileInputStream(mContentFileDescriptor) + val mGraphicBuffer = ByteArrayOutputStream() + val buf = ByteArray(1024) + while (true) { + val readNum = fIS.read(buf) + if (readNum == -1) break + mGraphicBuffer.write(buf, 0, readNum) + } + + // Generate the checksum + val sum = generateChecksum(mGraphicBuffer) + + return sum == sha256 + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + + @Throws(IOException::class) + private fun generateChecksum(data: ByteArrayOutputStream): String { + try { + val digest: MessageDigest = MessageDigest.getInstance("SHA-256") + val hash: ByteArray = digest.digest(data.toByteArray()) + return printableHexString(hash) + } catch (e: Exception) { + e.printStackTrace() + } + + return "" + } + + + private fun printableHexString(data: ByteArray): String { + // Create Hex String + val hexString: StringBuilder = StringBuilder() + for (aMessageDigest:Byte in data) { + var h: String = Integer.toHexString(0xFF and aMessageDigest.toInt()) + while (h.length < 2) + h = "0$h" + hexString.append(h) + } + return hexString.toString() + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/ui/MainActivity.kt b/app/src/main/java/com/vanced/manager/ui/MainActivity.kt index 50663408..40161b1f 100644 --- a/app/src/main/java/com/vanced/manager/ui/MainActivity.kt +++ b/app/src/main/java/com/vanced/manager/ui/MainActivity.kt @@ -124,8 +124,6 @@ class MainActivity : AppCompatActivity() { } !prefs.getBoolean("statement", true) -> DialogContainer.statementFalse(this) variant == "root" -> { - if (showRootDialog) - DialogContainer.showRootDialog(this) if (PackageHelper.getPackageVersionName( "com.google.android.youtube", diff --git a/app/src/main/java/com/vanced/manager/ui/dialogs/DialogContainer.kt b/app/src/main/java/com/vanced/manager/ui/dialogs/DialogContainer.kt index 01b0d370..00a0afb1 100644 --- a/app/src/main/java/com/vanced/manager/ui/dialogs/DialogContainer.kt +++ b/app/src/main/java/com/vanced/manager/ui/dialogs/DialogContainer.kt @@ -73,28 +73,6 @@ object DialogContainer { } } - fun showRootDialog(activity: Activity) { - MaterialAlertDialogBuilder(activity).apply { - setTitle(activity.getString(R.string.hold_on)) - setMessage(activity.getString(R.string.disable_signature)) - setNeutralButton(activity.getString(R.string.button_dismiss)) { dialog, _ -> - dialog.dismiss() - } - setPositiveButton(activity.getString(R.string.guide)) { _, _ -> - openUrl( - "https://lmgtfy.com/?q=andnixsh+apk+verification+disable", - R.color.Twitter, - activity - ) - } - setCancelable(false) - create() - show() - } - PreferenceManager.getDefaultSharedPreferences(activity).edit() - .putBoolean("show_root_dialog", false).apply() - } - //Easter Egg fun statementFalse(context: Context) { MaterialAlertDialogBuilder(context).apply { diff --git a/app/src/main/java/com/vanced/manager/ui/fragments/ManagerChangelogFragment.kt b/app/src/main/java/com/vanced/manager/ui/fragments/ManagerChangelogFragment.kt index 644c8d7c..083c4e7a 100644 --- a/app/src/main/java/com/vanced/manager/ui/fragments/ManagerChangelogFragment.kt +++ b/app/src/main/java/com/vanced/manager/ui/fragments/ManagerChangelogFragment.kt @@ -24,7 +24,7 @@ class ManagerChangelogFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) CoroutineScope(Dispatchers.Main).launch { - val changelog = InternetTools.getObjectFromJson("https://x1nto.github.io/VancedFiles/manager.json", "changelog") + val changelog = InternetTools.getObjectFromJson("https://ytvanced.github.io/VancedBackend/manager.json", "changelog") view.findViewById(R.id.manager_changelog).text = changelog } } diff --git a/app/src/main/java/com/vanced/manager/ui/viewmodels/HomeViewModel.kt b/app/src/main/java/com/vanced/manager/ui/viewmodels/HomeViewModel.kt index d8884e03..41f41cfc 100644 --- a/app/src/main/java/com/vanced/manager/ui/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/com/vanced/manager/ui/viewmodels/HomeViewModel.kt @@ -65,7 +65,7 @@ open class HomeViewModel(application: Application): AndroidViewModel(application fun fetchData() { CoroutineScope(Dispatchers.IO).launch { fetching.set(true) - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) + //if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) Crowdin.forceUpdate(getApplication()) vancedVersion.set(getJsonString("vanced.json", "version", getApplication())) microgVersion.set(getJsonString("microg.json", "version", getApplication())) diff --git a/app/src/main/java/com/vanced/manager/utils/InternetTools.kt b/app/src/main/java/com/vanced/manager/utils/InternetTools.kt index 81e2c76a..36b24d66 100644 --- a/app/src/main/java/com/vanced/manager/utils/InternetTools.kt +++ b/app/src/main/java/com/vanced/manager/utils/InternetTools.kt @@ -67,7 +67,7 @@ object InternetTools { suspend fun isUpdateAvailable(): Boolean { val result = try { - JsonHelper.getJson("https://x1nto.github.io/VancedFiles/manager.json").int("versionCode") ?: 0 + JsonHelper.getJson("https://ytvanced.github.io/VancedBackend/manager.json").int("versionCode") ?: 0 } catch (e: Exception) { 0 } diff --git a/app/src/main/java/com/vanced/manager/utils/PackageHelper.kt b/app/src/main/java/com/vanced/manager/utils/PackageHelper.kt index d6ec1f3c..ecc096f6 100644 --- a/app/src/main/java/com/vanced/manager/utils/PackageHelper.kt +++ b/app/src/main/java/com/vanced/manager/utils/PackageHelper.kt @@ -2,9 +2,11 @@ package com.vanced.manager.utils import android.app.Activity import android.app.PendingIntent +import android.content.Context import android.content.Intent import android.content.pm.PackageManager import com.vanced.manager.core.installer.AppUninstallerService +import java.lang.Exception object PackageHelper { @@ -30,4 +32,19 @@ object PackageHelper { val pendingIntent = PendingIntent.getService(activity.applicationContext, 0, callbackIntent, 0) activity.packageManager.packageInstaller.uninstall(pkg, pendingIntent.intentSender) } + + fun uninstallApk(pkg: String, applicationContext: Context): Boolean { + val callbackIntent = Intent(applicationContext, AppUninstallerService::class.java) + callbackIntent.putExtra("pkg", pkg) + val pendingIntent = PendingIntent.getService(applicationContext, 0, callbackIntent, 0) + try { + applicationContext.packageManager.packageInstaller.uninstall(pkg, pendingIntent.intentSender) + return true + } + catch (e: Exception) + { + e.printStackTrace() + return false; + } + } } \ No newline at end of file