Better stub hiding experience

This commit is contained in:
topjohnwu 2020-10-17 03:40:43 -07:00
parent aaaaa3d044
commit 2e4dc91b96
16 changed files with 162 additions and 134 deletions

View File

@ -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()
}
} }

View File

@ -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 {

View File

@ -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()
}
} }

View File

@ -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

View File

@ -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

View File

@ -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())
}

View File

@ -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 {

View File

@ -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

View File

@ -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()
}
} }
} }
} }

View File

@ -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)
} }

View File

@ -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))

View File

@ -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()
} }
} }

View File

@ -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))
}
} }

View File

@ -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))

View File

@ -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

View File

@ -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>