diff --git a/app/src/main/java/com/topjohnwu/magisk/core/App.kt b/app/src/main/java/com/topjohnwu/magisk/core/App.kt index 988640ce4..285277ec1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/App.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/App.kt @@ -73,7 +73,7 @@ open class App() : Application() { androidContext(wrapped) modules(koinModules) } - ResMgr.init(impl) + AssetHack.init(impl) app.registerActivityLifecycleCallbacks(ForegroundTracker) WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build()) } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Hacks.kt b/app/src/main/java/com/topjohnwu/magisk/core/Hacks.kt index 634a3c71d..b7d2b5a4e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Hacks.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Hacks.kt @@ -14,6 +14,7 @@ import android.content.Intent import android.content.res.AssetManager import android.content.res.Configuration import android.content.res.Resources +import android.util.DisplayMetrics import androidx.annotation.RequiresApi import com.topjohnwu.magisk.DynAPK import com.topjohnwu.magisk.R @@ -27,23 +28,22 @@ fun AssetManager.addAssetPath(path: String) { DynAPK.addAssetPath(this, path) } -fun Context.wrap(global: Boolean = true): Context = - if (global) GlobalResContext(this) else ResContext(this) +fun Context.wrap(inject: Boolean = false): Context = + if (inject) ReInjectedContext(this) else InjectedContext(this) -fun Context.wrapJob(): Context = object : GlobalResContext(this) { +fun Context.wrapJob(): Context = object : InjectedContext(this) { - override fun getApplicationContext(): Context { - return this - } + override fun getApplicationContext() = this @SuppressLint("NewApi") override fun getSystemService(name: String): Any? { - return if (!isRunningAsStub) super.getSystemService(name) else - when (name) { - Context.JOB_SCHEDULER_SERVICE -> - JobSchedulerWrapper(super.getSystemService(name) as JobScheduler) - else -> super.getSystemService(name) + return super.getSystemService(name).let { + when { + !isRunningAsStub -> it + name == JOB_SCHEDULER_SERVICE -> JobSchedulerWrapper(it as JobScheduler) + else -> it } + } } } @@ -58,34 +58,27 @@ inline fun Activity.redirect() = Intent(intent) inline fun Context.intent() = Intent().setComponent(T::class.java.cmp(packageName)) -private open class GlobalResContext(base: Context) : ContextWrapper(base) { - open val mRes: Resources get() = ResMgr.resource - - override fun getResources(): Resources { - return mRes - } - - override fun getClassLoader(): ClassLoader { - return javaClass.classLoader!! - } - +private open class InjectedContext(base: Context) : ContextWrapper(base) { + open val res: Resources get() = AssetHack.resource + override fun getAssets(): AssetManager = res.assets + override fun getResources() = res + override fun getClassLoader() = javaClass.classLoader!! override fun createConfigurationContext(config: Configuration): Context { - return ResContext(super.createConfigurationContext(config)) + return super.createConfigurationContext(config).wrap(true) } } -private class ResContext(base: Context) : GlobalResContext(base) { - override val mRes by lazy { base.resources.patch() } - +private class ReInjectedContext(base: Context) : InjectedContext(base) { + override val res by lazy { base.resources.patch() } private fun Resources.patch(): Resources { updateConfig() if (isRunningAsStub) - assets.addAssetPath(ResMgr.apk) + assets.addAssetPath(AssetHack.apk) return this } } -object ResMgr { +object AssetHack { lateinit var resource: Resources lateinit var apk: String @@ -100,37 +93,27 @@ object ResMgr { apk = context.packageResourcePath } } + + fun newResource(): Resources { + val asset = AssetManager::class.java.newInstance() + asset.addAssetPath(apk) + val config = Configuration(resource.configuration) + val metrics = DisplayMetrics() + metrics.setTo(resource.displayMetrics) + return Resources(asset, metrics, config) + } } @RequiresApi(28) private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler() { - - override fun schedule(job: JobInfo): Int { - return base.schedule(job.patch()) - } - - override fun enqueue(job: JobInfo, work: JobWorkItem): Int { - return base.enqueue(job.patch(), work) - } - - override fun cancel(jobId: Int) { - base.cancel(jobId) - } - - override fun cancelAll() { - base.cancelAll() - } - - override fun getAllPendingJobs(): List { - return base.allPendingJobs - } - - override fun getPendingJob(jobId: Int): JobInfo? { - return base.getPendingJob(jobId) - } - + override fun schedule(job: JobInfo) = base.schedule(job.patch()) + override fun enqueue(job: JobInfo, work: JobWorkItem) = base.enqueue(job.patch(), work) + override fun cancel(jobId: Int) = base.cancel(jobId) + override fun cancelAll() = base.cancelAll() + override fun getAllPendingJobs(): List = base.allPendingJobs + override fun getPendingJob(jobId: Int) = base.getPendingJob(jobId) private fun JobInfo.patch(): JobInfo { - // We need to swap out the service of JobInfo + // Swap out the service of JobInfo val name = service.className val component = ComponentName( service.packageName, diff --git a/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt b/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt index bc8b790ca..64e88b325 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt @@ -34,7 +34,7 @@ abstract class BaseActivity : AppCompatActivity() { } override fun attachBaseContext(base: Context) { - super.attachBaseContext(base.wrap(false)) + super.attachBaseContext(base.wrap(true)) } fun withPermission(permission: String, builder: PermissionRequestBuilder.() -> Unit) { diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt index 2956fb1d1..912c304ba 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt @@ -3,20 +3,16 @@ package com.topjohnwu.magisk.core.utils import android.annotation.SuppressLint -import android.content.res.AssetManager import android.content.res.Configuration import android.content.res.Resources -import android.util.DisplayMetrics import com.topjohnwu.magisk.R +import com.topjohnwu.magisk.core.AssetHack import com.topjohnwu.magisk.core.Config -import com.topjohnwu.magisk.core.ResMgr -import com.topjohnwu.magisk.core.addAssetPath import com.topjohnwu.magisk.ktx.langTagToLocale import com.topjohnwu.magisk.ktx.toLangTag import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.util.* -import kotlin.Comparator import kotlin.collections.ArrayList var currentLocale: Locale = Locale.getDefault() @@ -30,11 +26,8 @@ suspend fun availableLocales() = cachedLocales ?: withContext(Dispatchers.Default) { val compareId = R.string.app_changelog - // Create a completely new resource to prevent cross talk over app's configs - val asset = AssetManager::class.java.newInstance().apply { addAssetPath(ResMgr.apk) } - val config = Configuration(ResMgr.resource.configuration) - val metrics = DisplayMetrics().apply { setTo(ResMgr.resource.displayMetrics) } - val res = Resources(asset, metrics, config) + // Create a completely new resource to prevent cross talk over active configs + val res = AssetHack.newResource() val locales = ArrayList().apply { // Add default locale @@ -49,15 +42,13 @@ withContext(Dispatchers.Default) { }.map { it.langTagToLocale() }.distinctBy { - config.setLocale(it) - res.updateConfiguration(config, metrics) + res.updateLocale(it) res.getString(compareId) - }.sortedWith(Comparator { a, b -> + }.sortedWith { a, b -> a.getDisplayName(a).compareTo(b.getDisplayName(b), true) - }) + } - config.setLocale(defaultLocale) - res.updateConfiguration(config, metrics) + res.updateLocale(defaultLocale) val defName = res.getString(R.string.system_default) val names = ArrayList(locales.size + 1) @@ -79,6 +70,11 @@ fun Resources.updateConfig(config: Configuration = configuration) { updateConfiguration(config, displayMetrics) } +fun Resources.updateLocale(locale: Locale) { + configuration.setLocale(locale) + updateConfiguration(configuration, displayMetrics) +} + fun refreshLocale() { val localeConfig = Config.locale currentLocale = when { @@ -86,5 +82,5 @@ fun refreshLocale() { else -> localeConfig.langTagToLocale() } Locale.setDefault(currentLocale) - ResMgr.resource.updateConfig() + AssetHack.resource.updateConfig() } diff --git a/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt b/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt index a24095cc8..37e5e5ae5 100644 --- a/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt +++ b/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt @@ -1,9 +1,9 @@ package com.topjohnwu.magisk.di import android.content.Context -import android.os.Build import androidx.preference.PreferenceManager -import com.topjohnwu.magisk.core.ResMgr +import com.topjohnwu.magisk.core.AssetHack +import com.topjohnwu.magisk.ktx.deviceProtectedContext import org.koin.core.qualifier.named import org.koin.dsl.module @@ -11,15 +11,9 @@ val SUTimeout = named("su_timeout") val Protected = named("protected") val applicationModule = module { - factory { ResMgr.resource } + factory { AssetHack.resource } factory { get().packageManager } - factory(Protected) { createDEContext(get()) } + factory(Protected) { get().deviceProtectedContext } single(SUTimeout) { get(Protected).getSharedPreferences("su_timeout", 0) } - single { PreferenceManager.getDefaultSharedPreferences(get(Protected)) } -} - -private fun createDEContext(context: Context): Context { - return if (Build.VERSION.SDK_INT >= 24) - context.createDeviceProtectedStorageContext() - else context + single { PreferenceManager.getDefaultSharedPreferences(get(Protected)) } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt b/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt index 274803dcd..37adca5cc 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt @@ -46,7 +46,7 @@ import androidx.transition.AutoTransition import androidx.transition.TransitionManager import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.Const -import com.topjohnwu.magisk.core.ResMgr +import com.topjohnwu.magisk.core.AssetHack import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.utils.DynamicClassLoader import com.topjohnwu.magisk.utils.Utils @@ -366,6 +366,6 @@ var TextView.precomputedText: CharSequence } fun Int.dpInPx(): Int { - val scale = ResMgr.resource.displayMetrics.density + val scale = AssetHack.resource.displayMetrics.density return (this * scale + 0.5).toInt() } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/MarkwonImagePlugin.kt b/app/src/main/java/com/topjohnwu/magisk/utils/MarkwonImagePlugin.kt index 4a17582a7..1e5a4e14f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/MarkwonImagePlugin.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/MarkwonImagePlugin.kt @@ -15,7 +15,7 @@ import android.widget.TextView import androidx.annotation.WorkerThread import com.caverock.androidsvg.SVG import com.caverock.androidsvg.SVGParseException -import com.topjohnwu.magisk.core.ResMgr +import com.topjohnwu.magisk.core.AssetHack import com.topjohnwu.superuser.internal.WaitRunnable import io.noties.markwon.AbstractMarkwonPlugin import io.noties.markwon.MarkwonSpansFactory @@ -216,7 +216,7 @@ class MarkwonImagePlugin(okHttp: OkHttpClient) : AbstractMarkwonPlugin() { return PictureDrawable(picture) } - val density: Float = ResMgr.resource.displayMetrics.density + val density: Float = AssetHack.resource.displayMetrics.density val width = (w * density + .5f).toInt() val height = (h * density + .5f).toInt() @@ -226,7 +226,7 @@ class MarkwonImagePlugin(okHttp: OkHttpClient) : AbstractMarkwonPlugin() { canvas.scale(density, density) svg.renderToCanvas(canvas) - return BitmapDrawable(ResMgr.resource, bitmap) + return BitmapDrawable(AssetHack.resource, bitmap) } }