Better stub hiding experience
This commit is contained in:
parent
aaaaa3d044
commit
2e4dc91b96
@ -1,5 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.core
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.util.Xml
|
import android.util.Xml
|
||||||
@ -9,19 +10,16 @@ import com.topjohnwu.magisk.BuildConfig
|
|||||||
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
||||||
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
||||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||||
import com.topjohnwu.magisk.core.utils.defaultLocale
|
|
||||||
import com.topjohnwu.magisk.core.utils.refreshLocale
|
import com.topjohnwu.magisk.core.utils.refreshLocale
|
||||||
import com.topjohnwu.magisk.data.preference.PreferenceModel
|
import com.topjohnwu.magisk.data.preference.PreferenceModel
|
||||||
import com.topjohnwu.magisk.data.repository.DBConfig
|
import com.topjohnwu.magisk.data.repository.DBConfig
|
||||||
import com.topjohnwu.magisk.di.Protected
|
import com.topjohnwu.magisk.di.Protected
|
||||||
import com.topjohnwu.magisk.ktx.get
|
|
||||||
import com.topjohnwu.magisk.ktx.inject
|
import com.topjohnwu.magisk.ktx.inject
|
||||||
import com.topjohnwu.magisk.ui.theme.Theme
|
import com.topjohnwu.magisk.ui.theme.Theme
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
|
||||||
import com.topjohnwu.superuser.io.SuFileInputStream
|
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
object Config : PreferenceModel, DBConfig {
|
object Config : PreferenceModel, DBConfig {
|
||||||
|
|
||||||
@ -29,6 +27,15 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
override val settingsDao: SettingsDao by inject()
|
override val settingsDao: SettingsDao by inject()
|
||||||
override val context: Context by inject(Protected)
|
override val context: Context by inject(Protected)
|
||||||
|
|
||||||
|
@get:SuppressLint("ApplySharedPref")
|
||||||
|
val prefsFile: File get() {
|
||||||
|
// Flush prefs to disk
|
||||||
|
prefs.edit().apply {
|
||||||
|
remove(Key.ASKED_HOME)
|
||||||
|
}.commit()
|
||||||
|
return File("${context.filesDir.parent}/shared_prefs", "${fileName}.xml")
|
||||||
|
}
|
||||||
|
|
||||||
object Key {
|
object Key {
|
||||||
// db configs
|
// db configs
|
||||||
const val ROOT_ACCESS = "root_access"
|
const val ROOT_ACCESS = "root_access"
|
||||||
@ -119,7 +126,7 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
||||||
|
|
||||||
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
|
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
|
||||||
var suAutoReponse by preferenceStrInt(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT)
|
var suAutoResponse by preferenceStrInt(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT)
|
||||||
var suNotification by preferenceStrInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST)
|
var suNotification by preferenceStrInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST)
|
||||||
var updateChannel by preferenceStrInt(Key.UPDATE_CHANNEL, defaultChannel)
|
var updateChannel by preferenceStrInt(Key.UPDATE_CHANNEL, defaultChannel)
|
||||||
|
|
||||||
@ -150,8 +157,12 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
|
|
||||||
private const val SU_FINGERPRINT = "su_fingerprint"
|
private const val SU_FINGERPRINT = "su_fingerprint"
|
||||||
|
|
||||||
fun initialize() {
|
fun load(pkg: String) {
|
||||||
prefs.edit { parsePrefs() }
|
try {
|
||||||
|
context.contentResolver.openInputStream(Provider.PREFS_URI(pkg))?.use {
|
||||||
|
prefs.edit { parsePrefs(it) }
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {}
|
||||||
|
|
||||||
prefs.edit {
|
prefs.edit {
|
||||||
// Settings migration
|
// Settings migration
|
||||||
@ -173,10 +184,8 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SharedPreferences.Editor.parsePrefs() {
|
private fun SharedPreferences.Editor.parsePrefs(input: InputStream) {
|
||||||
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
|
runCatching {
|
||||||
if (config.exists()) runCatching {
|
|
||||||
val input = SuFileInputStream(config)
|
|
||||||
val parser = Xml.newPullParser()
|
val parser = Xml.newPullParser()
|
||||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||||
parser.setInput(input, "UTF-8")
|
parser.setInput(input, "UTF-8")
|
||||||
@ -220,21 +229,6 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
else -> parser.next()
|
else -> parser.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.delete()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun export() {
|
|
||||||
// Flush prefs to disk
|
|
||||||
prefs.edit().apply {
|
|
||||||
remove(Key.ASKED_HOME)
|
|
||||||
}.commit()
|
|
||||||
val context = get<Context>(Protected)
|
|
||||||
val xml = File(
|
|
||||||
"${context.filesDir.parent}/shared_prefs",
|
|
||||||
"${context.packageName}_preferences.xml"
|
|
||||||
)
|
|
||||||
Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,6 @@ object Const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object Url {
|
object Url {
|
||||||
const val ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip"
|
|
||||||
const val PATREON_URL = "https://www.patreon.com/topjohnwu"
|
const val PATREON_URL = "https://www.patreon.com/topjohnwu"
|
||||||
const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
|
const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
|
||||||
|
|
||||||
@ -58,11 +57,9 @@ object Const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object Key {
|
object Key {
|
||||||
// others
|
|
||||||
const val LINK_KEY = "Link"
|
|
||||||
const val ETAG_KEY = "ETag"
|
|
||||||
// intents
|
// intents
|
||||||
const val OPEN_SECTION = "section"
|
const val OPEN_SECTION = "section"
|
||||||
|
const val HIDDEN_PKG = "hidden_pkg"
|
||||||
}
|
}
|
||||||
|
|
||||||
object Value {
|
object Value {
|
||||||
|
@ -2,9 +2,13 @@ package com.topjohnwu.magisk.core
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.ProviderInfo
|
import android.content.pm.ProviderInfo
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.ParcelFileDescriptor
|
||||||
|
import android.os.ParcelFileDescriptor.MODE_READ_ONLY
|
||||||
import com.topjohnwu.magisk.FileProvider
|
import com.topjohnwu.magisk.FileProvider
|
||||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
open class Provider : FileProvider() {
|
open class Provider : FileProvider() {
|
||||||
|
|
||||||
@ -16,4 +20,20 @@ open class Provider : FileProvider() {
|
|||||||
SuCallbackHandler(context!!, method, extras)
|
SuCallbackHandler(context!!, method, extras)
|
||||||
return Bundle.EMPTY
|
return Bundle.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
|
||||||
|
return when (uri.encodedPath ?: return null) {
|
||||||
|
"/apk_file" -> ParcelFileDescriptor.open(File(context!!.packageCodePath), MODE_READ_ONLY)
|
||||||
|
"/prefs_file" -> ParcelFileDescriptor.open(Config.prefsFile, MODE_READ_ONLY)
|
||||||
|
else -> super.openFile(uri, mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun APK_URI(pkg: String) =
|
||||||
|
Uri.Builder().scheme("content").authority("$pkg.provider").path("apk_file").build()
|
||||||
|
|
||||||
|
fun PREFS_URI(pkg: String) =
|
||||||
|
Uri.Builder().scheme("content").authority("$pkg.provider").path("prefs_file").build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ package com.topjohnwu.magisk.core
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.data.network.RawServices
|
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||||
import com.topjohnwu.magisk.ktx.get
|
import com.topjohnwu.magisk.ktx.get
|
||||||
import com.topjohnwu.magisk.ui.MainActivity
|
import com.topjohnwu.magisk.ui.MainActivity
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
@ -29,18 +29,18 @@ open class SplashActivity : Activity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRepackage() {
|
private fun handleRepackage(pkg: String?) {
|
||||||
val pkg = Config.suManager
|
if (packageName != APPLICATION_ID) {
|
||||||
if (Config.suManager.isNotEmpty() && packageName == BuildConfig.APPLICATION_ID) {
|
|
||||||
Config.suManager = ""
|
|
||||||
Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec()
|
|
||||||
}
|
|
||||||
if (pkg == packageName) {
|
|
||||||
runCatching {
|
runCatching {
|
||||||
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
// Hidden, remove com.topjohnwu.magisk if exist as it could be malware
|
||||||
packageManager.getApplicationInfo(BuildConfig.APPLICATION_ID, 0)
|
packageManager.getApplicationInfo(APPLICATION_ID, 0)
|
||||||
Shell.su("(pm uninstall ${BuildConfig.APPLICATION_ID})& >/dev/null 2>&1").exec()
|
Shell.su("(pm uninstall $APPLICATION_ID)& >/dev/null 2>&1").exec()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (Config.suManager.isNotEmpty())
|
||||||
|
Config.suManager = ""
|
||||||
|
pkg ?: return
|
||||||
|
Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,14 +48,16 @@ open class SplashActivity : Activity() {
|
|||||||
// Pre-initialize root shell
|
// Pre-initialize root shell
|
||||||
Shell.getShell()
|
Shell.getShell()
|
||||||
|
|
||||||
Config.initialize()
|
val hiddenPackage = intent.getStringExtra(Const.Key.HIDDEN_PKG)
|
||||||
handleRepackage()
|
|
||||||
|
Config.load(hiddenPackage ?: APPLICATION_ID)
|
||||||
|
handleRepackage(hiddenPackage)
|
||||||
Notifications.setup(this)
|
Notifications.setup(this)
|
||||||
UpdateCheckService.schedule(this)
|
UpdateCheckService.schedule(this)
|
||||||
Shortcuts.setupDynamic(this)
|
Shortcuts.setupDynamic(this)
|
||||||
|
|
||||||
// Pre-fetch network stuffs
|
// Pre-fetch network services
|
||||||
get<RawServices>()
|
get<NetworkService>()
|
||||||
|
|
||||||
DONE = true
|
DONE = true
|
||||||
|
|
||||||
|
@ -69,8 +69,8 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
|
|||||||
// -- Download logic
|
// -- Download logic
|
||||||
|
|
||||||
private suspend fun Subject.startDownload() {
|
private suspend fun Subject.startDownload() {
|
||||||
val skip = this is Subject.Magisk && file.checkSum("MD5", magisk.md5)
|
val skipDl = this is Subject.Magisk && file.checkSum("MD5", magisk.md5)
|
||||||
if (!skip) {
|
if (!skipDl) {
|
||||||
val stream = service.fetchFile(url).toProgressStream(this)
|
val stream = service.fetchFile(url).toProgressStream(this)
|
||||||
when (this) {
|
when (this) {
|
||||||
is Subject.Module -> // Download and process on-the-fly
|
is Subject.Module -> // Download and process on-the-fly
|
||||||
|
@ -5,20 +5,16 @@ import androidx.core.net.toFile
|
|||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.DynAPK
|
import com.topjohnwu.magisk.DynAPK
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Config
|
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.download.Action.APK.Restore
|
|
||||||
import com.topjohnwu.magisk.core.download.Action.APK.Upgrade
|
|
||||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
import com.topjohnwu.magisk.core.tasks.PatchAPK
|
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||||
import com.topjohnwu.magisk.ktx.relaunchApp
|
import com.topjohnwu.magisk.ktx.relaunchApp
|
||||||
import com.topjohnwu.magisk.ktx.writeTo
|
import com.topjohnwu.magisk.ktx.writeTo
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
private fun Context.patch(apk: File) {
|
private fun Context.patch(apk: File) {
|
||||||
val patched = File(apk.parent, "patched.apk")
|
val patched = File(apk.parent, "patched.apk")
|
||||||
PatchAPK.patch(this, apk.path, patched.path, packageName, applicationInfo.nonLocalizedLabel)
|
HideAPK.patch(this, apk.path, patched.path, packageName, applicationInfo.nonLocalizedLabel)
|
||||||
apk.delete()
|
apk.delete()
|
||||||
patched.renameTo(apk)
|
patched.renameTo(apk)
|
||||||
}
|
}
|
||||||
@ -31,7 +27,7 @@ private fun BaseDownloader.notifyHide(id: Int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun BaseDownloader.upgrade(subject: Subject.Manager) {
|
suspend fun BaseDownloader.handleAPK(subject: Subject.Manager) {
|
||||||
val apk = subject.file.toFile()
|
val apk = subject.file.toFile()
|
||||||
val id = subject.notifyID()
|
val id = subject.notifyID()
|
||||||
if (isRunningAsStub) {
|
if (isRunningAsStub) {
|
||||||
@ -53,20 +49,3 @@ private suspend fun BaseDownloader.upgrade(subject: Subject.Manager) {
|
|||||||
patch(apk)
|
patch(apk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun BaseDownloader.restore(apk: File, id: Int) {
|
|
||||||
update(id) {
|
|
||||||
it.setProgress(0, 0, true)
|
|
||||||
.setProgress(0, 0, true)
|
|
||||||
.setContentTitle(getString(R.string.restore_img_msg))
|
|
||||||
.setContentText("")
|
|
||||||
}
|
|
||||||
Config.export()
|
|
||||||
Shell.su("pm install $apk && pm uninstall $packageName").exec()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun BaseDownloader.handleAPK(subject: Subject.Manager) =
|
|
||||||
when (subject.action) {
|
|
||||||
is Upgrade -> upgrade(subject)
|
|
||||||
is Restore -> restore(subject.file.toFile(), subject.notifyID())
|
|
||||||
}
|
|
||||||
|
@ -40,16 +40,12 @@ sealed class Subject : Parcelable {
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
class Manager(
|
class Manager(
|
||||||
override val action: Action.APK,
|
|
||||||
private val app: ManagerJson = Info.remote.app,
|
private val app: ManagerJson = Info.remote.app,
|
||||||
val stub: StubJson = Info.remote.stub
|
val stub: StubJson = Info.remote.stub
|
||||||
) : Subject() {
|
) : Subject() {
|
||||||
|
override val action get() = Action.Download
|
||||||
override val title: String
|
override val title: String get() = "MagiskManager-${app.version}(${app.versionCode})"
|
||||||
get() = "MagiskManager-${app.version}(${app.versionCode})"
|
override val url: String get() = app.link
|
||||||
|
|
||||||
override val url: String
|
|
||||||
get() = app.link
|
|
||||||
|
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
override val file by lazy {
|
override val file by lazy {
|
||||||
|
@ -36,7 +36,7 @@ class SuRequestHandler(
|
|||||||
if (policy.packageName == BuildConfig.APPLICATION_ID)
|
if (policy.packageName == BuildConfig.APPLICATION_ID)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
when (Config.suAutoReponse) {
|
when (Config.suAutoResponse) {
|
||||||
Config.Value.SU_AUTO_DENY -> {
|
Config.Value.SU_AUTO_DENY -> {
|
||||||
respond(SuPolicy.DENY, 0)
|
respond(SuPolicy.DENY, 0)
|
||||||
return false
|
return false
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package com.topjohnwu.magisk.core.tasks
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
|
import android.app.ProgressDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
|
||||||
|
import com.topjohnwu.magisk.DynAPK
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.*
|
||||||
import com.topjohnwu.magisk.core.Const
|
|
||||||
import com.topjohnwu.magisk.core.Info
|
|
||||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
|
||||||
import com.topjohnwu.magisk.core.utils.AXML
|
import com.topjohnwu.magisk.core.utils.AXML
|
||||||
import com.topjohnwu.magisk.core.utils.Keygen
|
import com.topjohnwu.magisk.core.utils.Keygen
|
||||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||||
import com.topjohnwu.magisk.ktx.get
|
import com.topjohnwu.magisk.ktx.inject
|
||||||
import com.topjohnwu.magisk.ktx.writeTo
|
import com.topjohnwu.magisk.ktx.writeTo
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
|
||||||
import com.topjohnwu.signing.JarMap
|
import com.topjohnwu.signing.JarMap
|
||||||
import com.topjohnwu.signing.SignApk
|
import com.topjohnwu.signing.SignApk
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
@ -28,18 +28,20 @@ import java.io.FileOutputStream
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
|
||||||
object PatchAPK {
|
object HideAPK {
|
||||||
|
|
||||||
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
|
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
|
||||||
private const val ALPHADOTS = "$ALPHA....."
|
private const val ALPHADOTS = "$ALPHA....."
|
||||||
|
|
||||||
private const val APP_ID = "com.topjohnwu.magisk"
|
|
||||||
private const val APP_NAME = "Magisk Manager"
|
private const val APP_NAME = "Magisk Manager"
|
||||||
|
|
||||||
// Some arbitrary limit
|
// Some arbitrary limit
|
||||||
const val MAX_LABEL_LENGTH = 32
|
const val MAX_LABEL_LENGTH = 32
|
||||||
|
|
||||||
private fun genPackageName(): CharSequence {
|
private val svc: NetworkService by inject()
|
||||||
|
private val Context.APK_URI get() = Provider.APK_URI(packageName)
|
||||||
|
private val Context.PREFS_URI get() = Provider.PREFS_URI(packageName)
|
||||||
|
|
||||||
|
private fun genPackageName(): String {
|
||||||
val random = SecureRandom()
|
val random = SecureRandom()
|
||||||
val len = 5 + random.nextInt(15)
|
val len = 5 + random.nextInt(15)
|
||||||
val builder = StringBuilder(len)
|
val builder = StringBuilder(len)
|
||||||
@ -59,20 +61,20 @@ object PatchAPK {
|
|||||||
val idx = random.nextInt(len - 1)
|
val idx = random.nextInt(len - 1)
|
||||||
builder[idx] = '.'
|
builder[idx] = '.'
|
||||||
}
|
}
|
||||||
return builder
|
return builder.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun patch(
|
fun patch(
|
||||||
context: Context,
|
context: Context,
|
||||||
apk: String, out: String,
|
apk: String, out: String,
|
||||||
pkg: CharSequence, label: CharSequence
|
pkg: String, label: CharSequence
|
||||||
): Boolean {
|
): Boolean {
|
||||||
try {
|
try {
|
||||||
val jar = JarMap.open(apk)
|
val jar = JarMap.open(apk)
|
||||||
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
||||||
val xml = AXML(jar.getRawData(je))
|
val xml = AXML(jar.getRawData(je))
|
||||||
|
|
||||||
if (!xml.findAndPatch(APP_ID to pkg.toString(), APP_NAME to label.toString()))
|
if (!xml.findAndPatch(APPLICATION_ID to pkg, APP_NAME to label.toString()))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// Write apk changes
|
// Write apk changes
|
||||||
@ -91,7 +93,6 @@ object PatchAPK {
|
|||||||
val dlStub = !isRunningAsStub && SDK_INT >= 28 && Const.Version.atLeast_20_2()
|
val dlStub = !isRunningAsStub && SDK_INT >= 28 && Const.Version.atLeast_20_2()
|
||||||
val src = if (dlStub) {
|
val src = if (dlStub) {
|
||||||
val stub = File(context.cacheDir, "stub.apk")
|
val stub = File(context.cacheDir, "stub.apk")
|
||||||
val svc = get<NetworkService>()
|
|
||||||
try {
|
try {
|
||||||
svc.fetchFile(Info.remote.stub.link).byteStream().use {
|
svc.fetchFile(Info.remote.stub.link).byteStream().use {
|
||||||
it.writeTo(stub)
|
it.writeTo(stub)
|
||||||
@ -117,23 +118,73 @@ object PatchAPK {
|
|||||||
if (!Shell.su("adb_pm_install $repack").exec().isSuccess)
|
if (!Shell.su("adb_pm_install $repack").exec().isSuccess)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
Config.suManager = pkg.toString()
|
context.apply {
|
||||||
Config.export()
|
val intent = packageManager.getLaunchIntentForPackage(pkg) ?: return false
|
||||||
Shell.su("pm uninstall $APP_ID").submit()
|
Config.suManager = pkg
|
||||||
|
grantUriPermission(pkg, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
grantUriPermission(pkg, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hideManager(context: Context, label: String) {
|
@Suppress("DEPRECATION")
|
||||||
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
fun hide(context: Context, label: String) {
|
||||||
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
val dialog = ProgressDialog.show(context, context.getString(R.string.hide_manager_title), "", true)
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = withContext(Dispatchers.IO) {
|
||||||
patchAndHide(context, label)
|
patchAndHide(context, label)
|
||||||
}
|
}
|
||||||
if (!result)
|
if (!result) {
|
||||||
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
||||||
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun downloadAndRestore(context: Context): Boolean {
|
||||||
|
val apk = if (isRunningAsStub) {
|
||||||
|
DynAPK.current(context)
|
||||||
|
} else {
|
||||||
|
File(context.cacheDir, "manager.apk").also { apk ->
|
||||||
|
try {
|
||||||
|
svc.fetchFile(Info.remote.app.link).byteStream().use {
|
||||||
|
it.writeTo(apk)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Shell.su("adb_pm_install $apk").exec().isSuccess)
|
||||||
|
return false
|
||||||
|
|
||||||
|
context.apply {
|
||||||
|
val intent = packageManager.getLaunchIntentForPackage(APPLICATION_ID) ?: return false
|
||||||
|
Config.suManager = ""
|
||||||
|
grantUriPermission(APPLICATION_ID, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
grantUriPermission(APPLICATION_ID, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
intent.putExtra(Const.Key.HIDDEN_PKG, packageName)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
fun restore(context: Context) {
|
||||||
|
val dialog = ProgressDialog.show(context, context.getString(R.string.restore_img_msg), "", true)
|
||||||
|
GlobalScope.launch {
|
||||||
|
val result = withContext(Dispatchers.IO) {
|
||||||
|
downloadAndRestore(context)
|
||||||
|
}
|
||||||
|
if (!result) {
|
||||||
|
Utils.toast(R.string.restore_manager_fail_toast, Toast.LENGTH_LONG)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,16 +11,14 @@ interface PreferenceModel {
|
|||||||
|
|
||||||
val fileName: String
|
val fileName: String
|
||||||
get() = "${context.packageName}_preferences"
|
get() = "${context.packageName}_preferences"
|
||||||
val commitPrefs: Boolean
|
|
||||||
get() = false
|
|
||||||
val prefs: SharedPreferences
|
val prefs: SharedPreferences
|
||||||
get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
|
get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
fun preferenceStrInt(
|
fun preferenceStrInt(
|
||||||
name: String,
|
name: String,
|
||||||
default: Int,
|
default: Int,
|
||||||
writeDefault: Boolean = false,
|
commit: Boolean = false
|
||||||
commit: Boolean = commitPrefs
|
|
||||||
) = object: ReadWriteProperty<PreferenceModel, Int> {
|
) = object: ReadWriteProperty<PreferenceModel, Int> {
|
||||||
val base = StringProperty(name, default.toString(), commit)
|
val base = StringProperty(name, default.toString(), commit)
|
||||||
override fun getValue(thisRef: PreferenceModel, property: KProperty<*>): Int =
|
override fun getValue(thisRef: PreferenceModel, property: KProperty<*>): Int =
|
||||||
@ -33,37 +31,37 @@ interface PreferenceModel {
|
|||||||
fun preference(
|
fun preference(
|
||||||
name: String,
|
name: String,
|
||||||
default: Boolean,
|
default: Boolean,
|
||||||
commit: Boolean = commitPrefs
|
commit: Boolean = false
|
||||||
) = BooleanProperty(name, default, commit)
|
) = BooleanProperty(name, default, commit)
|
||||||
|
|
||||||
fun preference(
|
fun preference(
|
||||||
name: String,
|
name: String,
|
||||||
default: Float,
|
default: Float,
|
||||||
commit: Boolean = commitPrefs
|
commit: Boolean = false
|
||||||
) = FloatProperty(name, default, commit)
|
) = FloatProperty(name, default, commit)
|
||||||
|
|
||||||
fun preference(
|
fun preference(
|
||||||
name: String,
|
name: String,
|
||||||
default: Int,
|
default: Int,
|
||||||
commit: Boolean = commitPrefs
|
commit: Boolean = false
|
||||||
) = IntProperty(name, default, commit)
|
) = IntProperty(name, default, commit)
|
||||||
|
|
||||||
fun preference(
|
fun preference(
|
||||||
name: String,
|
name: String,
|
||||||
default: Long,
|
default: Long,
|
||||||
commit: Boolean = commitPrefs
|
commit: Boolean = false
|
||||||
) = LongProperty(name, default, commit)
|
) = LongProperty(name, default, commit)
|
||||||
|
|
||||||
fun preference(
|
fun preference(
|
||||||
name: String,
|
name: String,
|
||||||
default: String,
|
default: String,
|
||||||
commit: Boolean = commitPrefs
|
commit: Boolean = false
|
||||||
) = StringProperty(name, default, commit)
|
) = StringProperty(name, default, commit)
|
||||||
|
|
||||||
fun preference(
|
fun preference(
|
||||||
name: String,
|
name: String,
|
||||||
default: Set<String>,
|
default: Set<String>,
|
||||||
commit: Boolean = commitPrefs
|
commit: Boolean = false
|
||||||
) = StringSetProperty(name, default, commit)
|
) = StringSetProperty(name, default, commit)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package com.topjohnwu.magisk.events.dialog
|
|||||||
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.download.Action
|
|
||||||
import com.topjohnwu.magisk.core.download.DownloadService
|
import com.topjohnwu.magisk.core.download.DownloadService
|
||||||
import com.topjohnwu.magisk.core.download.Subject
|
import com.topjohnwu.magisk.core.download.Subject
|
||||||
import com.topjohnwu.magisk.ktx.res
|
import com.topjohnwu.magisk.ktx.res
|
||||||
@ -16,7 +15,7 @@ class ManagerInstallDialog : DialogEvent() {
|
|||||||
|
|
||||||
override fun build(dialog: MagiskDialog) {
|
override fun build(dialog: MagiskDialog) {
|
||||||
with(dialog) {
|
with(dialog) {
|
||||||
val subject = Subject.Manager(Action.APK.Upgrade)
|
val subject = Subject.Manager()
|
||||||
|
|
||||||
applyTitle(R.string.repo_install_title.res(R.string.app_name.res()))
|
applyTitle(R.string.repo_install_title.res(R.string.app_name.res()))
|
||||||
applyMessage(R.string.repo_install_msg.res(subject.title))
|
applyMessage(R.string.repo_install_msg.res(subject.title))
|
||||||
|
@ -11,7 +11,7 @@ import com.topjohnwu.magisk.core.Config
|
|||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.UpdateCheckService
|
import com.topjohnwu.magisk.core.UpdateCheckService
|
||||||
import com.topjohnwu.magisk.core.tasks.PatchAPK
|
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||||
import com.topjohnwu.magisk.core.utils.availableLocales
|
import com.topjohnwu.magisk.core.utils.availableLocales
|
||||||
@ -90,7 +90,7 @@ object Hide : BaseSettingsItem.Input() {
|
|||||||
set(value) = set(value, field, { field = it }, BR.result, BR.error)
|
set(value) = set(value, field, { field = it }, BR.result, BR.error)
|
||||||
|
|
||||||
val maxLength
|
val maxLength
|
||||||
get() = PatchAPK.MAX_LABEL_LENGTH
|
get() = HideAPK.MAX_LABEL_LENGTH
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
val isError
|
val isError
|
||||||
@ -288,9 +288,9 @@ object AutomaticResponse : BaseSettingsItem.Selector() {
|
|||||||
override val entryRes = R.array.auto_response
|
override val entryRes = R.array.auto_response
|
||||||
override val entryValRes = R.array.value_array
|
override val entryValRes = R.array.value_array
|
||||||
|
|
||||||
override var value = Config.suAutoReponse
|
override var value = Config.suAutoResponse
|
||||||
set(value) = setV(value, field, { field = it }) {
|
set(value) = setV(value, field, { field = it }) {
|
||||||
Config.suAutoReponse = entryValues[it].toInt()
|
Config.suAutoResponse = entryValues[it].toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,11 +15,8 @@ import com.topjohnwu.magisk.arch.diffListOf
|
|||||||
import com.topjohnwu.magisk.arch.itemBindingOf
|
import com.topjohnwu.magisk.arch.itemBindingOf
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.download.Action
|
|
||||||
import com.topjohnwu.magisk.core.download.DownloadService
|
|
||||||
import com.topjohnwu.magisk.core.download.Subject
|
|
||||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
import com.topjohnwu.magisk.core.tasks.PatchAPK
|
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||||
import com.topjohnwu.magisk.data.database.RepoDao
|
import com.topjohnwu.magisk.data.database.RepoDao
|
||||||
import com.topjohnwu.magisk.events.AddHomeIconEvent
|
import com.topjohnwu.magisk.events.AddHomeIconEvent
|
||||||
import com.topjohnwu.magisk.events.RecreateEvent
|
import com.topjohnwu.magisk.events.RecreateEvent
|
||||||
@ -104,7 +101,7 @@ class SettingsViewModel(
|
|||||||
is Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().publish()
|
is Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().publish()
|
||||||
is ClearRepoCache -> clearRepoCache()
|
is ClearRepoCache -> clearRepoCache()
|
||||||
is SystemlessHosts -> createHosts()
|
is SystemlessHosts -> createHosts()
|
||||||
is Restore -> restoreManager()
|
is Restore -> HideAPK.restore(view.context)
|
||||||
is AddShortcut -> AddHomeIconEvent().publish()
|
is AddShortcut -> AddHomeIconEvent().publish()
|
||||||
else -> callback()
|
else -> callback()
|
||||||
}
|
}
|
||||||
@ -112,7 +109,7 @@ class SettingsViewModel(
|
|||||||
override fun onItemChanged(view: View, item: BaseSettingsItem) = when (item) {
|
override fun onItemChanged(view: View, item: BaseSettingsItem) = when (item) {
|
||||||
is Language -> RecreateEvent().publish()
|
is Language -> RecreateEvent().publish()
|
||||||
is UpdateChannel -> openUrlIfNecessary(view)
|
is UpdateChannel -> openUrlIfNecessary(view)
|
||||||
is Hide -> PatchAPK.hideManager(view.context, item.value)
|
is Hide -> HideAPK.hide(view.context, item.value)
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,9 +139,4 @@ class SettingsViewModel(
|
|||||||
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreManager() {
|
|
||||||
DownloadService.start(get(), Subject.Manager(Action.APK.Restore))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import com.topjohnwu.magisk.core.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
|
|||||||
import com.topjohnwu.magisk.core.Const.ID.UPDATE_NOTIFICATION_CHANNEL
|
import com.topjohnwu.magisk.core.Const.ID.UPDATE_NOTIFICATION_CHANNEL
|
||||||
import com.topjohnwu.magisk.core.SplashActivity
|
import com.topjohnwu.magisk.core.SplashActivity
|
||||||
import com.topjohnwu.magisk.core.cmp
|
import com.topjohnwu.magisk.core.cmp
|
||||||
import com.topjohnwu.magisk.core.download.Action
|
|
||||||
import com.topjohnwu.magisk.core.download.DownloadService
|
import com.topjohnwu.magisk.core.download.DownloadService
|
||||||
import com.topjohnwu.magisk.core.download.Subject
|
import com.topjohnwu.magisk.core.download.Subject
|
||||||
import com.topjohnwu.magisk.core.intent
|
import com.topjohnwu.magisk.core.intent
|
||||||
@ -70,7 +69,7 @@ object Notifications {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun managerUpdate(context: Context) {
|
fun managerUpdate(context: Context) {
|
||||||
val intent = DownloadService.pendingIntent(context, Subject.Manager(Action.APK.Upgrade))
|
val intent = DownloadService.pendingIntent(context, Subject.Manager())
|
||||||
|
|
||||||
val builder = updateBuilder(context)
|
val builder = updateBuilder(context)
|
||||||
.setContentTitle(context.getString(R.string.manager_update_title))
|
.setContentTitle(context.getString(R.string.manager_update_title))
|
||||||
|
@ -90,7 +90,7 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
adb_pm_install() {
|
adb_pm_install() {
|
||||||
local tmp=/data/local/tmp/patched.apk
|
local tmp=/data/local/tmp/temp.apk
|
||||||
cp -f "$1" $tmp
|
cp -f "$1" $tmp
|
||||||
chmod 644 $tmp
|
chmod 644 $tmp
|
||||||
su 2000 -c pm install $tmp || pm install $tmp
|
su 2000 -c pm install $tmp || pm install $tmp
|
||||||
|
@ -217,7 +217,8 @@
|
|||||||
<string name="done">Done!</string>
|
<string name="done">Done!</string>
|
||||||
<string name="failure">Failed</string>
|
<string name="failure">Failed</string>
|
||||||
<string name="hide_manager_title">Hiding Magisk Manager…</string>
|
<string name="hide_manager_title">Hiding Magisk Manager…</string>
|
||||||
<string name="hide_manager_fail_toast">Hide Magisk Manager failed.</string>
|
<string name="hide_manager_fail_toast">Hide Magisk Manager failed</string>
|
||||||
|
<string name="restore_manager_fail_toast">Restoring Magisk Manager failed</string>
|
||||||
<string name="open_link_failed_toast">No application found to open the link</string>
|
<string name="open_link_failed_toast">No application found to open the link</string>
|
||||||
<string name="complete_uninstall">Complete Uninstall</string>
|
<string name="complete_uninstall">Complete Uninstall</string>
|
||||||
<string name="restore_img">Restore Images</string>
|
<string name="restore_img">Restore Images</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user