Repository: YarikSOffice/LanguageTest Branch: master Commit: db5b3742bfcc Files: 39 Total size: 48.6 KB Directory structure: gitextract_f07za5s_/ ├── .gitignore ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── yariksoffice/ │ │ └── languagetest/ │ │ ├── App.java │ │ ├── LocaleManager.java │ │ ├── TestService.java │ │ ├── Utility.java │ │ ├── WebViewLocaleHelper.java │ │ └── ui/ │ │ ├── BaseActivity.java │ │ ├── MainActivity.java │ │ ├── SettingsActivity.java │ │ ├── TestActivity1.java │ │ ├── TestActivity2.java │ │ └── WebViewActivity.java │ └── res/ │ ├── drawable/ │ │ ├── language_en.xml │ │ ├── language_ru.xml │ │ └── language_uk.xml │ ├── layout/ │ │ ├── locale_info.xml │ │ ├── main_activity.xml │ │ ├── settings_activity.xml │ │ ├── test_activity_1.xml │ │ ├── test_activity_2.xml │ │ └── web_view_activity.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-ru/ │ │ └── strings.xml │ └── values-uk/ │ └── strings.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea* .DS_Store /build /captures .externalNativeBuild ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 Yaroslav Berezanskyi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Changing the Language in Android Apps The repository contains 2 approaches for changing a locale in android apps. `ignore_deprecation` branch is more easier and flexible approach despite using some deprecated API. Article on Medium: https://proandroiddev.com/change-language-programmatically-at-runtime-on-android-5e6bc15c758 ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 28 buildToolsVersion "28.0.3" defaultConfig { applicationId "com.yariksoffice.languagetest" minSdkVersion 15 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 incremental true } } dependencies { implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/yari/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/App.java ================================================ package com.yariksoffice.languagetest; import android.app.Application; import android.content.Context; import android.content.res.Configuration; import android.util.Log; public class App extends Application { public static final String TAG = "App"; // for the sake of simplicity. use DI in real apps instead public static LocaleManager localeManager; @Override public void onCreate() { super.onCreate(); Utility.bypassHiddenApiRestrictions(); } @Override protected void attachBaseContext(Context base) { localeManager = new LocaleManager(base); super.attachBaseContext(localeManager.setLocale(base)); Log.d(TAG, "attachBaseContext"); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); localeManager.setLocale(this); Log.d(TAG, "onConfigurationChanged: " + newConfig.locale.getLanguage()); } } ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/LocaleManager.java ================================================ package com.yariksoffice.languagetest; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.os.LocaleList; import android.preference.PreferenceManager; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Set; import androidx.annotation.RequiresApi; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.N; public class LocaleManager { public static final String LANGUAGE_ENGLISH = "en"; public static final String LANGUAGE_UKRAINIAN = "uk"; public static final String LANGUAGE_RUSSIAN = "ru"; private static final String LANGUAGE_KEY = "language_key"; private final SharedPreferences prefs; public LocaleManager(Context context) { prefs = PreferenceManager.getDefaultSharedPreferences(context); } public Context setLocale(Context c) { return updateResources(c, getLanguage()); } public Context setNewLocale(Context c, String language) { persistLanguage(language); return updateResources(c, language); } public String getLanguage() { return prefs.getString(LANGUAGE_KEY, LANGUAGE_ENGLISH); } @SuppressLint("ApplySharedPref") private void persistLanguage(String language) { // use commit() instead of apply(), because sometimes we kill the application process // immediately that prevents apply() from finishing prefs.edit().putString(LANGUAGE_KEY, language).commit(); } private Context updateResources(Context context, String language) { Locale locale = new Locale(language); Locale.setDefault(locale); Resources res = context.getResources(); Configuration config = new Configuration(res.getConfiguration()); if (Utility.isAtLeastVersion(N)) { setLocaleForApi24(config, locale); context = context.createConfigurationContext(config); } else if (Utility.isAtLeastVersion(JELLY_BEAN_MR1)) { config.setLocale(locale); context = context.createConfigurationContext(config); } else { config.locale = locale; res.updateConfiguration(config, res.getDisplayMetrics()); } return context; } @RequiresApi(api = N) private void setLocaleForApi24(Configuration config, Locale target) { Set set = new LinkedHashSet<>(); // bring the target locale to the front of the list set.add(target); LocaleList all = LocaleList.getDefault(); for (int i = 0; i < all.size(); i++) { // append other locales supported by the user set.add(all.get(i)); } Locale[] locales = set.toArray(new Locale[0]); config.setLocales(new LocaleList(locales)); } public static Locale getLocale(Resources res) { Configuration config = res.getConfiguration(); return Utility.isAtLeastVersion(N) ? config.getLocales().get(0) : config.locale; } } ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/TestService.java ================================================ package com.yariksoffice.languagetest; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; import androidx.annotation.Nullable; import android.util.Log; import android.widget.Toast; import java.util.Locale; public class TestService extends Service { private final String TAG = "TestService"; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(App.localeManager.setLocale(base)); Log.d(TAG, "attachBaseContext"); } @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate"); Locale locale = LocaleManager.getLocale(getResources()); String message = locale.getLanguage() + " " + Utility.hexString(getResources()); Toast.makeText(this, message, Toast.LENGTH_LONG).show(); stopSelf(); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } } ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/Utility.java ================================================ package com.yariksoffice.languagetest; import android.app.Activity; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.os.Build; import android.util.Log; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; import java.util.Map.Entry; import static android.content.pm.PackageManager.GET_META_DATA; import static android.os.Build.VERSION_CODES.P; import static com.yariksoffice.languagetest.App.TAG; public class Utility { public static String hexString(Resources res) { Object resImpl = getPrivateField("android.content.res.Resources", "mResourcesImpl", res); Object o = resImpl != null ? resImpl : res; return "@" + Integer.toHexString(o.hashCode()); } public static Object getPrivateField(String className, String fieldName, Object object) { try { Class c = Class.forName(className); Field f = c.getDeclaredField(fieldName); f.setAccessible(true); return f.get(object); } catch (Throwable e) { e.printStackTrace(); return null; } } public static void bypassHiddenApiRestrictions() { // http://weishu.me/2019/03/16/another-free-reflection-above-android-p/ if (!isAtLeastVersion(P)) return; try { Method forName = Class.class.getDeclaredMethod("forName", String.class); Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class); Class vmRuntimeClass = (Class) forName.invoke(null, "dalvik.system.VMRuntime"); Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null); Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{ String[].class }); Object sVmRuntime = getRuntime.invoke(null); setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{ new String[]{ "L" } }); } catch (Throwable e) { Log.e(TAG, "Reflect bootstrap failed:", e); } } public static void resetActivityTitle(Activity a) { try { ActivityInfo info = a.getPackageManager().getActivityInfo(a.getComponentName(), GET_META_DATA); if (info.labelRes != 0) { a.setTitle(info.labelRes); } } catch (NameNotFoundException e) { e.printStackTrace(); } } @SuppressWarnings("unchecked") public static String getTitleCache() { try { Object o = Utility.getPrivateField("android.app.ApplicationPackageManager", "sStringCache", null); Map> cache = (Map>) o; if (cache == null) return ""; StringBuilder builder = new StringBuilder("Cache:").append("\n"); for (Entry> e : cache.entrySet()) { CharSequence title = e.getValue().get(); if (title != null) { builder.append(title).append("\n"); } } return builder.toString(); } catch (Exception e) { // https://developer.android.com/about/versions/pie/restrictions-non-sdk-interfaces return "Can't access title cache"; } } public static Resources getTopLevelResources(Activity a) { try { return a.getPackageManager().getResourcesForApplication(a.getApplicationInfo()); } catch (NameNotFoundException e) { throw new RuntimeException(e); } } public static boolean isAtLeastVersion(int version) { return Build.VERSION.SDK_INT >= version; } } ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/WebViewLocaleHelper.java ================================================ package com.yariksoffice.languagetest; import android.content.Context; import android.webkit.WebView; /** * WebViewLocaleHelper implements a workaround that fixes the unwanted side effect while * using a WebView introduced in Android N. * * For unknown reasons, the very first creation of a WebView (either programmatically * or via inflation) resets an application locale to the device default. * More on that: https://issuetracker.google.com/issues/37113860 */ public class WebViewLocaleHelper { private boolean requireWorkaround = true; public void implementWorkaround(Context context) { if (requireWorkaround) { requireWorkaround = false; new WebView(context).destroy(); App.localeManager.setLocale(context); } } } ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/ui/BaseActivity.java ================================================ package com.yariksoffice.languagetest.ui; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.MenuItem; import android.widget.TextView; import com.yariksoffice.languagetest.App; import com.yariksoffice.languagetest.LocaleManager; import com.yariksoffice.languagetest.R; import com.yariksoffice.languagetest.Utility; import java.util.Locale; import static com.yariksoffice.languagetest.LocaleManager.LANGUAGE_ENGLISH; import static com.yariksoffice.languagetest.LocaleManager.LANGUAGE_RUSSIAN; import static com.yariksoffice.languagetest.LocaleManager.LANGUAGE_UKRAINIAN; public abstract class BaseActivity extends AppCompatActivity { private static final String TAG = "BaseActivity"; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(App.localeManager.setLocale(base)); Log.d(TAG, "attachBaseContext"); } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); Utility.resetActivityTitle(this); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { finish(); return true; } else { return super.onOptionsItemSelected(item); } } @Override protected void onResume() { super.onResume(); showResourcesInfo(); TextView tv = findViewById(R.id.cache); tv.setText(Utility.getTitleCache()); } private void showResourcesInfo() { Resources topLevelRes = Utility.getTopLevelResources(this); updateInfo("Top level ", findViewById(R.id.tv1), topLevelRes); Resources appRes = getApplication().getResources(); updateInfo("Application ", findViewById(R.id.tv2), appRes); Resources actRes = getResources(); updateInfo("Activity ", findViewById(R.id.tv3), actRes); TextView tv4 = findViewById(R.id.tv4); String defLanguage = Locale.getDefault().getLanguage(); tv4.setText(String.format("Locale.getDefault() - %s", defLanguage)); tv4.setCompoundDrawablesWithIntrinsicBounds(null, null, getLanguageDrawable(defLanguage), null); } private void updateInfo(String title, TextView tv, Resources res) { Locale l = LocaleManager.getLocale(res); tv.setText(title + Utility.hexString(res) + String.format(" - %s", l.getLanguage())); Drawable icon = getLanguageDrawable(l.getLanguage()); tv.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null); } private Drawable getLanguageDrawable(String language) { switch (language) { case LANGUAGE_ENGLISH: return ContextCompat.getDrawable(this, R.drawable.language_en); case LANGUAGE_UKRAINIAN: return ContextCompat.getDrawable(this, R.drawable.language_uk); case LANGUAGE_RUSSIAN: return ContextCompat.getDrawable(this, R.drawable.language_ru); default: Log.w(TAG, "Unsupported language"); return null; } } } ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/ui/MainActivity.java ================================================ package com.yariksoffice.languagetest.ui; import android.content.Intent; import android.os.Bundle; import androidx.annotation.Nullable; import android.widget.TextView; import com.yariksoffice.languagetest.R; import com.yariksoffice.languagetest.TestService; import com.yariksoffice.languagetest.WebViewLocaleHelper; import java.text.SimpleDateFormat; import java.util.Date; public class MainActivity extends BaseActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); WebViewLocaleHelper helper = new WebViewLocaleHelper(); findViewById(R.id.activity_1).setOnClickListener(v -> startActivity(new Intent(this, TestActivity1.class))); findViewById(R.id.activity_2).setOnClickListener(v -> startActivity(new Intent(this, TestActivity2.class))); findViewById(R.id.web_view).setOnClickListener(v -> { helper.implementWorkaround(this); startActivity(new Intent(this, WebViewActivity.class)); }); findViewById(R.id.service).setOnClickListener(v -> startService(new Intent(this, TestService.class))); findViewById(R.id.settings).setOnClickListener(v -> startActivity(new Intent(this, SettingsActivity.class))); TextView tv = findViewById(R.id.hello); String date = SimpleDateFormat.getDateInstance().format(new Date()); tv.setText(getString(R.string.hello, date)); } } ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/ui/SettingsActivity.java ================================================ package com.yariksoffice.languagetest.ui; import android.content.Intent; import android.os.Bundle; import androidx.annotation.Nullable; import android.widget.Toast; import com.yariksoffice.languagetest.App; import com.yariksoffice.languagetest.R; import static com.yariksoffice.languagetest.LocaleManager.LANGUAGE_ENGLISH; import static com.yariksoffice.languagetest.LocaleManager.LANGUAGE_RUSSIAN; import static com.yariksoffice.languagetest.LocaleManager.LANGUAGE_UKRAINIAN; public class SettingsActivity extends BaseActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.settings_activity); //noinspection ConstantConditions getSupportActionBar().setDisplayHomeAsUpEnabled(true); findViewById(R.id.en).setOnClickListener(v -> setNewLocale(LANGUAGE_ENGLISH, false)); findViewById(R.id.en).setOnLongClickListener(v -> setNewLocale(LANGUAGE_ENGLISH, true)); findViewById(R.id.ukr).setOnClickListener(v -> setNewLocale(LANGUAGE_UKRAINIAN, false)); findViewById(R.id.ukr).setOnLongClickListener(v -> setNewLocale(LANGUAGE_UKRAINIAN, true)); findViewById(R.id.ru).setOnClickListener(v -> setNewLocale(LANGUAGE_RUSSIAN, false)); findViewById(R.id.ru).setOnLongClickListener(v -> setNewLocale(LANGUAGE_RUSSIAN, true)); } private boolean setNewLocale(String language, boolean restartProcess) { App.localeManager.setNewLocale(this, language); Intent i = new Intent(this, MainActivity.class); startActivity(i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)); if (restartProcess) { System.exit(0); } else { Toast.makeText(this, "Activity restarted", Toast.LENGTH_SHORT).show(); } return true; } } ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/ui/TestActivity1.java ================================================ package com.yariksoffice.languagetest.ui; import android.os.Bundle; import androidx.annotation.Nullable; import com.yariksoffice.languagetest.R; public class TestActivity1 extends BaseActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.test_activity_1); //noinspection ConstantConditions getSupportActionBar().setDisplayHomeAsUpEnabled(true); } } ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/ui/TestActivity2.java ================================================ package com.yariksoffice.languagetest.ui; import android.os.Bundle; import androidx.annotation.Nullable; import com.yariksoffice.languagetest.R; public class TestActivity2 extends BaseActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.test_activity_2); //noinspection ConstantConditions getSupportActionBar().setDisplayHomeAsUpEnabled(true); } } ================================================ FILE: app/src/main/java/com/yariksoffice/languagetest/ui/WebViewActivity.java ================================================ package com.yariksoffice.languagetest.ui; import android.os.Bundle; import android.webkit.WebView; import com.yariksoffice.languagetest.R; import androidx.annotation.Nullable; public class WebViewActivity extends BaseActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.web_view_activity); //noinspection ConstantConditions getSupportActionBar().setDisplayHomeAsUpEnabled(true); WebView webView = findViewById(R.id.web_view); webView.loadUrl("https://www.google.com/"); } } ================================================ FILE: app/src/main/res/drawable/language_en.xml ================================================ ================================================ FILE: app/src/main/res/drawable/language_ru.xml ================================================ ================================================ FILE: app/src/main/res/drawable/language_uk.xml ================================================ ================================================ FILE: app/src/main/res/layout/locale_info.xml ================================================ ================================================ FILE: app/src/main/res/layout/main_activity.xml ================================================