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 89d40442c..9bc623d29 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/App.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/App.kt @@ -82,7 +82,7 @@ open class App() : Application() { androidContext(wrapped) modules(koinModules) } - ResourceMgr.init(impl) + ResMgr.init(impl) app.registerActivityLifecycleCallbacks(get()) 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 217af289a..ac0d3925e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Hacks.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Hacks.kt @@ -57,7 +57,7 @@ fun Class<*>.cmp(pkg: String): ComponentName { inline fun Context.intent() = Intent().setComponent(T::class.java.cmp(packageName)) private open class GlobalResContext(base: Context) : ContextWrapper(base) { - open val mRes: Resources get() = ResourceMgr.resource + open val mRes: Resources get() = ResMgr.resource override fun getResources(): Resources { return mRes @@ -78,22 +78,24 @@ private class ResContext(base: Context) : GlobalResContext(base) { private fun Resources.patch(): Resources { updateConfig() if (isRunningAsStub) - assets.addAssetPath(ResourceMgr.resApk) + assets.addAssetPath(ResMgr.apk) return this } } -object ResourceMgr { +object ResMgr { lateinit var resource: Resources - lateinit var resApk: String + lateinit var apk: String fun init(context: Context) { resource = context.resources refreshLocale() if (isRunningAsStub) { - resApk = DynAPK.current(context).path - resource.assets.addAssetPath(resApk) + apk = DynAPK.current(context).path + resource.assets.addAssetPath(apk) + } else { + apk = context.packageResourcePath } } } @@ -169,8 +171,5 @@ val shouldKeepResources = listOf( R.string.unsupport_magisk_title, R.string.install_inactive_slot_msg, R.string.invalid_update_channel, - R.string.update_available, - - /* Android Studio is dumb and cannot detect following usages in databinding */ - R.menu.menu_reboot + R.string.update_available ) 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 6cce37c15..b98813ccd 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,11 +3,14 @@ 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.Config -import com.topjohnwu.magisk.core.ResourceMgr +import com.topjohnwu.magisk.core.ResMgr +import com.topjohnwu.magisk.core.addAssetPath import com.topjohnwu.magisk.extensions.langTagToLocale import com.topjohnwu.magisk.extensions.toLangTag import io.reactivex.Single @@ -22,41 +25,37 @@ val defaultLocale: Locale = Locale.getDefault() val availableLocales = Single.fromCallable { val compareId = R.string.app_changelog - val config = ResourceMgr.resource.configuration - val metrics = ResourceMgr.resource.displayMetrics - val res = Resources(ResourceMgr.resource.assets, metrics, config) - val locales = mutableListOf().apply { + // 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) + + val locales = ArrayList().apply { // Add default locale - add(Locale.ENGLISH) + add("en") // Add some special locales - add(Locale.TAIWAN) - add(Locale("pt", "BR")) + add("zh-TW") + add("pt-BR") - // Other locales - val otherLocales = ResourceMgr.resource.assets.locales - .map { it.langTagToLocale() } - .distinctBy { - config.setLocale(it) - res.updateConfiguration(config, metrics) - res.getString(compareId) - } - - addAll(otherLocales) + // Then add all supported locales + addAll(res.assets.locales) + }.map { + it.langTagToLocale() + }.distinctBy { + config.setLocale(it) + res.updateConfiguration(config, metrics) + res.getString(compareId) }.sortedWith(Comparator { a, b -> - a.getDisplayName(a).toLowerCase(a) - .compareTo(b.getDisplayName(b).toLowerCase(b)) + a.getDisplayName(a).compareTo(b.getDisplayName(b), true) }) config.setLocale(defaultLocale) res.updateConfiguration(config, metrics) val defName = res.getString(R.string.system_default) - // Restore back to current locale - config.setLocale(currentLocale) - res.updateConfiguration(config, metrics) - Pair(locales, defName) }.map { (locales, defName) -> val names = ArrayList(locales.size + 1) @@ -85,5 +84,5 @@ fun refreshLocale() { else -> localeConfig.langTagToLocale() } Locale.setDefault(currentLocale) - ResourceMgr.resource.updateConfig() + ResMgr.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 574c90abe..617917ccc 100644 --- a/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt +++ b/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt @@ -8,7 +8,7 @@ import android.os.Build import android.os.Bundle import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.preference.PreferenceManager -import com.topjohnwu.magisk.core.ResourceMgr +import com.topjohnwu.magisk.core.ResMgr import com.topjohnwu.magisk.utils.RxBus import org.koin.core.qualifier.named import org.koin.dsl.module @@ -18,7 +18,7 @@ val Protected = named("protected") val applicationModule = module { single { RxBus() } - factory { ResourceMgr.resource } + factory { ResMgr.resource } factory { get().packageManager } factory(Protected) { createDEContext(get()) } single(SUTimeout) { get(Protected).getSharedPreferences("su_timeout", 0) } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SettingsItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SettingsItem.kt index aa7858d6a..fc372ce9b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SettingsItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/SettingsItem.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.res.Resources import android.view.MotionEvent import android.view.View +import androidx.annotation.ArrayRes import androidx.annotation.CallSuper import androidx.databinding.Bindable import androidx.databinding.ViewDataBinding @@ -145,13 +146,19 @@ sealed class SettingsItem : ObservableItem() { protected val resources get() = get() - abstract val entries: Array - abstract val entryValues: Array + @ArrayRes open val entryRes = -1 + @ArrayRes open val entryValRes = -1 + + open val entries get() = resources.getArrayOrEmpty(entryRes) + open val entryValues get() = resources.getArrayOrEmpty(entryValRes) @get:Bindable val selectedEntry get() = entries.getOrNull(value) + private fun Resources.getArrayOrEmpty(id: Int): Array = + runCatching { getStringArray(id) }.getOrDefault(emptyArray()) + override fun onPressed(view: View, callback: Callback) { if (entries.isEmpty() || entryValues.isEmpty()) return callback.onItemPressed(view, this) diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt index 6933c4022..09b6ec183 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt @@ -39,8 +39,8 @@ object Language : SettingsItem.Selector() { } override val title = R.string.language.asTransitive() - override var entries = arrayOf() - override var entryValues = arrayOf() + override var entries = emptyArray() + override var entryValues = emptyArray() init { availableLocales.subscribeK { (names, values) -> @@ -132,19 +132,19 @@ object GridSize : SettingsItem.Selector() { override val title = R.string.settings_grid_column_count_title.asTransitive() override val description = R.string.settings_grid_column_count_summary.asTransitive() - override val entries = resources.getStringArray(R.array.span_count) - override val entryValues = resources.getStringArray(R.array.value_array) + override val entryRes = R.array.span_count + override val entryValRes = R.array.value_array } object UpdateChannel : SettingsItem.Selector() { override var value by bindableValue(Config.updateChannel) { Config.updateChannel = it } override val title = R.string.settings_update_channel_title.asTransitive() - override val entries = resources.getStringArray(R.array.update_channel).let { + override val entries get() = resources.getStringArray(R.array.update_channel).let { if (!isCanaryVersion && Config.updateChannel < Config.Value.CANARY_CHANNEL) it.take(it.size - 2).toTypedArray() else it } - override val entryValues = resources.getStringArray(R.array.value_array) + override val entryValRes = R.array.value_array } object UpdateChannelUrl : SettingsItem.Input() { @@ -247,8 +247,8 @@ object Superuser : SettingsItem.Section() { object AccessMode : SettingsItem.Selector() { override val title = R.string.superuser_access.asTransitive() - override val entries = resources.getStringArray(R.array.su_access) - override val entryValues = resources.getStringArray(R.array.value_array) + override val entryRes = R.array.su_access + override val entryValRes = R.array.value_array override var value by bindableValue(Config.rootMode) { Config.rootMode = entryValues[it].toInt() @@ -257,16 +257,15 @@ object AccessMode : SettingsItem.Selector() { object MultiuserMode : SettingsItem.Selector() { override val title = R.string.multiuser_mode.asTransitive() - override val entries = resources.getStringArray(R.array.multiuser_mode) - override val entryValues = resources.getStringArray(R.array.value_array) - private val descArray = resources.getStringArray(R.array.multiuser_summary) + override val entryRes = R.array.multiuser_mode + override val entryValRes = R.array.value_array override var value by bindableValue(Config.suMultiuserMode) { Config.suMultiuserMode = entryValues[it].toInt() } override val description - get() = descArray[value].asTransitive() + get() = resources.getStringArray(R.array.multiuser_summary)[value].asTransitive() override fun refresh() { isEnabled = Const.USER_ID == 0 @@ -275,22 +274,21 @@ object MultiuserMode : SettingsItem.Selector() { object MountNamespaceMode : SettingsItem.Selector() { override val title = R.string.mount_namespace_mode.asTransitive() - override val entries = resources.getStringArray(R.array.namespace) - override val entryValues = resources.getStringArray(R.array.value_array) - private val descArray = resources.getStringArray(R.array.namespace_summary) + override val entryRes = R.array.namespace + override val entryValRes = R.array.value_array override var value by bindableValue(Config.suMntNamespaceMode) { Config.suMntNamespaceMode = entryValues[it].toInt() } override val description - get() = descArray[value].asTransitive() + get() = resources.getStringArray(R.array.namespace_summary)[value].asTransitive() } object AutomaticResponse : SettingsItem.Selector() { override val title = R.string.auto_response.asTransitive() - override val entries = resources.getStringArray(R.array.auto_response) - override val entryValues = resources.getStringArray(R.array.value_array) + override val entryRes = R.array.auto_response + override val entryValRes = R.array.value_array override var value by bindableValue(Config.suAutoReponse) { Config.suAutoReponse = entryValues[it].toInt() @@ -299,8 +297,8 @@ object AutomaticResponse : SettingsItem.Selector() { object RequestTimeout : SettingsItem.Selector() { override val title = R.string.request_timeout.asTransitive() - override val entries = resources.getStringArray(R.array.request_timeout) - override val entryValues = resources.getStringArray(R.array.request_timeout_value) + override val entryRes = R.array.request_timeout + override val entryValRes = R.array.request_timeout_value override var value by bindableValue(selected) { Config.suDefaultTimeout = entryValues[it].toInt() @@ -312,8 +310,8 @@ object RequestTimeout : SettingsItem.Selector() { object SUNotification : SettingsItem.Selector() { override val title = R.string.superuser_notification.asTransitive() - override val entries = resources.getStringArray(R.array.su_notification) - override val entryValues = resources.getStringArray(R.array.value_array) + override val entryRes = R.array.su_notification + override val entryValRes = R.array.value_array override var value by bindableValue(Config.suNotification) { Config.suNotification = entryValues[it].toInt()