You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
Merge remote-tracking branch 'john/master' into feature/redesign
# Conflicts: # app/build.gradle # app/src/main/java/com/topjohnwu/magisk/ClassMap.kt # app/src/main/java/com/topjohnwu/magisk/Info.kt # app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt # app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt # app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt # app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
class CachedValue<T>(private val factory: () -> T) : Lazy<T> {
|
||||
|
||||
private var _val : T? = null
|
||||
|
||||
override val value: T
|
||||
get() {
|
||||
val local = _val
|
||||
return local ?: synchronized(this) {
|
||||
_val ?: factory().also { _val = it }
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInitialized() = _val != null
|
||||
|
||||
fun invalidate() {
|
||||
synchronized(this) {
|
||||
_val = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.extensions.drawableCompat
|
||||
import com.topjohnwu.magisk.extensions.replaceRandomWithSpecial
|
||||
@@ -240,6 +241,13 @@ fun setEnabled(view: View, isEnabled: Boolean) {
|
||||
view.isEnabled = isEnabled
|
||||
}
|
||||
|
||||
@BindingAdapter("error")
|
||||
fun TextInputLayout.setErrorString(error: String) {
|
||||
val newError = error.let { if (it.isEmpty()) null else it }
|
||||
if (this.error == null && newError == null) return
|
||||
this.error = newError
|
||||
}
|
||||
|
||||
// md2
|
||||
|
||||
@BindingAdapter("onSelectClick", "onSelectReset", requireAll = false)
|
||||
|
||||
@@ -9,15 +9,11 @@ import java.io.Serializable
|
||||
* You can define if wrapped type is Nullable or not.
|
||||
* You can use kotlin get/set syntax for value
|
||||
*/
|
||||
class KObservableField<T> : ObservableField<T>, Serializable {
|
||||
open class KObservableField<T> : ObservableField<T>, Serializable {
|
||||
|
||||
var value: T
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
notifyChange()
|
||||
}
|
||||
}
|
||||
get() = get()
|
||||
set(value) { set(value) }
|
||||
|
||||
constructor(init: T) {
|
||||
value = init
|
||||
@@ -27,23 +23,8 @@ class KObservableField<T> : ObservableField<T>, Serializable {
|
||||
value = init
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Needed for data binding, use KObservableField.value syntax from code",
|
||||
replaceWith = ReplaceWith("value")
|
||||
)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun get(): T {
|
||||
return value
|
||||
return super.get() as T
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Needed for data binding, use KObservableField.value = ... syntax from code",
|
||||
replaceWith = ReplaceWith("value = newValue")
|
||||
)
|
||||
override fun set(newValue: T) {
|
||||
value = newValue
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "KObservableField(value=$value)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Base64
|
||||
import android.util.Base64OutputStream
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.di.koinModules
|
||||
import com.topjohnwu.signing.CryptoUtils.readCertificate
|
||||
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
|
||||
import com.topjohnwu.superuser.internal.InternalUtils
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.context.startKoin
|
||||
import timber.log.Timber
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.KeyStore
|
||||
import java.security.MessageDigest
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
|
||||
private interface CertKeyProvider {
|
||||
val cert: X509Certificate
|
||||
val key: PrivateKey
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class Keygen: CertKeyProvider {
|
||||
|
||||
companion object {
|
||||
private const val ALIAS = "magisk"
|
||||
private val PASSWORD get() = "magisk".toCharArray()
|
||||
private const val TESTKEY_CERT = "61ed377e85d386a8dfee6b864bd85b0bfaa5af81"
|
||||
private const val DNAME = "C=US,ST=California,L=Mountain View,O=Google Inc.,OU=Android,CN=Android"
|
||||
private const val BASE64_FLAG = Base64.NO_PADDING or Base64.NO_WRAP
|
||||
}
|
||||
|
||||
private val start = Calendar.getInstance()
|
||||
private val end = Calendar.getInstance().apply { add(Calendar.YEAR, 30) }
|
||||
|
||||
override val cert get() = provider.cert
|
||||
override val key get() = provider.key
|
||||
|
||||
private val provider: CertKeyProvider
|
||||
|
||||
inner class KeyStoreProvider : CertKeyProvider {
|
||||
private val ks by lazy { init() }
|
||||
override val cert by lazy { ks.getCertificate(ALIAS) as X509Certificate }
|
||||
override val key by lazy { ks.getKey(ALIAS, PASSWORD) as PrivateKey }
|
||||
}
|
||||
|
||||
class TestProvider : CertKeyProvider {
|
||||
override val cert by lazy {
|
||||
readCertificate(javaClass.getResourceAsStream("/keys/testkey.x509.pem"))
|
||||
}
|
||||
override val key by lazy {
|
||||
readPrivateKey(javaClass.getResourceAsStream("/keys/testkey.pk8"))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// This object could possibly be accessed from an external app
|
||||
// Get context from reflection into Android's framework
|
||||
val context = InternalUtils.getContext()
|
||||
val pm = context.packageManager
|
||||
val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
|
||||
val sig = info.signatures[0]
|
||||
val digest = MessageDigest.getInstance("SHA1")
|
||||
val chksum = digest.digest(sig.toByteArray())
|
||||
|
||||
val sb = StringBuilder()
|
||||
for (b in chksum) {
|
||||
sb.append("%02x".format(0xFF and b.toInt()))
|
||||
}
|
||||
|
||||
provider = if (sb.toString() == TESTKEY_CERT) {
|
||||
// The app was signed by the test key, continue to use it (legacy mode)
|
||||
TestProvider()
|
||||
} else {
|
||||
KeyStoreProvider()
|
||||
}
|
||||
}
|
||||
|
||||
private fun init(): KeyStore {
|
||||
GlobalContext.getOrNull() ?: {
|
||||
// Invoked externally, do some basic initialization
|
||||
startKoin {
|
||||
modules(koinModules)
|
||||
}
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}()
|
||||
|
||||
val raw = Config.keyStoreRaw
|
||||
val ks = KeyStore.getInstance("PKCS12")
|
||||
if (raw.isEmpty()) {
|
||||
ks.load(null)
|
||||
} else {
|
||||
GZIPInputStream(Base64.decode(raw, BASE64_FLAG).inputStream()).use {
|
||||
ks.load(it, PASSWORD)
|
||||
}
|
||||
}
|
||||
|
||||
// Keys already exist
|
||||
if (ks.containsAlias(ALIAS))
|
||||
return ks
|
||||
|
||||
// Generate new private key and certificate
|
||||
val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(4096) }.genKeyPair()
|
||||
val dname = X500Name(DNAME)
|
||||
val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()),
|
||||
start.time, end.time, dname, kp.public)
|
||||
val signer = JcaContentSignerBuilder("SHA256WithRSA").build(kp.private)
|
||||
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||
|
||||
// Store them into keystore
|
||||
ks.setKeyEntry(ALIAS, kp.private, PASSWORD, arrayOf(cert))
|
||||
val bytes = ByteArrayOutputStream()
|
||||
GZIPOutputStream(Base64OutputStream(bytes, BASE64_FLAG)).use {
|
||||
ks.store(it, PASSWORD)
|
||||
}
|
||||
Config.keyStoreRaw = bytes.toString("UTF-8")
|
||||
|
||||
return ks
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.ResourceMgr
|
||||
import com.topjohnwu.magisk.extensions.langTagToLocale
|
||||
import io.reactivex.Single
|
||||
import java.util.*
|
||||
import kotlin.Comparator
|
||||
|
||||
var currentLocale: Locale = Locale.getDefault()
|
||||
|
||||
@SuppressLint("ConstantLocale")
|
||||
val defaultLocale: Locale = Locale.getDefault()
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val availableLocales = Single.fromCallable {
|
||||
val compareId = R.string.app_changelog
|
||||
mutableListOf<Locale>().apply {
|
||||
// Add default locale
|
||||
add(Locale.ENGLISH)
|
||||
|
||||
// Add some special locales
|
||||
add(Locale.TAIWAN)
|
||||
add(Locale("pt", "BR"))
|
||||
|
||||
val config = Configuration()
|
||||
val metrics = ResourceMgr.resource.displayMetrics
|
||||
val res = Resources(ResourceMgr.resource.assets, metrics, config)
|
||||
|
||||
// Other locales
|
||||
val otherLocales = ResourceMgr.resource.assets.locales
|
||||
.map { it.langTagToLocale() }
|
||||
.distinctBy {
|
||||
config.setLocale(it)
|
||||
res.updateConfiguration(config, metrics)
|
||||
res.getString(compareId)
|
||||
}
|
||||
|
||||
listOf("", "").toTypedArray()
|
||||
|
||||
addAll(otherLocales)
|
||||
}.sortedWith(Comparator { a, b ->
|
||||
a.getDisplayName(a).toLowerCase(a)
|
||||
.compareTo(b.getDisplayName(b).toLowerCase(b))
|
||||
})
|
||||
}.cache()!!
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.widget.Toast
|
||||
import com.topjohnwu.magisk.*
|
||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
||||
import com.topjohnwu.magisk.extensions.get
|
||||
import com.topjohnwu.magisk.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.ui.SplashActivity
|
||||
import com.topjohnwu.magisk.extensions.writeTo
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.signing.JarMap
|
||||
import com.topjohnwu.signing.SignAPK
|
||||
@@ -47,81 +50,86 @@ object PatchAPK {
|
||||
}
|
||||
|
||||
private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean {
|
||||
if (from.length != to.length)
|
||||
if (to.length > from.length)
|
||||
return false
|
||||
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer()
|
||||
val offList = mutableListOf<Int>()
|
||||
var i = 0
|
||||
while (i < buf.length - from.length) {
|
||||
var match = true
|
||||
for (j in 0 until from.length) {
|
||||
loop@ while (i < buf.length - from.length) {
|
||||
for (j in from.indices) {
|
||||
if (buf.get(i + j) != from[j]) {
|
||||
match = false
|
||||
break
|
||||
++i
|
||||
continue@loop
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
offList.add(i)
|
||||
i += from.length
|
||||
}
|
||||
++i
|
||||
offList.add(i)
|
||||
i += from.length
|
||||
}
|
||||
if (offList.isEmpty())
|
||||
return false
|
||||
|
||||
val toBuf = to.toCharArray().copyOf(from.length)
|
||||
for (off in offList) {
|
||||
buf.position(off)
|
||||
buf.put(to)
|
||||
buf.put(toBuf)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun findAndPatch(xml: ByteArray, a: Int, b: Int): Boolean {
|
||||
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer()
|
||||
val len = xml.size / 4
|
||||
for (i in 0 until len) {
|
||||
if (buf.get(i) == a) {
|
||||
buf.put(i, b)
|
||||
return true
|
||||
private fun patchAndHide(context: Context, label: String): Boolean {
|
||||
// If not running as stub, and we are compatible with stub, use stub
|
||||
val src = if (!isRunningAsStub && SDK_INT >= 28 && Info.env.connectionMode == 3) {
|
||||
val stub = File(context.cacheDir, "stub.apk")
|
||||
val svc = get<GithubRawServices>()
|
||||
runCatching {
|
||||
svc.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
|
||||
it.writeTo(stub)
|
||||
}
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
return false
|
||||
}
|
||||
stub.path
|
||||
} else {
|
||||
context.packageCodePath
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun patchAndHide(context: Context): Boolean {
|
||||
// Generate a new app with random package name
|
||||
val repack = File(context.filesDir, "patched.apk")
|
||||
// Generate a new random package name and signature
|
||||
val repack = File(context.cacheDir, "patched.apk")
|
||||
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
|
||||
Config.keyStoreRaw = ""
|
||||
|
||||
if (!patch(context.packageCodePath, repack.path, pkg))
|
||||
if (!patch(src, repack.path, pkg, label))
|
||||
return false
|
||||
|
||||
// Install the application
|
||||
repack.setReadable(true, false)
|
||||
if (!Shell.su("pm install $repack").exec().isSuccess)
|
||||
if (!Shell.su("force_pm_install $repack").exec().isSuccess)
|
||||
return false
|
||||
|
||||
Config.suManager = pkg
|
||||
Config.export()
|
||||
Utils.rmAndLaunch(BuildConfig.APPLICATION_ID,
|
||||
ComponentName(pkg, ClassMap.get<Class<*>>(SplashActivity::class.java).name))
|
||||
Shell.su("pm uninstall ${BuildConfig.APPLICATION_ID}").submit()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun patch(apk: String, out: String, pkg: String): Boolean {
|
||||
@JvmOverloads
|
||||
fun patch(apk: String, out: String, pkg: String, label: String = "Manager"): Boolean {
|
||||
try {
|
||||
val jar = JarMap(apk)
|
||||
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
||||
val xml = jar.getRawData(je)
|
||||
|
||||
if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) ||
|
||||
!findAndPatch(xml, R.string.app_name, R.string.re_app_name))
|
||||
!findAndPatch(xml, "Magisk Manager", label))
|
||||
return false
|
||||
|
||||
// Write apk changes
|
||||
jar.getOutputStream(je).write(xml)
|
||||
SignAPK.sign(jar, FileOutputStream(out).buffered())
|
||||
val keys = Keygen()
|
||||
SignAPK.sign(keys.cert, keys.key, jar, FileOutputStream(out).buffered())
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
return false
|
||||
@@ -130,11 +138,36 @@ object PatchAPK {
|
||||
return true
|
||||
}
|
||||
|
||||
fun hideManager(context: Context) {
|
||||
fun patch(apk: File, out: File, pkg: String, label: String): Boolean {
|
||||
try {
|
||||
if (apk.length() < 1 shl 18) {
|
||||
// APK is smaller than 256K, must be stub
|
||||
return patch(apk.path, out.path, pkg, label)
|
||||
}
|
||||
|
||||
// Try using the new APK to patch itself
|
||||
val loader = DynamicClassLoader(apk)
|
||||
val cls = loader.loadClass("a.a")
|
||||
|
||||
for (m in cls.declaredMethods) {
|
||||
val pars = m.parameterTypes
|
||||
if (pars.size == 4 && pars[0] == String::class.java) {
|
||||
return m.invoke(null, apk.path, out.path, pkg, label) as Boolean
|
||||
}
|
||||
}
|
||||
throw Exception("No matching method found")
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
// Fallback to use the current implementation
|
||||
return patch(apk.path, out.path, pkg, label)
|
||||
}
|
||||
}
|
||||
|
||||
fun hideManager(context: Context, label: String) {
|
||||
Completable.fromAction {
|
||||
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
||||
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
||||
if (!patchAndHide(context))
|
||||
if (!patchAndHide(context, label))
|
||||
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
||||
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
||||
}.subscribeK()
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.res.AssetManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.StringRes
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.extensions.langTagToLocale
|
||||
import io.reactivex.Single
|
||||
import java.util.*
|
||||
|
||||
var isRunningAsStub = false
|
||||
|
||||
var currentLocale: Locale = Locale.getDefault()
|
||||
private set
|
||||
|
||||
@SuppressLint("ConstantLocale")
|
||||
val defaultLocale: Locale = Locale.getDefault()
|
||||
|
||||
val availableLocales = Single.fromCallable {
|
||||
val compareId = R.string.app_changelog
|
||||
mutableListOf<Locale>().apply {
|
||||
// Add default locale
|
||||
add(Locale.ENGLISH)
|
||||
|
||||
// Add some special locales
|
||||
add(Locale.TAIWAN)
|
||||
add(Locale("pt", "BR"))
|
||||
|
||||
val config = Configuration()
|
||||
val metrics = ResourceMgr.resource.displayMetrics
|
||||
val res = Resources(ResourceMgr.resource.assets, metrics, config)
|
||||
|
||||
// Other locales
|
||||
val otherLocales = ResourceMgr.resource.assets.locales
|
||||
.map { it.langTagToLocale() }
|
||||
.distinctBy {
|
||||
config.setLocale(it)
|
||||
res.updateConfiguration(config, metrics)
|
||||
res.getString(compareId)
|
||||
}
|
||||
|
||||
listOf("", "").toTypedArray()
|
||||
|
||||
addAll(otherLocales)
|
||||
}.sortedWith(Comparator { a, b ->
|
||||
a.getDisplayName(a).toLowerCase(a)
|
||||
.compareTo(b.getDisplayName(b).toLowerCase(b))
|
||||
})
|
||||
}.cache()!!
|
||||
|
||||
private val addAssetPath by lazy {
|
||||
AssetManager::class.java.getMethod("addAssetPath", String::class.java)
|
||||
}
|
||||
|
||||
fun AssetManager.addAssetPath(path: String) {
|
||||
addAssetPath.invoke(this, path)
|
||||
}
|
||||
|
||||
fun Context.wrap(global: Boolean = true): Context
|
||||
= if (!global) ResourceMgr.ResContext(this) else ResourceMgr.GlobalResContext(this)
|
||||
|
||||
object ResourceMgr {
|
||||
|
||||
lateinit var resource: Resources
|
||||
private lateinit var resApk: String
|
||||
|
||||
fun init(context: Context) {
|
||||
resource = context.resources
|
||||
if (isRunningAsStub)
|
||||
resApk = DynAPK.current(context).path
|
||||
}
|
||||
|
||||
// Override locale and inject resources from dynamic APK
|
||||
private fun Resources.patch(config: Configuration = Configuration(configuration)): Resources {
|
||||
config.setLocale(currentLocale)
|
||||
updateConfiguration(config, displayMetrics)
|
||||
if (isRunningAsStub)
|
||||
assets.addAssetPath(resApk)
|
||||
return this
|
||||
}
|
||||
|
||||
fun reload(config: Configuration = Configuration(resource.configuration)) {
|
||||
val localeConfig = Config.locale
|
||||
currentLocale = when {
|
||||
localeConfig.isEmpty() -> defaultLocale
|
||||
else -> localeConfig.langTagToLocale()
|
||||
}
|
||||
Locale.setDefault(currentLocale)
|
||||
resource.patch(config)
|
||||
}
|
||||
|
||||
fun getString(locale: Locale, @StringRes id: Int): String {
|
||||
val config = Configuration()
|
||||
config.setLocale(locale)
|
||||
return Resources(resource.assets, resource.displayMetrics, config).getString(id)
|
||||
}
|
||||
|
||||
open class GlobalResContext(base: Context) : ContextWrapper(base) {
|
||||
open val mRes: Resources get() = resource
|
||||
private val loader by lazy { javaClass.classLoader!! }
|
||||
|
||||
override fun getResources(): Resources {
|
||||
return mRes
|
||||
}
|
||||
|
||||
override fun getClassLoader(): ClassLoader {
|
||||
return loader
|
||||
}
|
||||
|
||||
override fun createConfigurationContext(config: Configuration): Context {
|
||||
return ResContext(super.createConfigurationContext(config))
|
||||
}
|
||||
}
|
||||
|
||||
class ResContext(base: Context) : GlobalResContext(base) {
|
||||
override val mRes by lazy { base.resources.patch() }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.Info
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.extensions.rawResource
|
||||
import com.topjohnwu.magisk.wrap
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import com.topjohnwu.superuser.io.SuFile
|
||||
@@ -16,12 +17,14 @@ class RootInit : Shell.Initializer() {
|
||||
}
|
||||
|
||||
fun init(context: Context, shell: Shell): Boolean {
|
||||
// Invalidate env state if shell is recreated
|
||||
Info.envRef.invalidate()
|
||||
|
||||
val job = shell.newJob()
|
||||
if (shell.isRoot) {
|
||||
job.add(context.rawResource(R.raw.util_functions))
|
||||
.add(context.rawResource(R.raw.utils))
|
||||
.add(context.rawResource(R.raw.utils))
|
||||
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
|
||||
Info.loadMagiskInfo()
|
||||
} else {
|
||||
job.add(context.rawResource(R.raw.nonroot_utils))
|
||||
}
|
||||
@@ -35,6 +38,7 @@ class RootInit : Shell.Initializer() {
|
||||
Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean()
|
||||
Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean()
|
||||
Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@ package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Process
|
||||
import android.widget.Toast
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.data.database.PolicyDao
|
||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||
import com.topjohnwu.magisk.extensions.inject
|
||||
import com.topjohnwu.magisk.extensions.get
|
||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||
import com.topjohnwu.magisk.model.entity.toLog
|
||||
import com.topjohnwu.magisk.model.entity.toPolicy
|
||||
@@ -17,15 +16,13 @@ import java.util.*
|
||||
|
||||
object SuLogger {
|
||||
|
||||
private val context: Context by inject()
|
||||
|
||||
fun handleLogs(intent: Intent) {
|
||||
fun handleLogs(context: Context, intent: Intent) {
|
||||
|
||||
val fromUid = intent.getIntExtra("from.uid", -1)
|
||||
if (fromUid < 0) return
|
||||
if (fromUid == Process.myUid()) return
|
||||
|
||||
val pm: PackageManager by inject()
|
||||
val pm = context.packageManager
|
||||
|
||||
val notify: Boolean
|
||||
val data = intent.extras
|
||||
@@ -36,7 +33,7 @@ object SuLogger {
|
||||
}.getOrElse { return }
|
||||
} else {
|
||||
// Doesn't report whether notify or not, check database ourselves
|
||||
val policyDB: PolicyDao by inject()
|
||||
val policyDB = get<PolicyDao>()
|
||||
val policy = policyDB.fetch(fromUid).blockingGet() ?: return
|
||||
notify = policy.notification
|
||||
policy
|
||||
@@ -46,7 +43,7 @@ object SuLogger {
|
||||
return
|
||||
|
||||
if (notify)
|
||||
handleNotify(policy)
|
||||
handleNotify(context, policy)
|
||||
|
||||
val toUid = intent.getIntExtra("to.uid", -1)
|
||||
if (toUid < 0) return
|
||||
@@ -62,11 +59,11 @@ object SuLogger {
|
||||
date = Date()
|
||||
)
|
||||
|
||||
val logRepo: LogRepository by inject()
|
||||
val logRepo = get<LogRepository>()
|
||||
logRepo.put(log).blockingGet()?.printStackTrace()
|
||||
}
|
||||
|
||||
private fun handleNotify(policy: MagiskPolicy) {
|
||||
private fun handleNotify(context: Context, policy: MagiskPolicy) {
|
||||
if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
|
||||
Utils.toast(
|
||||
context.getString(
|
||||
@@ -80,16 +77,16 @@ object SuLogger {
|
||||
}
|
||||
}
|
||||
|
||||
fun handleNotify(intent: Intent) {
|
||||
fun handleNotify(context: Context, intent: Intent) {
|
||||
val fromUid = intent.getIntExtra("from.uid", -1)
|
||||
if (fromUid < 0) return
|
||||
if (fromUid == Process.myUid()) return
|
||||
runCatching {
|
||||
val packageManager: PackageManager by inject()
|
||||
val policy = fromUid.toPolicy(packageManager)
|
||||
val pm = context.packageManager
|
||||
val policy = fromUid.toPolicy(pm)
|
||||
.copy(policy = intent.getIntExtra("policy", -1))
|
||||
if (policy.policy >= 0)
|
||||
handleNotify(policy)
|
||||
handleNotify(context, policy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
@@ -46,7 +45,7 @@ object Utils {
|
||||
.setRequiresDeviceIdle(true)
|
||||
.build()
|
||||
val request = PeriodicWorkRequest
|
||||
.Builder(ClassMap[UpdateCheckService::class.java], 12, TimeUnit.HOURS)
|
||||
.Builder(ClassMap[UpdateCheckService::class.java] as Class<Worker>, 12, TimeUnit.HOURS)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
||||
@@ -73,8 +72,4 @@ object Utils {
|
||||
if ((exists() && isDirectory) || mkdirs()) this else null
|
||||
}
|
||||
|
||||
fun rmAndLaunch(rm: String, component: ComponentName) {
|
||||
Shell.su("(rm_launch $rm ${component.flattenToString()})").exec()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user