diff --git a/app/src/main/java/com/topjohnwu/magisk/data/database/MagiskDB.java b/app/src/main/java/com/topjohnwu/magisk/data/database/MagiskDB.java index b2a515eff..5ea518388 100644 --- a/app/src/main/java/com/topjohnwu/magisk/data/database/MagiskDB.java +++ b/app/src/main/java/com/topjohnwu/magisk/data/database/MagiskDB.java @@ -146,7 +146,7 @@ public class MagiskDB { String dateString = null, newString; for (ContentValues values : SQL("SELECT * FROM %s ORDER BY time DESC", LOG_TABLE)) { Date date = new Date(values.getAsLong("time")); - newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date); + newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.getLocale()).format(date); if (!TextUtils.equals(dateString, newString)) { dateString = newString; list = new ArrayList<>(); diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/SuLogEntry.java b/app/src/main/java/com/topjohnwu/magisk/model/entity/SuLogEntry.java index aa778d65e..a63159f9b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/SuLogEntry.java +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/SuLogEntry.java @@ -47,10 +47,10 @@ public class SuLogEntry { } public String getDateString() { - return DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date); + return DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.getLocale()).format(date); } public String getTimeString() { - return new SimpleDateFormat("h:mm a", LocaleManager.locale).format(date); + return new SimpleDateFormat("h:mm a", LocaleManager.getLocale()).format(date); } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt index b92f804e2..24df89c3d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt @@ -6,7 +6,6 @@ import android.text.TextUtils import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import com.topjohnwu.magisk.* -import com.topjohnwu.magisk.utils.LocaleManager import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Shortcuts @@ -45,9 +44,6 @@ open class SplashActivity : AppCompatActivity() { } } - // Dynamic detect all locales - LocaleManager.loadAvailableLocales(R.string.app_changelog) - // Set default configs Config.initialize() diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/base/BasePreferenceFragment.java b/app/src/main/java/com/topjohnwu/magisk/ui/base/BasePreferenceFragment.java index 8ae2bb7a1..aff051778 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/base/BasePreferenceFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/ui/base/BasePreferenceFragment.java @@ -10,7 +10,6 @@ import android.view.ViewGroup; import com.topjohnwu.magisk.App; import com.topjohnwu.magisk.R; -import com.topjohnwu.magisk.utils.Event; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; @@ -21,7 +20,7 @@ import androidx.preference.PreferenceViewHolder; import androidx.recyclerview.widget.RecyclerView; public abstract class BasePreferenceFragment extends PreferenceFragmentCompat - implements SharedPreferences.OnSharedPreferenceChangeListener, Event.AutoListener { + implements SharedPreferences.OnSharedPreferenceChangeListener { public App app = App.self; @@ -29,22 +28,15 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = super.onCreateView(inflater, container, savedInstanceState); app.getPrefs().registerOnSharedPreferenceChangeListener(this); - Event.register(this); return v; } @Override public void onDestroyView() { app.getPrefs().unregisterOnSharedPreferenceChangeListener(this); - Event.unregister(this); super.onDestroyView(); } - @Override - public int[] getListeningEvents() { - return new int[0]; - } - @Override protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { return new PreferenceGroupAdapter(preferenceScreen) { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.java b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.java index 6cee06511..298cdb5e4 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.java @@ -1,5 +1,6 @@ package com.topjohnwu.magisk.ui.settings; +import android.annotation.SuppressLint; import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; @@ -14,7 +15,6 @@ import com.topjohnwu.magisk.Const; import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.ui.base.BasePreferenceFragment; import com.topjohnwu.magisk.utils.DownloadApp; -import com.topjohnwu.magisk.utils.Event; import com.topjohnwu.magisk.utils.FingerprintHelper; import com.topjohnwu.magisk.utils.LocaleManager; import com.topjohnwu.magisk.utils.PatchAPK; @@ -25,7 +25,6 @@ import com.topjohnwu.superuser.Shell; import java.io.IOException; import java.util.Arrays; -import java.util.Locale; import androidx.appcompat.app.AlertDialog; import androidx.preference.ListPreference; @@ -33,6 +32,9 @@ import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreferenceCompat; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import kotlin.Pair; public class SettingsFragment extends BasePreferenceFragment { @@ -115,6 +117,8 @@ public class SettingsFragment extends BasePreferenceFragment { return true; }); + setLocalePreference((ListPreference) findPreference(Config.Key.LOCALE)); + /* We only show canary channels if user is already on canary channel * or the user have already chosen canary channel */ if (!Utils.isCanary() && @@ -168,19 +172,36 @@ public class SettingsFragment extends BasePreferenceFragment { } } + @SuppressWarnings("ResultOfMethodCallIgnored") + @SuppressLint("CheckResult") private void setLocalePreference(ListPreference lp) { - CharSequence[] entries = new CharSequence[LocaleManager.locales.size() + 1]; - CharSequence[] entryValues = new CharSequence[LocaleManager.locales.size() + 1]; - entries[0] = LocaleManager.getString(LocaleManager.defaultLocale, R.string.system_default); - entryValues[0] = ""; - int i = 1; - for (Locale locale : LocaleManager.locales) { - entries[i] = locale.getDisplayName(locale); - entryValues[i++] = LocaleManager.toLanguageTag(locale); - } - lp.setEntries(entries); - lp.setEntryValues(entryValues); - lp.setSummary(LocaleManager.locale.getDisplayName(LocaleManager.locale)); + lp.setSummary("Loading"); + lp.setEnabled(false); + LocaleManager.getAvailableLocales() + .flattenAsFlowable(locales -> locales) + .map(locale -> new Pair<>(locale.getDisplayName(locale), LocaleManager.toLanguageTag(locale))) + .toList() + .map(list -> { + CharSequence[] names = new CharSequence[list.size() + 1]; + CharSequence[] values = new CharSequence[list.size() + 1]; + + names[0] = LocaleManager.getString(LocaleManager.getDefaultLocale(), R.string.system_default); + values[0] = ""; + int i = 1; + for (Pair item : list) { + names[i] = item.getFirst(); + values[i++] = item.getSecond(); + } + return new Pair<>(names, values); + }) + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(it -> { + lp.setEnabled(true); + lp.setEntries(it.getFirst()); + lp.setEntryValues(it.getSecond()); + lp.setSummary(LocaleManager.getLocale().getDisplayName(LocaleManager.getLocale())); + }, Throwable::printStackTrace); } @Override @@ -198,7 +219,8 @@ public class SettingsFragment extends BasePreferenceFragment { if (prefs.getBoolean(key, false)) { try { Const.MAGISK_DISABLE_FILE.createNewFile(); - } catch (IOException ignored) {} + } catch (IOException ignored) { + } } else { Const.MAGISK_DISABLE_FILE.delete(); } @@ -252,27 +274,27 @@ public class SettingsFragment extends BasePreferenceFragment { break; case Config.Key.ROOT_ACCESS: rootConfig.setSummary(getResources() - .getStringArray(R.array.su_access)[(int)Config.get(key)]); + .getStringArray(R.array.su_access)[(int) Config.get(key)]); break; case Config.Key.SU_AUTO_RESPONSE: autoRes.setSummary(getResources() - .getStringArray(R.array.auto_response)[(int)Config.get(key)]); + .getStringArray(R.array.auto_response)[(int) Config.get(key)]); break; case Config.Key.SU_NOTIFICATION: suNotification.setSummary(getResources() - .getStringArray(R.array.su_notification)[(int)Config.get(key)]); + .getStringArray(R.array.su_notification)[(int) Config.get(key)]); break; case Config.Key.SU_REQUEST_TIMEOUT: requestTimeout.setSummary( - getString(R.string.request_timeout_summary, (int)Config.get(key))); + getString(R.string.request_timeout_summary, (int) Config.get(key))); break; case Config.Key.SU_MULTIUSER_MODE: multiuserConfig.setSummary(getResources() - .getStringArray(R.array.multiuser_summary)[(int)Config.get(key)]); + .getStringArray(R.array.multiuser_summary)[(int) Config.get(key)]); break; case Config.Key.SU_MNT_NS: nsConfig.setSummary(getResources() - .getStringArray(R.array.namespace_summary)[(int)Config.get(key)]); + .getStringArray(R.array.namespace_summary)[(int) Config.get(key)]); break; } } @@ -286,16 +308,4 @@ public class SettingsFragment extends BasePreferenceFragment { setSummary(Config.Key.SU_MULTIUSER_MODE); setSummary(Config.Key.SU_MNT_NS); } - - @Override - @Deprecated - public void onEvent(int event) { - setLocalePreference((ListPreference) findPreference(Config.Key.LOCALE)); - } - - @Override - @Deprecated - public int[] getListeningEvents() { - return new int[] {Event.LOCALE_FETCH_DONE}; - } } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.java b/app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.java deleted file mode 100644 index c9cae156d..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.topjohnwu.magisk.utils; - -import android.content.Context; -import android.content.ContextWrapper; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Build; -import android.text.TextUtils; - -import com.topjohnwu.magisk.App; -import com.topjohnwu.magisk.Config; -import com.topjohnwu.superuser.Shell; -import com.topjohnwu.superuser.internal.InternalUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; - -import androidx.annotation.StringRes; - -public class LocaleManager { - public static Locale locale = Locale.getDefault(); - public static final Locale defaultLocale = Locale.getDefault(); - public static List locales; - - public static Locale forLanguageTag(String tag) { - if (Build.VERSION.SDK_INT >= 21) { - return Locale.forLanguageTag(tag); - } else { - String[] tok = tag.split("-"); - if (tok.length == 0) { - return new Locale(""); - } - String language; - switch (tok[0]) { - case "und": - language = ""; // Undefined - break; - case "fil": - language = "tl"; // Filipino - break; - default: - language = tok[0]; - } - if (language.length() != 2 && language.length() != 3) - return new Locale(""); - if (tok.length == 1) - return new Locale(language); - String country = tok[1]; - if (country.length() != 2 && country.length() != 3) - return new Locale(language); - return new Locale(language, country); - } - } - - public static String toLanguageTag(Locale loc) { - if (Build.VERSION.SDK_INT >= 21) { - return loc.toLanguageTag(); - } else { - String language = loc.getLanguage(); - String country = loc.getCountry(); - String variant = loc.getVariant(); - if (language.isEmpty() || !language.matches("\\p{Alpha}{2,8}")) { - language = "und"; // Follow the Locale#toLanguageTag() implementation - } else if (language.equals("iw")) { - language = "he"; // correct deprecated "Hebrew" - } else if (language.equals("in")) { - language = "id"; // correct deprecated "Indonesian" - } else if (language.equals("ji")) { - language = "yi"; // correct deprecated "Yiddish" - } - // ensure valid country code, if not well formed, it's omitted - if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}")) { - country = ""; - } - - // variant subtags that begin with a letter must be at least 5 characters long - if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}")) { - variant = ""; - } - StringBuilder tag = new StringBuilder(language); - if (!country.isEmpty()) - tag.append('-').append(country); - if (!variant.isEmpty()) - tag.append('-').append(variant); - return tag.toString(); - } - } - - public static void setLocale(ContextWrapper wrapper) { - String localeConfig = Config.get(Config.Key.LOCALE); - if (TextUtils.isEmpty(localeConfig)) { - locale = defaultLocale; - } else { - locale = forLanguageTag(localeConfig); - } - Locale.setDefault(locale); - InternalUtils.replaceBaseContext(wrapper, getLocaleContext(locale)); - } - - public static Context getLocaleContext(Context context, Locale locale) { - Configuration config = new Configuration(context.getResources().getConfiguration()); - config.setLocale(locale); - return context.createConfigurationContext(config); - } - - public static Context getLocaleContext(Locale locale) { - return getLocaleContext(App.self.getBaseContext(), locale); - } - - public static String getString(Locale locale, @StringRes int id) { - return getLocaleContext(locale).getString(id); - } - - @Deprecated - public static void loadAvailableLocales(@StringRes int compareId) { - Shell.EXECUTOR.execute(() -> { - locales = new ArrayList<>(); - HashSet set = new HashSet<>(); - Resources res = App.self.getResources(); - Locale locale; - - // Add default locale - locales.add(Locale.ENGLISH); - set.add(getString(Locale.ENGLISH, compareId)); - - // Add some special locales - locales.add(Locale.TAIWAN); - set.add(getString(Locale.TAIWAN, compareId)); - locale = new Locale("pt", "BR"); - locales.add(locale); - set.add(getString(locale, compareId)); - - // Other locales - for (String s : res.getAssets().getLocales()) { - locale = forLanguageTag(s); - if (set.add(getString(locale, compareId))) { - locales.add(locale); - } - } - - Collections.sort(locales, (a, b) -> a.getDisplayName(a).compareTo(b.getDisplayName(b))); - Event.trigger(Event.LOCALE_FETCH_DONE); - }); - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.kt b/app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.kt new file mode 100644 index 000000000..692adccbb --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/LocaleManager.kt @@ -0,0 +1,135 @@ +package com.topjohnwu.magisk.utils + +import android.content.Context +import android.content.ContextWrapper +import android.content.res.Configuration +import android.content.res.Resources +import android.os.Build +import androidx.annotation.StringRes +import com.topjohnwu.magisk.App +import com.topjohnwu.magisk.Config +import com.topjohnwu.magisk.R +import com.topjohnwu.superuser.internal.InternalUtils +import io.reactivex.Single +import java.util.* + +object LocaleManager { + @JvmStatic + var locale = Locale.getDefault() + @JvmStatic + val defaultLocale = Locale.getDefault() + + @JvmStatic + val availableLocales = Single.fromCallable { + val compareId = R.string.app_changelog + val res: Resources by inject() + mutableListOf().apply { + // Add default locale + add(Locale.ENGLISH) + + // Add some special locales + add(Locale.TAIWAN) + add(Locale("pt", "BR")) + + // Other locales + val otherLocales = res.assets.locales + .map { forLanguageTag(it) } + .distinctBy { getString(it, compareId) } + + listOf("", "").toTypedArray() + + addAll(otherLocales) + }.sortedWith(Comparator { a, b -> + a.getDisplayName(a).toLowerCase(a) + .compareTo(b.getDisplayName(b).toLowerCase(b)) + }) + }.cache() + + private fun forLanguageTag(tag: String): Locale { + if (Build.VERSION.SDK_INT >= 21) { + return Locale.forLanguageTag(tag) + } else { + val tok = tag.split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (tok.isEmpty()) { + return Locale("") + } + val language = when (tok[0]) { + "und" -> "" // Undefined + "fil" -> "tl" // Filipino + else -> tok[0] + } + if (language.length != 2 && language.length != 3) + return Locale("") + if (tok.size == 1) + return Locale(language) + val country = tok[1] + + return if (country.length != 2 && country.length != 3) Locale(language) + else Locale(language, country) + } + } + + @JvmStatic + fun toLanguageTag(loc: Locale): String { + if (Build.VERSION.SDK_INT >= 21) { + return loc.toLanguageTag() + } else { + var language = loc.language + var country = loc.country + var variant = loc.variant + when { + language.isEmpty() || !language.matches("\\p{Alpha}{2,8}".toRegex()) -> + language = "und" // Follow the Locale#toLanguageTag() implementation + language == "iw" -> language = "he" // correct deprecated "Hebrew" + language == "in" -> language = "id" // correct deprecated "Indonesian" + language == "ji" -> language = "yi" // correct deprecated "Yiddish" + } + // ensure valid country code, if not well formed, it's omitted + + // variant subtags that begin with a letter must be at least 5 characters long + // ensure valid country code, if not well formed, it's omitted + if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}".toRegex())) { + country = "" + } + + // variant subtags that begin with a letter must be at least 5 characters long + if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}".toRegex())) { + variant = "" + } + val tag = StringBuilder(language) + if (country.isNotEmpty()) + tag.append('-').append(country) + if (variant.isNotEmpty()) + tag.append('-').append(variant) + return tag.toString() + } + } + + @JvmStatic + fun setLocale(wrapper: ContextWrapper) { + val localeConfig = Config.get(Config.Key.LOCALE) + locale = when { + localeConfig.isNullOrEmpty() -> defaultLocale + else -> forLanguageTag(localeConfig) + } + Locale.setDefault(locale) + InternalUtils.replaceBaseContext(wrapper, getLocaleContext(locale)) + } + + @JvmStatic + fun getLocaleContext(context: Context, locale: Locale): Context { + val config = Configuration(context.resources.configuration) + config.setLocale(locale) + return context.createConfigurationContext(config) + } + + @JvmStatic + fun getLocaleContext(locale: Locale): Context { + return getLocaleContext(App.self.baseContext, locale) + } + + @JvmStatic + fun getString(locale: Locale, @StringRes id: Int): String { + return getLocaleContext(locale).getString(id) + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java index 3033e9093..af954af5f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java @@ -71,7 +71,7 @@ public class Utils { if (info.labelRes > 0) { Resources res = pm.getResourcesForApplication(info); Configuration config = new Configuration(); - config.setLocale(LocaleManager.locale); + config.setLocale(LocaleManager.getLocale()); res.updateConfiguration(config, res.getDisplayMetrics()); return res.getString(info.labelRes); }