Fix all locale issues

This commit is contained in:
topjohnwu 2020-02-18 14:02:08 -08:00
parent 1e7e06d1cc
commit 0d7474cc88
6 changed files with 65 additions and 62 deletions

View File

@ -82,7 +82,7 @@ open class App() : Application() {
androidContext(wrapped) androidContext(wrapped)
modules(koinModules) modules(koinModules)
} }
ResourceMgr.init(impl) ResMgr.init(impl)
app.registerActivityLifecycleCallbacks(get<ActivityTracker>()) app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build()) WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
} }

View File

@ -57,7 +57,7 @@ fun Class<*>.cmp(pkg: String): ComponentName {
inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName)) inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName))
private open class GlobalResContext(base: Context) : ContextWrapper(base) { 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 { override fun getResources(): Resources {
return mRes return mRes
@ -78,22 +78,24 @@ private class ResContext(base: Context) : GlobalResContext(base) {
private fun Resources.patch(): Resources { private fun Resources.patch(): Resources {
updateConfig() updateConfig()
if (isRunningAsStub) if (isRunningAsStub)
assets.addAssetPath(ResourceMgr.resApk) assets.addAssetPath(ResMgr.apk)
return this return this
} }
} }
object ResourceMgr { object ResMgr {
lateinit var resource: Resources lateinit var resource: Resources
lateinit var resApk: String lateinit var apk: String
fun init(context: Context) { fun init(context: Context) {
resource = context.resources resource = context.resources
refreshLocale() refreshLocale()
if (isRunningAsStub) { if (isRunningAsStub) {
resApk = DynAPK.current(context).path apk = DynAPK.current(context).path
resource.assets.addAssetPath(resApk) resource.assets.addAssetPath(apk)
} else {
apk = context.packageResourcePath
} }
} }
} }
@ -169,8 +171,5 @@ val shouldKeepResources = listOf(
R.string.unsupport_magisk_title, R.string.unsupport_magisk_title,
R.string.install_inactive_slot_msg, R.string.install_inactive_slot_msg,
R.string.invalid_update_channel, R.string.invalid_update_channel,
R.string.update_available, R.string.update_available
/* Android Studio is dumb and cannot detect following usages in databinding */
R.menu.menu_reboot
) )

View File

@ -3,11 +3,14 @@
package com.topjohnwu.magisk.core.utils package com.topjohnwu.magisk.core.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.res.AssetManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.util.DisplayMetrics
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config 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.langTagToLocale
import com.topjohnwu.magisk.extensions.toLangTag import com.topjohnwu.magisk.extensions.toLangTag
import io.reactivex.Single import io.reactivex.Single
@ -22,41 +25,37 @@ val defaultLocale: Locale = Locale.getDefault()
val availableLocales = Single.fromCallable { val availableLocales = Single.fromCallable {
val compareId = R.string.app_changelog 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<Locale>().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<String>().apply {
// Add default locale // Add default locale
add(Locale.ENGLISH) add("en")
// Add some special locales // Add some special locales
add(Locale.TAIWAN) add("zh-TW")
add(Locale("pt", "BR")) add("pt-BR")
// Other locales // Then add all supported locales
val otherLocales = ResourceMgr.resource.assets.locales addAll(res.assets.locales)
.map { it.langTagToLocale() } }.map {
.distinctBy { it.langTagToLocale()
config.setLocale(it) }.distinctBy {
res.updateConfiguration(config, metrics) config.setLocale(it)
res.getString(compareId) res.updateConfiguration(config, metrics)
} res.getString(compareId)
addAll(otherLocales)
}.sortedWith(Comparator { a, b -> }.sortedWith(Comparator { a, b ->
a.getDisplayName(a).toLowerCase(a) a.getDisplayName(a).compareTo(b.getDisplayName(b), true)
.compareTo(b.getDisplayName(b).toLowerCase(b))
}) })
config.setLocale(defaultLocale) config.setLocale(defaultLocale)
res.updateConfiguration(config, metrics) res.updateConfiguration(config, metrics)
val defName = res.getString(R.string.system_default) val defName = res.getString(R.string.system_default)
// Restore back to current locale
config.setLocale(currentLocale)
res.updateConfiguration(config, metrics)
Pair(locales, defName) Pair(locales, defName)
}.map { (locales, defName) -> }.map { (locales, defName) ->
val names = ArrayList<String>(locales.size + 1) val names = ArrayList<String>(locales.size + 1)
@ -85,5 +84,5 @@ fun refreshLocale() {
else -> localeConfig.langTagToLocale() else -> localeConfig.langTagToLocale()
} }
Locale.setDefault(currentLocale) Locale.setDefault(currentLocale)
ResourceMgr.resource.updateConfig() ResMgr.resource.updateConfig()
} }

View File

@ -8,7 +8,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.topjohnwu.magisk.core.ResourceMgr import com.topjohnwu.magisk.core.ResMgr
import com.topjohnwu.magisk.utils.RxBus import com.topjohnwu.magisk.utils.RxBus
import org.koin.core.qualifier.named import org.koin.core.qualifier.named
import org.koin.dsl.module import org.koin.dsl.module
@ -18,7 +18,7 @@ val Protected = named("protected")
val applicationModule = module { val applicationModule = module {
single { RxBus() } single { RxBus() }
factory { ResourceMgr.resource } factory { ResMgr.resource }
factory { get<Context>().packageManager } factory { get<Context>().packageManager }
factory(Protected) { createDEContext(get()) } factory(Protected) { createDEContext(get()) }
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) } single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import androidx.annotation.ArrayRes
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
@ -145,13 +146,19 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
protected val resources get() = get<Resources>() protected val resources get() = get<Resources>()
abstract val entries: Array<out CharSequence> @ArrayRes open val entryRes = -1
abstract val entryValues: Array<out CharSequence> @ArrayRes open val entryValRes = -1
open val entries get() = resources.getArrayOrEmpty(entryRes)
open val entryValues get() = resources.getArrayOrEmpty(entryValRes)
@get:Bindable @get:Bindable
val selectedEntry val selectedEntry
get() = entries.getOrNull(value) get() = entries.getOrNull(value)
private fun Resources.getArrayOrEmpty(id: Int): Array<String> =
runCatching { getStringArray(id) }.getOrDefault(emptyArray())
override fun onPressed(view: View, callback: Callback) { override fun onPressed(view: View, callback: Callback) {
if (entries.isEmpty() || entryValues.isEmpty()) return if (entries.isEmpty() || entryValues.isEmpty()) return
callback.onItemPressed(view, this) callback.onItemPressed(view, this)

View File

@ -39,8 +39,8 @@ object Language : SettingsItem.Selector() {
} }
override val title = R.string.language.asTransitive() override val title = R.string.language.asTransitive()
override var entries = arrayOf<String>() override var entries = emptyArray<String>()
override var entryValues = arrayOf<String>() override var entryValues = emptyArray<String>()
init { init {
availableLocales.subscribeK { (names, values) -> 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 title = R.string.settings_grid_column_count_title.asTransitive()
override val description = R.string.settings_grid_column_count_summary.asTransitive() override val description = R.string.settings_grid_column_count_summary.asTransitive()
override val entries = resources.getStringArray(R.array.span_count) override val entryRes = R.array.span_count
override val entryValues = resources.getStringArray(R.array.value_array) override val entryValRes = R.array.value_array
} }
object UpdateChannel : SettingsItem.Selector() { object UpdateChannel : SettingsItem.Selector() {
override var value by bindableValue(Config.updateChannel) { Config.updateChannel = it } override var value by bindableValue(Config.updateChannel) { Config.updateChannel = it }
override val title = R.string.settings_update_channel_title.asTransitive() 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) if (!isCanaryVersion && Config.updateChannel < Config.Value.CANARY_CHANNEL)
it.take(it.size - 2).toTypedArray() else it 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() { object UpdateChannelUrl : SettingsItem.Input() {
@ -247,8 +247,8 @@ object Superuser : SettingsItem.Section() {
object AccessMode : SettingsItem.Selector() { object AccessMode : SettingsItem.Selector() {
override val title = R.string.superuser_access.asTransitive() override val title = R.string.superuser_access.asTransitive()
override val entries = resources.getStringArray(R.array.su_access) override val entryRes = R.array.su_access
override val entryValues = resources.getStringArray(R.array.value_array) override val entryValRes = R.array.value_array
override var value by bindableValue(Config.rootMode) { override var value by bindableValue(Config.rootMode) {
Config.rootMode = entryValues[it].toInt() Config.rootMode = entryValues[it].toInt()
@ -257,16 +257,15 @@ object AccessMode : SettingsItem.Selector() {
object MultiuserMode : SettingsItem.Selector() { object MultiuserMode : SettingsItem.Selector() {
override val title = R.string.multiuser_mode.asTransitive() override val title = R.string.multiuser_mode.asTransitive()
override val entries = resources.getStringArray(R.array.multiuser_mode) override val entryRes = R.array.multiuser_mode
override val entryValues = resources.getStringArray(R.array.value_array) override val entryValRes = R.array.value_array
private val descArray = resources.getStringArray(R.array.multiuser_summary)
override var value by bindableValue(Config.suMultiuserMode) { override var value by bindableValue(Config.suMultiuserMode) {
Config.suMultiuserMode = entryValues[it].toInt() Config.suMultiuserMode = entryValues[it].toInt()
} }
override val description override val description
get() = descArray[value].asTransitive() get() = resources.getStringArray(R.array.multiuser_summary)[value].asTransitive()
override fun refresh() { override fun refresh() {
isEnabled = Const.USER_ID == 0 isEnabled = Const.USER_ID == 0
@ -275,22 +274,21 @@ object MultiuserMode : SettingsItem.Selector() {
object MountNamespaceMode : SettingsItem.Selector() { object MountNamespaceMode : SettingsItem.Selector() {
override val title = R.string.mount_namespace_mode.asTransitive() override val title = R.string.mount_namespace_mode.asTransitive()
override val entries = resources.getStringArray(R.array.namespace) override val entryRes = R.array.namespace
override val entryValues = resources.getStringArray(R.array.value_array) override val entryValRes = R.array.value_array
private val descArray = resources.getStringArray(R.array.namespace_summary)
override var value by bindableValue(Config.suMntNamespaceMode) { override var value by bindableValue(Config.suMntNamespaceMode) {
Config.suMntNamespaceMode = entryValues[it].toInt() Config.suMntNamespaceMode = entryValues[it].toInt()
} }
override val description override val description
get() = descArray[value].asTransitive() get() = resources.getStringArray(R.array.namespace_summary)[value].asTransitive()
} }
object AutomaticResponse : SettingsItem.Selector() { object AutomaticResponse : SettingsItem.Selector() {
override val title = R.string.auto_response.asTransitive() override val title = R.string.auto_response.asTransitive()
override val entries = resources.getStringArray(R.array.auto_response) override val entryRes = R.array.auto_response
override val entryValues = resources.getStringArray(R.array.value_array) override val entryValRes = R.array.value_array
override var value by bindableValue(Config.suAutoReponse) { override var value by bindableValue(Config.suAutoReponse) {
Config.suAutoReponse = entryValues[it].toInt() Config.suAutoReponse = entryValues[it].toInt()
@ -299,8 +297,8 @@ object AutomaticResponse : SettingsItem.Selector() {
object RequestTimeout : SettingsItem.Selector() { object RequestTimeout : SettingsItem.Selector() {
override val title = R.string.request_timeout.asTransitive() override val title = R.string.request_timeout.asTransitive()
override val entries = resources.getStringArray(R.array.request_timeout) override val entryRes = R.array.request_timeout
override val entryValues = resources.getStringArray(R.array.request_timeout_value) override val entryValRes = R.array.request_timeout_value
override var value by bindableValue(selected) { override var value by bindableValue(selected) {
Config.suDefaultTimeout = entryValues[it].toInt() Config.suDefaultTimeout = entryValues[it].toInt()
@ -312,8 +310,8 @@ object RequestTimeout : SettingsItem.Selector() {
object SUNotification : SettingsItem.Selector() { object SUNotification : SettingsItem.Selector() {
override val title = R.string.superuser_notification.asTransitive() override val title = R.string.superuser_notification.asTransitive()
override val entries = resources.getStringArray(R.array.su_notification) override val entryRes = R.array.su_notification
override val entryValues = resources.getStringArray(R.array.value_array) override val entryValRes = R.array.value_array
override var value by bindableValue(Config.suNotification) { override var value by bindableValue(Config.suNotification) {
Config.suNotification = entryValues[it].toInt() Config.suNotification = entryValues[it].toInt()