Merge pull request #121 from ostajic/dev
New Root Install Method & Log code & Version Bump
This commit is contained in:
commit
96a9950644
|
@ -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'
|
||||
|
|
|
@ -14,14 +14,14 @@ 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()
|
||||
)
|
||||
}
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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!!)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<FileInfo>) {
|
||||
private fun installSplitApkFiles(apkFiles: ArrayList<FileInfo>) : Boolean {
|
||||
var sessionId: Int?
|
||||
Log.d("AppLog", "installing split apk files:$apkFiles")
|
||||
run {
|
||||
|
@ -52,6 +128,8 @@ class RootSplitInstallerService: Service() {
|
|||
sessionId = Integer.parseInt(sessionIdMatcher.group(1)!!)
|
||||
}
|
||||
apkFiles.forEach { apkFile ->
|
||||
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)
|
||||
|
@ -68,15 +146,14 @@ class RootSplitInstallerService: Service() {
|
|||
}
|
||||
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<FileInfo>, 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<FileInfo>): 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<FileInfo>) : 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<FileInfo>): Boolean {
|
||||
return installSplitApkFiles(apkFiles)
|
||||
}
|
||||
|
||||
//install stock youtube since no install was found
|
||||
private fun fixNoInstall(baseApkFiles: ArrayList<FileInfo>): 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()
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<TextView>(R.id.manager_changelog).text = changelog
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue