From 7173693d1b1a6db1c3e400797dea78f2dedf8ef7 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Wed, 10 Jul 2024 21:50:54 -0700 Subject: [PATCH] Use platform LocaleManager if possible --- app/apk/src/main/AndroidManifest.xml | 2 +- .../magisk/ui/deny/AppProcessInfo.kt | 4 +- .../magisk/ui/settings/SettingsItems.kt | 44 ++--- .../magisk/ui/settings/SettingsViewModel.kt | 20 +- .../magisk/ui/superuser/SuperuserViewModel.kt | 4 +- .../magisk/utils/MotionRevealHelper.kt | 6 +- .../com/topjohnwu/magisk/core/AppContext.kt | 15 +- .../java/com/topjohnwu/magisk/core/Config.kt | 4 +- .../java/com/topjohnwu/magisk/core/Hacks.kt | 18 +- .../topjohnwu/magisk/core/di/Networking.kt | 4 +- .../com/topjohnwu/magisk/core/ktx/XAndroid.kt | 7 +- .../com/topjohnwu/magisk/core/ktx/XJVM.kt | 6 +- .../magisk/core/utils/LocaleSetting.kt | 183 ++++++++++++++++++ .../topjohnwu/magisk/core/utils/Locales.kt | 88 --------- app/core/src/main/res/xml/locale_config.xml | 51 +++++ 15 files changed, 294 insertions(+), 162 deletions(-) create mode 100644 app/core/src/main/java/com/topjohnwu/magisk/core/utils/LocaleSetting.kt delete mode 100644 app/core/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt create mode 100644 app/core/src/main/res/xml/locale_config.xml diff --git a/app/apk/src/main/AndroidManifest.xml b/app/apk/src/main/AndroidManifest.xml index bf89f2d1a..a55c4e523 100644 --- a/app/apk/src/main/AndroidManifest.xml +++ b/app/apk/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ - + ( - { it.label.lowercase(currentLocale) }, + { it.label.lowercase(Locale.ROOT) }, { it.info.packageName } ) } diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt index b18b712a3..a248dce2b 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt @@ -14,18 +14,16 @@ import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.ktx.activity 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.availableLocales -import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding import com.topjohnwu.magisk.databinding.set +import com.topjohnwu.magisk.utils.TextHolder import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.superuser.Shell -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import com.topjohnwu.magisk.core.R as CoreR // --- Customization @@ -35,38 +33,28 @@ object Customization : BaseSettingsItem.Section() { } object Language : BaseSettingsItem.Selector() { + private val names: Array get() = LocaleSetting.available.names + private val tags: Array get() = LocaleSetting.available.tags + override var value - get() = index + get() = tags.indexOf(Config.locale) set(value) { - index = value - Config.locale = entryValues[value] + Config.locale = tags[value] } override val title = CoreR.string.language.asText() - private var entries = emptyArray() - private var entryValues = emptyArray() - private var index = -1 + override fun entries(res: Resources) = names + override fun descriptions(res: Resources) = names +} - 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) { - scope.launch { - availableLocales().let { (names, values) -> - entries = names - entryValues = values - val selectedLocale = currentLocale.getDisplayName(currentLocale) - index = names.indexOfFirst { it == selectedLocale }.let { if (it == -1) 0 else it } - notifyPropertyChanged(BR.description) - } +object LanguageSystem : BaseSettingsItem.Blank() { + override val title = CoreR.string.language.asText() + override val description: TextHolder + get() { + val locale = LocaleSetting.instance.appLocale + return locale?.getDisplayName(locale)?.asText() ?: CoreR.string.system_default.asText() } - } } object Theme : BaseSettingsItem.Blank() { diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt index f06a176d4..f9415abb0 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt @@ -1,6 +1,10 @@ package com.topjohnwu.magisk.ui.settings +import android.app.Activity +import android.content.Intent +import android.net.Uri import android.os.Build +import android.provider.Settings import android.view.View import android.widget.Toast 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.toast import com.topjohnwu.magisk.core.tasks.HideAPK +import com.topjohnwu.magisk.core.utils.LocaleSetting import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.events.AddHomeIconEvent import com.topjohnwu.magisk.events.AuthEvent @@ -30,12 +35,6 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler { it.put(BR.handler, this) } - init { - viewModelScope.launch { - Language.loadLanguages(this) - } - } - private fun createItems(): List { val context = AppContext val hidden = context.packageName != BuildConfig.APP_PACKAGE_NAME @@ -43,7 +42,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler { // Customization val list = mutableListOf( Customization, - Theme, Language + Theme, if (LocaleSetting.useLocaleManager) LanguageSystem else Language ) if (isRunningAsStub && ShortcutManagerCompat.isRequestPinShortcutSupported(context)) list.add(AddShortcut) @@ -98,6 +97,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler { SystemlessHosts -> createHosts() Hide, Restore -> withInstallPermission(andThen) AddShortcut -> AddHomeIconEvent().publish() + LanguageSystem -> launchAppLocaleSettings(view.activity) 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) { UpdateChannelUrl.refresh() if (UpdateChannelUrl.isEnabled && UpdateChannelUrl.value.isBlank()) { diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt index 05f464415..af24fdde8 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt @@ -16,7 +16,6 @@ import com.topjohnwu.magisk.core.R import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao import com.topjohnwu.magisk.core.ktx.getLabel 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.RvItem import com.topjohnwu.magisk.databinding.bindExtra @@ -30,6 +29,7 @@ import com.topjohnwu.magisk.view.TextItem import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.util.Locale class SuperuserViewModel( private val db: PolicyDao @@ -92,7 +92,7 @@ class SuperuserViewModel( policies.addAll(map) } policies.sortWith(compareBy( - { it.appName.lowercase(currentLocale) }, + { it.appName.lowercase(Locale.ROOT) }, { it.packageName } )) itemsPolicies.update(policies) diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/utils/MotionRevealHelper.kt b/app/apk/src/main/java/com/topjohnwu/magisk/utils/MotionRevealHelper.kt index 5b78818ab..23eb7282c 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/utils/MotionRevealHelper.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/utils/MotionRevealHelper.kt @@ -14,7 +14,7 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator import com.google.android.material.circularreveal.CircularRevealCompat import com.google.android.material.circularreveal.CircularRevealWidget 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 object MotionRevealHelper { @@ -63,7 +63,9 @@ object MotionRevealHelper { it.interpolator = FastOutSlowInInterpolator() 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 targetX = if (revealInfo.radius == 0f) 0f else maxX * rtlMod val moveX = ObjectAnimator.ofFloat(this, View.TRANSLATION_X, targetX) diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/AppContext.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/AppContext.kt index fac9bd286..29e4c55ca 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/AppContext.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/AppContext.kt @@ -2,6 +2,7 @@ package com.topjohnwu.magisk.core import android.app.Activity import android.app.Application +import android.app.LocaleManager import android.content.ComponentCallbacks2 import android.content.Context import android.content.ContextWrapper @@ -13,12 +14,11 @@ import android.system.Os import androidx.profileinstaller.ProfileInstaller import com.topjohnwu.magisk.StubApk 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.ProcessLifecycle import com.topjohnwu.magisk.core.utils.RootUtils 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.superuser.Shell import com.topjohnwu.superuser.internal.UiThreadHandler @@ -31,6 +31,9 @@ import timber.log.Timber import java.lang.ref.WeakReference import kotlin.system.exitProcess +lateinit var AppApkPath: String + private set + object AppContext : ContextWrapper(null), Application.ActivityLifecycleCallbacks, ComponentCallbacks2 { @@ -52,7 +55,7 @@ object AppContext : ContextWrapper(null), } override fun onConfigurationChanged(newConfig: Configuration) { - resources.setConfig(newConfig) + LocaleSetting.instance.updateResource(resources) } override fun onActivityResumed(activity: Activity) { @@ -79,7 +82,6 @@ object AppContext : ContextWrapper(null), } else { base.packageResourcePath } - refreshLocale() resources.patch() val shellBuilder = Shell.Builder.create() @@ -97,6 +99,11 @@ object AppContext : ContextWrapper(null), // Pre-heat the shell ASAP 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() ProcessLifecycle.init(this) NetworkObserver.init(this) diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/Config.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/Config.kt index e593b056b..0f976ddc4 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/Config.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/Config.kt @@ -6,7 +6,7 @@ import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.ktx.writeTo import com.topjohnwu.magisk.core.repository.DBConfig 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.GlobalScope import kotlinx.coroutines.runBlocking @@ -135,7 +135,7 @@ object Config : PreferenceConfig, DBConfig { get() = localePrefs set(value) { localePrefs = value - refreshLocale() + LocaleSetting.instance.setLocale(value) } var zygisk by dbSettings(Key.ZYGISK, false) diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/Hacks.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/Hacks.kt index cbc87795e..671ee918a 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/Hacks.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/Hacks.kt @@ -6,22 +6,18 @@ import android.content.ComponentName import android.content.Context import android.content.ContextWrapper import android.content.Intent -import android.content.res.AssetManager import android.content.res.Configuration import android.content.res.Resources -import android.util.DisplayMetrics import com.topjohnwu.magisk.StubApk import com.topjohnwu.magisk.core.ktx.unwrap -import com.topjohnwu.magisk.core.utils.syncLocale - -lateinit var AppApkPath: String +import com.topjohnwu.magisk.core.utils.LocaleSetting fun Resources.addAssetPath(path: String) = StubApk.addAssetPath(this, path) fun Resources.patch(): Resources { if (isRunningAsStub) addAssetPath(AppApkPath) - syncLocale() + LocaleSetting.instance.updateResource(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) = ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name) diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/di/Networking.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/di/Networking.kt index a5f69ccfd..3ad9db593 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/di/Networking.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/di/Networking.kt @@ -6,7 +6,7 @@ import com.topjohnwu.magisk.ProviderInstaller import com.topjohnwu.magisk.core.BuildConfig import com.topjohnwu.magisk.core.Config 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.ConnectionSpec import okhttp3.Dns @@ -68,7 +68,7 @@ fun createOkHttpClient(context: Context): OkHttpClient { builder.addInterceptor { chain -> val request = chain.request().newBuilder() 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()) } diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XAndroid.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XAndroid.kt index dd711d192..11fb43d93 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XAndroid.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XAndroid.kt @@ -6,7 +6,6 @@ import android.content.* import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager -import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.drawable.AdaptiveIconDrawable @@ -19,8 +18,8 @@ import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Toast 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.currentLocale import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.superuser.internal.UiThreadHandler import java.io.File @@ -54,9 +53,7 @@ fun ApplicationInfo.getLabel(pm: PackageManager): String { runCatching { if (labelRes > 0) { val res = pm.getResourcesForApplication(this) - val config = Configuration() - config.setLocale(currentLocale) - res.updateConfiguration(config, res.displayMetrics) + LocaleSetting.instance.updateResource(res) return res.getString(labelRes) } } diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XJVM.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XJVM.kt index 0c70309b0..7ebb5f80a 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XJVM.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XJVM.kt @@ -1,7 +1,6 @@ package com.topjohnwu.magisk.core.ktx import androidx.collection.SparseArrayCompat -import com.topjohnwu.magisk.core.utils.currentLocale import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -17,6 +16,7 @@ import java.lang.reflect.Field import java.text.DateFormat import java.text.SimpleDateFormat import java.util.Collections +import java.util.Locale import java.util.zip.ZipEntry import java.util.zip.ZipInputStream @@ -98,13 +98,13 @@ fun Long.toTime(format: DateFormat) = format.format(this).orEmpty() val timeFormatStandard by lazy { SimpleDateFormat( "yyyy-MM-dd'T'HH.mm.ss", - currentLocale + Locale.ROOT ) } val timeDateFormat: DateFormat by lazy { DateFormat.getDateTimeInstance( DateFormat.DEFAULT, DateFormat.DEFAULT, - currentLocale + Locale.ROOT ) } diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/utils/LocaleSetting.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/utils/LocaleSetting.kt new file mode 100644 index 000000000..b995ecb4d --- /dev/null +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/utils/LocaleSetting.kt @@ -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, + val tags: Array + ) + + @SuppressLint("NewApi") + companion object { + val available: AppLocaleList by lazy { + val names = ArrayList() + val tags = ArrayList() + + 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() + } + } + } +} diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt deleted file mode 100644 index c8a57da2c..000000000 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt +++ /dev/null @@ -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>? = 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().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(locales.size + 1) - val values = ArrayList(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() -} diff --git a/app/core/src/main/res/xml/locale_config.xml b/app/core/src/main/res/xml/locale_config.xml new file mode 100644 index 000000000..7b725ad86 --- /dev/null +++ b/app/core/src/main/res/xml/locale_config.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +