Use platform LocaleManager if possible

This commit is contained in:
topjohnwu 2024-07-10 21:50:54 -07:00
parent 6b81716440
commit 7173693d1b
15 changed files with 294 additions and 162 deletions

View File

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<application> <application android:localeConfig="@xml/locale_config">
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
android:exported="true" android:exported="true"

View File

@ -16,7 +16,7 @@ import android.os.Build
import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION.SDK_INT
import androidx.core.os.ProcessCompat import androidx.core.os.ProcessCompat
import com.topjohnwu.magisk.core.ktx.getLabel import com.topjohnwu.magisk.core.ktx.getLabel
import com.topjohnwu.magisk.core.utils.currentLocale import java.util.Locale
import java.util.TreeSet import java.util.TreeSet
class CmdlineListItem(line: String) { class CmdlineListItem(line: String) {
@ -102,7 +102,7 @@ class AppProcessInfo(
companion object { companion object {
private val comparator = compareBy<AppProcessInfo>( private val comparator = compareBy<AppProcessInfo>(
{ it.label.lowercase(currentLocale) }, { it.label.lowercase(Locale.ROOT) },
{ it.info.packageName } { it.info.packageName }
) )
} }

View File

@ -14,18 +14,16 @@ import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.ktx.activity import com.topjohnwu.magisk.core.ktx.activity
import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.LocaleSetting
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.availableLocales
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import com.topjohnwu.magisk.core.R as CoreR import com.topjohnwu.magisk.core.R as CoreR
// --- Customization // --- Customization
@ -35,37 +33,27 @@ object Customization : BaseSettingsItem.Section() {
} }
object Language : BaseSettingsItem.Selector() { object Language : BaseSettingsItem.Selector() {
private val names: Array<String> get() = LocaleSetting.available.names
private val tags: Array<String> get() = LocaleSetting.available.tags
override var value override var value
get() = index get() = tags.indexOf(Config.locale)
set(value) { set(value) {
index = value Config.locale = tags[value]
Config.locale = entryValues[value]
} }
override val title = CoreR.string.language.asText() override val title = CoreR.string.language.asText()
private var entries = emptyArray<String>() override fun entries(res: Resources) = names
private var entryValues = emptyArray<String>() override fun descriptions(res: Resources) = names
private var index = -1
override fun entries(res: Resources) = entries
override fun descriptions(res: Resources) = entries
override fun onPressed(view: View, handler: Handler) {
if (entries.isNotEmpty())
super.onPressed(view, handler)
} }
suspend fun loadLanguages(scope: CoroutineScope) { object LanguageSystem : BaseSettingsItem.Blank() {
scope.launch { override val title = CoreR.string.language.asText()
availableLocales().let { (names, values) -> override val description: TextHolder
entries = names get() {
entryValues = values val locale = LocaleSetting.instance.appLocale
val selectedLocale = currentLocale.getDisplayName(currentLocale) return locale?.getDisplayName(locale)?.asText() ?: CoreR.string.system_default.asText()
index = names.indexOfFirst { it == selectedLocale }.let { if (it == -1) 0 else it }
notifyPropertyChanged(BR.description)
}
}
} }
} }

View File

@ -1,6 +1,10 @@
package com.topjohnwu.magisk.ui.settings package com.topjohnwu.magisk.ui.settings
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.Settings
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
@ -16,6 +20,7 @@ import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.activity import com.topjohnwu.magisk.core.ktx.activity
import com.topjohnwu.magisk.core.ktx.toast import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.LocaleSetting
import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.events.AddHomeIconEvent import com.topjohnwu.magisk.events.AddHomeIconEvent
import com.topjohnwu.magisk.events.AuthEvent import com.topjohnwu.magisk.events.AuthEvent
@ -30,12 +35,6 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
it.put(BR.handler, this) it.put(BR.handler, this)
} }
init {
viewModelScope.launch {
Language.loadLanguages(this)
}
}
private fun createItems(): List<BaseSettingsItem> { private fun createItems(): List<BaseSettingsItem> {
val context = AppContext val context = AppContext
val hidden = context.packageName != BuildConfig.APP_PACKAGE_NAME val hidden = context.packageName != BuildConfig.APP_PACKAGE_NAME
@ -43,7 +42,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
// Customization // Customization
val list = mutableListOf( val list = mutableListOf(
Customization, Customization,
Theme, Language Theme, if (LocaleSetting.useLocaleManager) LanguageSystem else Language
) )
if (isRunningAsStub && ShortcutManagerCompat.isRequestPinShortcutSupported(context)) if (isRunningAsStub && ShortcutManagerCompat.isRequestPinShortcutSupported(context))
list.add(AddShortcut) list.add(AddShortcut)
@ -98,6 +97,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
SystemlessHosts -> createHosts() SystemlessHosts -> createHosts()
Hide, Restore -> withInstallPermission(andThen) Hide, Restore -> withInstallPermission(andThen)
AddShortcut -> AddHomeIconEvent().publish() AddShortcut -> AddHomeIconEvent().publish()
LanguageSystem -> launchAppLocaleSettings(view.activity)
else -> andThen() else -> andThen()
} }
} }
@ -112,6 +112,12 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
} }
} }
private fun launchAppLocaleSettings(activity: Activity) {
val intent = Intent(Settings.ACTION_APP_LOCALE_SETTINGS)
intent.data = Uri.fromParts("package", activity.packageName, null)
activity.startActivity(intent)
}
private fun openUrlIfNecessary(view: View) { private fun openUrlIfNecessary(view: View) {
UpdateChannelUrl.refresh() UpdateChannelUrl.refresh()
if (UpdateChannelUrl.isEnabled && UpdateChannelUrl.value.isBlank()) { if (UpdateChannelUrl.isEnabled && UpdateChannelUrl.value.isBlank()) {

View File

@ -16,7 +16,6 @@ import com.topjohnwu.magisk.core.R
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.ktx.getLabel import com.topjohnwu.magisk.core.ktx.getLabel
import com.topjohnwu.magisk.core.model.su.SuPolicy import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.MergeObservableList import com.topjohnwu.magisk.databinding.MergeObservableList
import com.topjohnwu.magisk.databinding.RvItem import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.bindExtra
@ -30,6 +29,7 @@ import com.topjohnwu.magisk.view.TextItem
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.Locale
class SuperuserViewModel( class SuperuserViewModel(
private val db: PolicyDao private val db: PolicyDao
@ -92,7 +92,7 @@ class SuperuserViewModel(
policies.addAll(map) policies.addAll(map)
} }
policies.sortWith(compareBy( policies.sortWith(compareBy(
{ it.appName.lowercase(currentLocale) }, { it.appName.lowercase(Locale.ROOT) },
{ it.packageName } { it.packageName }
)) ))
itemsPolicies.update(policies) itemsPolicies.update(policies)

View File

@ -14,7 +14,7 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.google.android.material.circularreveal.CircularRevealCompat import com.google.android.material.circularreveal.CircularRevealCompat
import com.google.android.material.circularreveal.CircularRevealWidget import com.google.android.material.circularreveal.CircularRevealWidget
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.core.utils.LocaleSetting
import kotlin.math.hypot import kotlin.math.hypot
object MotionRevealHelper { object MotionRevealHelper {
@ -63,7 +63,9 @@ object MotionRevealHelper {
it.interpolator = FastOutSlowInInterpolator() it.interpolator = FastOutSlowInInterpolator()
it.addListener(onStart = { show() }, onEnd = { if (revealInfo.radius != 0f) hide() }) it.addListener(onStart = { show() }, onEnd = { if (revealInfo.radius != 0f) hide() })
val rtlMod = if (currentLocale.layoutDirection == View.LAYOUT_DIRECTION_RTL) 1f else -1f val rtlMod =
if (LocaleSetting.instance.currentLocale.layoutDirection == View.LAYOUT_DIRECTION_RTL)
1f else -1f
val maxX = revealInfo.centerX - marginEnd - measuredWidth / 2f val maxX = revealInfo.centerX - marginEnd - measuredWidth / 2f
val targetX = if (revealInfo.radius == 0f) 0f else maxX * rtlMod val targetX = if (revealInfo.radius == 0f) 0f else maxX * rtlMod
val moveX = ObjectAnimator.ofFloat(this, View.TRANSLATION_X, targetX) val moveX = ObjectAnimator.ofFloat(this, View.TRANSLATION_X, targetX)

View File

@ -2,6 +2,7 @@ package com.topjohnwu.magisk.core
import android.app.Activity import android.app.Activity
import android.app.Application import android.app.Application
import android.app.LocaleManager
import android.content.ComponentCallbacks2 import android.content.ComponentCallbacks2
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
@ -13,12 +14,11 @@ import android.system.Os
import androidx.profileinstaller.ProfileInstaller import androidx.profileinstaller.ProfileInstaller
import com.topjohnwu.magisk.StubApk import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.base.UntrackedActivity import com.topjohnwu.magisk.core.base.UntrackedActivity
import com.topjohnwu.magisk.core.utils.LocaleSetting
import com.topjohnwu.magisk.core.utils.NetworkObserver import com.topjohnwu.magisk.core.utils.NetworkObserver
import com.topjohnwu.magisk.core.utils.ProcessLifecycle import com.topjohnwu.magisk.core.utils.ProcessLifecycle
import com.topjohnwu.magisk.core.utils.RootUtils import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.ShellInit import com.topjohnwu.magisk.core.utils.ShellInit
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.core.utils.setConfig
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
@ -31,6 +31,9 @@ import timber.log.Timber
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.system.exitProcess import kotlin.system.exitProcess
lateinit var AppApkPath: String
private set
object AppContext : ContextWrapper(null), object AppContext : ContextWrapper(null),
Application.ActivityLifecycleCallbacks, ComponentCallbacks2 { Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
@ -52,7 +55,7 @@ object AppContext : ContextWrapper(null),
} }
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
resources.setConfig(newConfig) LocaleSetting.instance.updateResource(resources)
} }
override fun onActivityResumed(activity: Activity) { override fun onActivityResumed(activity: Activity) {
@ -79,7 +82,6 @@ object AppContext : ContextWrapper(null),
} else { } else {
base.packageResourcePath base.packageResourcePath
} }
refreshLocale()
resources.patch() resources.patch()
val shellBuilder = Shell.Builder.create() val shellBuilder = Shell.Builder.create()
@ -97,6 +99,11 @@ object AppContext : ContextWrapper(null),
// Pre-heat the shell ASAP // Pre-heat the shell ASAP
Shell.getShell(null) {} Shell.getShell(null) {}
if (SDK_INT >= 34 && isRunningAsStub) {
// Send over the locale config manually
val lm = getSystemService(LocaleManager::class.java)
lm.overrideLocaleConfig = LocaleSetting.localeConfig
}
Notifications.setup() Notifications.setup()
ProcessLifecycle.init(this) ProcessLifecycle.init(this)
NetworkObserver.init(this) NetworkObserver.init(this)

View File

@ -6,7 +6,7 @@ import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.writeTo import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.repository.DBConfig import com.topjohnwu.magisk.core.repository.DBConfig
import com.topjohnwu.magisk.core.repository.PreferenceConfig import com.topjohnwu.magisk.core.repository.PreferenceConfig
import com.topjohnwu.magisk.core.utils.refreshLocale import com.topjohnwu.magisk.core.utils.LocaleSetting
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -135,7 +135,7 @@ object Config : PreferenceConfig, DBConfig {
get() = localePrefs get() = localePrefs
set(value) { set(value) {
localePrefs = value localePrefs = value
refreshLocale() LocaleSetting.instance.setLocale(value)
} }
var zygisk by dbSettings(Key.ZYGISK, false) var zygisk by dbSettings(Key.ZYGISK, false)

View File

@ -6,22 +6,18 @@ import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
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.StubApk import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.ktx.unwrap import com.topjohnwu.magisk.core.ktx.unwrap
import com.topjohnwu.magisk.core.utils.syncLocale import com.topjohnwu.magisk.core.utils.LocaleSetting
lateinit var AppApkPath: String
fun Resources.addAssetPath(path: String) = StubApk.addAssetPath(this, path) fun Resources.addAssetPath(path: String) = StubApk.addAssetPath(this, path)
fun Resources.patch(): Resources { fun Resources.patch(): Resources {
if (isRunningAsStub) if (isRunningAsStub)
addAssetPath(AppApkPath) addAssetPath(AppApkPath)
syncLocale() LocaleSetting.instance.updateResource(this)
return this return this
} }
@ -40,16 +36,6 @@ fun Context.wrap(): Context {
} }
} }
fun createNewResources(): Resources {
val asset = AssetManager::class.java.newInstance()
val config = Configuration(AppContext.resources.configuration)
val metrics = DisplayMetrics()
metrics.setTo(AppContext.resources.displayMetrics)
val res = Resources(asset, metrics, config)
res.addAssetPath(AppApkPath)
return res
}
fun Class<*>.cmp(pkg: String) = fun Class<*>.cmp(pkg: String) =
ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name) ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name)

View File

@ -6,7 +6,7 @@ import com.topjohnwu.magisk.ProviderInstaller
import com.topjohnwu.magisk.core.BuildConfig import com.topjohnwu.magisk.core.BuildConfig
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.core.utils.LocaleSetting
import okhttp3.Cache import okhttp3.Cache
import okhttp3.ConnectionSpec import okhttp3.ConnectionSpec
import okhttp3.Dns import okhttp3.Dns
@ -68,7 +68,7 @@ fun createOkHttpClient(context: Context): OkHttpClient {
builder.addInterceptor { chain -> builder.addInterceptor { chain ->
val request = chain.request().newBuilder() val request = chain.request().newBuilder()
request.header("User-Agent", "Magisk/${BuildConfig.APP_VERSION_CODE}") request.header("User-Agent", "Magisk/${BuildConfig.APP_VERSION_CODE}")
request.header("Accept-Language", currentLocale.toLanguageTag()) request.header("Accept-Language", LocaleSetting.instance.currentLocale.toLanguageTag())
chain.proceed(request.build()) chain.proceed(request.build())
} }

View File

@ -6,7 +6,6 @@ import android.content.*
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.drawable.AdaptiveIconDrawable import android.graphics.drawable.AdaptiveIconDrawable
@ -19,8 +18,8 @@ import android.view.View
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.topjohnwu.magisk.core.utils.LocaleSetting
import com.topjohnwu.magisk.core.utils.RootUtils import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File import java.io.File
@ -54,9 +53,7 @@ fun ApplicationInfo.getLabel(pm: PackageManager): String {
runCatching { runCatching {
if (labelRes > 0) { if (labelRes > 0) {
val res = pm.getResourcesForApplication(this) val res = pm.getResourcesForApplication(this)
val config = Configuration() LocaleSetting.instance.updateResource(res)
config.setLocale(currentLocale)
res.updateConfiguration(config, res.displayMetrics)
return res.getString(labelRes) return res.getString(labelRes)
} }
} }

View File

@ -1,7 +1,6 @@
package com.topjohnwu.magisk.core.ktx package com.topjohnwu.magisk.core.ktx
import androidx.collection.SparseArrayCompat import androidx.collection.SparseArrayCompat
import com.topjohnwu.magisk.core.utils.currentLocale
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -17,6 +16,7 @@ import java.lang.reflect.Field
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Collections import java.util.Collections
import java.util.Locale
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
@ -98,13 +98,13 @@ fun Long.toTime(format: DateFormat) = format.format(this).orEmpty()
val timeFormatStandard by lazy { val timeFormatStandard by lazy {
SimpleDateFormat( SimpleDateFormat(
"yyyy-MM-dd'T'HH.mm.ss", "yyyy-MM-dd'T'HH.mm.ss",
currentLocale Locale.ROOT
) )
} }
val timeDateFormat: DateFormat by lazy { val timeDateFormat: DateFormat by lazy {
DateFormat.getDateTimeInstance( DateFormat.getDateTimeInstance(
DateFormat.DEFAULT, DateFormat.DEFAULT,
DateFormat.DEFAULT, DateFormat.DEFAULT,
currentLocale Locale.ROOT
) )
} }

View File

@ -0,0 +1,183 @@
package com.topjohnwu.magisk.core.utils
import android.annotation.SuppressLint
import android.app.LocaleConfig
import android.app.LocaleManager
import android.content.ContextWrapper
import android.content.res.Resources
import android.os.Build
import android.os.LocaleList
import androidx.annotation.RequiresApi
import com.topjohnwu.magisk.core.AppApkPath
import com.topjohnwu.magisk.core.AppContext
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.R
import com.topjohnwu.magisk.core.base.relaunch
import com.topjohnwu.magisk.core.isRunningAsStub
import org.xmlpull.v1.XmlPullParser
import java.util.Locale
interface LocaleSetting {
// The locale that is manually overridden, null if system default
val appLocale: Locale?
// The current active locale used in the application
val currentLocale: Locale
fun setLocale(tag: String)
fun updateResource(res: Resources)
private class Api23Impl : LocaleSetting {
private val systemLocale: Locale = Locale.getDefault()
override var currentLocale: Locale = systemLocale
override var appLocale: Locale? = null
init {
setLocale(Config.locale)
}
override fun setLocale(tag: String) {
val locale = when {
tag.isEmpty() -> null
else -> Locale.forLanguageTag(tag)
}
currentLocale = locale ?: systemLocale
appLocale = locale
Locale.setDefault(currentLocale)
updateResource(AppContext.resources)
AppContext.foregroundActivity?.relaunch()
}
@Suppress("DEPRECATION")
override fun updateResource(res: Resources) {
val config = res.configuration
config.setLocale(currentLocale)
res.updateConfiguration(config, null)
}
}
@RequiresApi(24)
private class Api24Impl : LocaleSetting {
private val systemLocaleList = LocaleList.getDefault()
private var currentLocaleList: LocaleList = systemLocaleList
override var appLocale: Locale? = null
override val currentLocale: Locale get() = currentLocaleList[0]
init {
setLocale(Config.locale)
}
override fun setLocale(tag: String) {
val localeList = when {
tag.isEmpty() -> null
else -> LocaleList.forLanguageTags(tag)
}
currentLocaleList = localeList ?: systemLocaleList
appLocale = localeList?.get(0)
LocaleList.setDefault(currentLocaleList)
updateResource(AppContext.resources)
AppContext.foregroundActivity?.relaunch()
}
@Suppress("DEPRECATION")
override fun updateResource(res: Resources) {
val config = res.configuration
config.setLocales(currentLocaleList)
res.updateConfiguration(config, null)
}
}
@RequiresApi(33)
private class Api33Impl : LocaleSetting {
private val lm: LocaleManager = AppContext.getSystemService(LocaleManager::class.java)
override val appLocale: Locale?
get() = lm.applicationLocales.let { if (it.isEmpty) null else it[0] }
override val currentLocale: Locale
get() = appLocale ?: lm.systemLocales[0]
// These following methods should not be used
override fun setLocale(tag: String) {}
override fun updateResource(res: Resources) {}
}
class AppLocaleList(
val names: Array<String>,
val tags: Array<String>
)
@SuppressLint("NewApi")
companion object {
val available: AppLocaleList by lazy {
val names = ArrayList<String>()
val tags = ArrayList<String>()
names.add(AppContext.getString(R.string.system_default))
tags.add("")
if (Build.VERSION.SDK_INT >= 34) {
// Use platform LocaleConfig parser
val config = localeConfig
val list = config.supportedLocales ?: LocaleList.getEmptyLocaleList()
names.ensureCapacity(list.size() + 1)
tags.ensureCapacity(list.size() + 1)
for (i in 0 until list.size()) {
val locale = list[i]
names.add(locale.getDisplayName(locale))
tags.add(locale.toLanguageTag())
}
} else {
// Manually parse locale_config.xml
val parser = AppContext.resources.getXml(R.xml.locale_config)
while (true) {
when (parser.next()) {
XmlPullParser.START_TAG -> {
if (parser.name == "locale") {
val tag = parser.getAttributeValue(0)
val locale = Locale.forLanguageTag(tag)
names.add(locale.getDisplayName(locale))
tags.add(tag)
}
}
XmlPullParser.END_DOCUMENT -> break
}
}
}
AppLocaleList(names.toTypedArray(), tags.toTypedArray())
}
@get:RequiresApi(34)
val localeConfig: LocaleConfig by lazy {
val context = if (isRunningAsStub) {
val pkgInfo = AppContext.packageManager.getPackageArchiveInfo(AppApkPath, 0)!!
object : ContextWrapper(AppContext) {
override fun getApplicationInfo() = pkgInfo.applicationInfo
}
} else {
AppContext
}
LocaleConfig.fromContextIgnoringOverride(context)
}
val useLocaleManager get() =
if (isRunningAsStub) Build.VERSION.SDK_INT >= 34
else Build.VERSION.SDK_INT >= 33
val instance: LocaleSetting by lazy {
// Initialize available locale list
available
if (useLocaleManager) {
Api33Impl()
} else if (Build.VERSION.SDK_INT <= 23) {
Api23Impl()
} else {
Api24Impl()
}
}
}
}

View File

@ -1,88 +0,0 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.core.utils
import android.annotation.SuppressLint
import android.content.res.Configuration
import android.content.res.Resources
import com.topjohnwu.magisk.core.AppContext
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.R
import com.topjohnwu.magisk.core.base.relaunch
import com.topjohnwu.magisk.core.createNewResources
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Locale
var currentLocale: Locale = Locale.getDefault()
@SuppressLint("ConstantLocale")
val defaultLocale: Locale = Locale.getDefault()
private var cachedLocales: Pair<Array<String>, Array<String>>? = null
suspend fun availableLocales() = cachedLocales ?:
withContext(Dispatchers.Default) {
val compareId = R.string.app_changelog
// Create a completely new resource to prevent cross talk over active configs
val res = createNewResources()
fun changeLocale(locale: Locale) {
res.configuration.setLocale(locale)
res.updateConfiguration(res.configuration, res.displayMetrics)
}
val locales = ArrayList<String>().apply {
// Add default locale
add("en")
// Add some special locales
add("zh-TW")
add("pt-BR")
// Then add all supported locales
addAll(Resources.getSystem().assets.locales)
}.map {
Locale.forLanguageTag(it)
}.distinctBy {
changeLocale(it)
res.getString(compareId)
}.sortedWith { a, b ->
a.getDisplayName(a).compareTo(b.getDisplayName(b), true)
}
changeLocale(defaultLocale)
val defName = res.getString(R.string.system_default)
val names = ArrayList<String>(locales.size + 1)
val values = ArrayList<String>(locales.size + 1)
names.add(defName)
values.add("")
locales.forEach { locale ->
names.add(locale.getDisplayName(locale))
values.add(locale.toLanguageTag())
}
(names.toTypedArray() to values.toTypedArray()).also { cachedLocales = it }
}
fun Resources.setConfig(config: Configuration) {
config.setLocale(currentLocale)
updateConfiguration(config, null)
}
fun Resources.syncLocale() = setConfig(configuration)
fun refreshLocale() {
val localeConfig = Config.locale
currentLocale = when {
localeConfig.isEmpty() -> defaultLocale
else -> Locale.forLanguageTag(localeConfig)
}
Locale.setDefault(currentLocale)
AppContext.resources.syncLocale()
AppContext.foregroundActivity?.relaunch()
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="ar" />
<locale android:name="ast" />
<locale android:name="az" />
<locale android:name="be" />
<locale android:name="bg" />
<locale android:name="bn" />
<locale android:name="ca" />
<locale android:name="cs" />
<locale android:name="de" />
<locale android:name="el" />
<locale android:name="en" />
<locale android:name="es" />
<locale android:name="et" />
<locale android:name="fa" />
<locale android:name="fr" />
<locale android:name="hi" />
<locale android:name="hr" />
<locale android:name="hu" />
<locale android:name="in" />
<locale android:name="it" />
<locale android:name="iw" />
<locale android:name="ja" />
<locale android:name="ka" />
<locale android:name="kk" />
<locale android:name="ko" />
<locale android:name="lt" />
<locale android:name="mk" />
<locale android:name="ml" />
<locale android:name="nb" />
<locale android:name="nl" />
<locale android:name="pa" />
<locale android:name="pl" />
<locale android:name="pt-BR" />
<locale android:name="pt-PT" />
<locale android:name="ro" />
<locale android:name="ru" />
<locale android:name="sk" />
<locale android:name="sq" />
<locale android:name="sr" />
<locale android:name="sv" />
<locale android:name="sw" />
<locale android:name="ta" />
<locale android:name="th" />
<locale android:name="tr" />
<locale android:name="uk" />
<locale android:name="vi" />
<locale android:name="zh-CN" />
<locale android:name="zh-TW" />
</locale-config>