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)
modules(koinModules)
}
ResourceMgr.init(impl)
ResMgr.init(impl)
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
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))
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
)

View File

@ -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<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(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<String>(locales.size + 1)
@ -85,5 +84,5 @@ fun refreshLocale() {
else -> localeConfig.langTagToLocale()
}
Locale.setDefault(currentLocale)
ResourceMgr.resource.updateConfig()
ResMgr.resource.updateConfig()
}

View File

@ -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<Context>().packageManager }
factory(Protected) { createDEContext(get()) }
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.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<SettingsItem>() {
protected val resources get() = get<Resources>()
abstract val entries: Array<out CharSequence>
abstract val entryValues: Array<out CharSequence>
@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<String> =
runCatching { getStringArray(id) }.getOrDefault(emptyArray())
override fun onPressed(view: View, callback: Callback) {
if (entries.isEmpty() || entryValues.isEmpty()) return
callback.onItemPressed(view, this)

View File

@ -39,8 +39,8 @@ object Language : SettingsItem.Selector() {
}
override val title = R.string.language.asTransitive()
override var entries = arrayOf<String>()
override var entryValues = arrayOf<String>()
override var entries = emptyArray<String>()
override var entryValues = emptyArray<String>()
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()