configuration.setLocale(locale) doesn't work with AppCompatDelegate.setDefaultNightMode - android

If I set darkmode with AppCompatDelegate.setDefaultNightMode and the system is not dark, then Configuration.setLocale doesn't work.
I change the locale, for example, from En to It, all the strings are still displayed in the system language.
There are no problems if I set the same NightMode of the system (Android 10).
The same problem with android 9 or less: if I set darkmode in my app and I change the context language, the activity displays text based on the language of the system.

Kotlin solution
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
overrideConfiguration?.let {
val uiMode = it.uiMode
it.setTo(baseContext.resources.configuration)
it.uiMode = uiMode
}
super.applyOverrideConfiguration(overrideConfiguration)
}
Java solution
#Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
if (overrideConfiguration != null) {
int uiMode = overrideConfiguration.uiMode;
overrideConfiguration.setTo(getBaseContext().getResources().getConfiguration());
overrideConfiguration.uiMode = uiMode;
}
super.applyOverrideConfiguration(overrideConfiguration);
}

Related

How to detect device is tablet or foldable?

I need to determine if device is tablet or foldable or phone.
I am using the following code to define the tablet:
public boolean isTablet(Context context) {
boolean xlarge = ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 4);
boolean large = ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE);
return (xlarge || large);
}
I tested this code on several devices, it worked correctly. But I'm still not sure that this is a hundred percent working option.
But I have another problem. I have not found a way to determine foldable devices.
Is it possible to determine whether the device is foldable or not?
To make your app fold aware, use Jetpack Window Manager, a library that provides a common API surface for different window features, like folds or a hinge. When your app is fold aware, the content in the window can be adapted to avoid folds and hinges or to take advantage of them and use them as natural separators.
It has now support for folding phones through WindowInfoTracker and FoldingFeature. WindowMetricsCalculator to help calculate the current WindowMetrics.
Add this dependency
implementation "androidx.window:window:1.0.0"
class DisplayFeaturesActivity : AppCompatActivity() {
private lateinit var binding: ActivityDisplayFeaturesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this#DisplayFeaturesActivity)
.windowLayoutInfo(this#DisplayFeaturesActivity)
.collect { newLayoutInfo ->
// Use newLayoutInfo to update the layout.
}
}
}
}
}

Android Push Notification intent not localizing app Activity with custom in-app Locale

I have an app feature that allows users to choose their own custom in-app language (separate from the device language).
Language updates fine when I open the app by tapping the app icon. I used this logic to change the base context:
override fun attachBaseContext(newBase: Context) {
val updatedContext = LocaleUtils.getLocalizedContextWrapper(getInAppLocale(newBase), locale)
super.attachBaseContext(updatedContext)
}
fun getLocalizedContextWrapper(context: Context, targetLocale: Locale): ContextWrapper {
return LocaleUtils(getLocalizedContext(context, targetLocale))
}
fun getLocalizedContext(context: Context, targetLocale: Locale): Context {
val newConfig = getLocalizedConfig(context, targetLocale)
return context.createConfigurationContext(newConfig)
}
private fun getLocalizedConfig(context: Context, targetLocale: Locale): Configuration {
val resources: Resources = context.resources
val config: Configuration = resources.configuration
config.setLocale(targetLocale)
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
val localeList = LocaleList(targetLocale)
LocaleList.setDefault(localeList)
config.setLocales(localeList)
}
else -> {
Locale.setDefault(targetLocale)
}
}
return config
}
I also ensured the Activity destination is a new task:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
But whenever I get a Push Notification and I tap on that to navigate to the app, the content is not localized. I can verify this by having a different language set by the device system settings. When I kill the app and restart it though, the app takes the custom in-app language from my shared preference as normal.
Also, when I tap the Push Notification, I can see that my BaseActivity is calling attachBaseContext() and is updating the correct in-app locale.
Has anyone encountered this problem before? Any help would be much appreciated.
Thank you so much in advance!

AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM not working

Edit: Updated in the bottom
I'm calling this in Application's onCreate: AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
Then when I'm going to the device's settings (Settings -> Display -> Night mode (switch: on/off)) then I'm resuming my application, the theme is not applied. It doesn't matter if I'm turning ON or OFF the night mode in the device's settings, the theme is not applied.
I also added a breakpoint and I checked that the following is returning me false even if the Dark Mode is ON from device's settings (Note: the app was started with Dark mode OFF).
fun isNightMode(app: Application): Boolean {
return when(app.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_NO -> false
Configuration.UI_MODE_NIGHT_YES -> true
else -> false
}
}
It looks like the application's resource doesn't get updated when I'm changing the theme from device settings.
For debug purpose I override the following function in the Application class:
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
}
and it's getting called.
Edit: It looks like this is causing the problem. Having this in the Application class:
override fun attachBaseContext(base: Context) {
val locale = Locale("de", "DE")
Locale.setDefault(locale)
val resources = base.resources
val configuration = Configuration(resources.configuration)
configuration.setLocale(locale)
val baseContext = base.createConfigurationContext(configuration)
super.attachBaseContext(baseContext)
}
If I remove the code above it's working.
Just to provide a standalone answer here.
Thanks to #Zbarcea Christian and #Prashanth for their comments.
The problem for me was the same: overriding attachBaseContext() method. So, fixed it like this:
override fun attachBaseContext(cxt: Context) {
val resources = cxt.resources
val configuration = Configuration(resources.configuration)
// Required for the day/night theme to work
configuration.uiMode = Configuration.UI_MODE_NIGHT_UNDEFINED
val baseContext = cxt.createConfigurationContext(configuration)
// set locale and so on ...
super.attachBaseContext(baseContext)
}

Change Locale not work after migrate to Androidx

I have an old project that supports multi-languages. I want to upgrade support library and target platform, Before migrating to Androidx everything works fine but now change language not work!
I use this code to change default locale of App
private static Context updateResources(Context context, String language)
{
Locale locale = new Locale(language);
Locale.setDefault(locale);
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
return context.createConfigurationContext(configuration);
}
And call this method on each activity by override attachBaseContext like this:
#Override
protected void attachBaseContext(Context newBase)
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
String language = preferences.getString(SELECTED_LANGUAGE, "fa");
super.attachBaseContext(updateResources(newBase, language));
}
I try other method to get string and I noticed that ‍‍‍‍getActivity().getBaseContext().getString work and getActivity().getString not work. Even the following code does not work and always show app_name vlaue in default resource string.xml.
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/app_name"/>
I share a sample code in https://github.com/Freydoonk/LanguageTest
Also getActivity()..getResources().getIdentifier not work and always return 0!
UPDATE Sep 5 2022:
AppCompat 1.5.0 was released, which includes these changes, specifically:
This stable version includes improvements to night mode stability
[...]
Fixes an issue where AppCompat’s context wrapper reused the application context's backing resource implementation, resulting in uiMode being overwritten on the application context. (Idf9d5)
Digging deeper inside the code reveals several crucial changes which essentially mean that, similar to before, if you're not using a ContextWrapper or ContextThemeWrapper AND don't offer users to set a different night mode than they've set on their device THEN all context wrapping and theming and locale updates should now work properly and you should be able to safely remove all your workarounds explained below or any other hacks you might have in place.
Unfortunately, if you're using any kind of ContextWrapper OR allowing users to manually set the night mode of your app, then there still seem to be issues, I'm not sure why Google has such trouble fixing this. The problem I've faced: If uiMode is NOT overwritten anymore, that means when you:
set your device to dark mode,
set your app to light mode (night mode disabled),
rotate your phone to landscape,
lock your screen,
unlock your screen again while still in landscape,
then depending on your device and possibly Android version you may see the application context's uiMode being stale and ending up with awesome black on black and white on white theming glitches. This also happens for device in light mode and app in night mode. In addition, your app's locale may be reset to the device locale. In these cases you need to use the solution described below.
Working solution for AppCompat 1.2.0-1.5.0 when encountering issues with locale or day/night:
If you use a ContextWrapper or ContextThemeWrapper inside attachBaseContext in AppCompat 1.2.0-1.4.2, locale changes will break, because when you pass your wrapped context to super,
the 1.2.0-1.4.2 AppCompatActivity makes internal calls which wrap your ContextWrapper in another ContextThemeWrapper and AppCompatDelegateImpl thus ending up in your locale being ignored,
or if you use a ContextThemeWrapper, overrides its configuration to a blank one, similar to what happened back in 1.1.0.
Whether you use a context wrapper or not in 1.5.0, as described in my latest update, there may still be theme glitches and app locale being reset. The solution either way is always the same, nothing else worked for me. The big obstacle is that, unlike in 1.1.0, applyOverrideConfiguration is called on your base context, not your host activity, so you can't just override that method in your activity and fix the locale (or uiMode) as you could in 1.1.0. The only working solution I'm aware of is to reverse the wrapping by overriding getDelegate() to make sure your wrapping and/or locale override comes last. First, you add the class below:
Kotlin sample (please note that the class MUST be inside the androidx.appcompat.app package because the only existing AppCompatDelegate constructor is package private)
package androidx.appcompat.app
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.util.AttributeSet
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.Toolbar
class BaseContextWrappingDelegate(private val superDelegate: AppCompatDelegate) : AppCompatDelegate() {
override fun getSupportActionBar() = superDelegate.supportActionBar
override fun setSupportActionBar(toolbar: Toolbar?) = superDelegate.setSupportActionBar(toolbar)
override fun getMenuInflater(): MenuInflater? = superDelegate.menuInflater
override fun onCreate(savedInstanceState: Bundle?) {
superDelegate.onCreate(savedInstanceState)
removeActivityDelegate(superDelegate)
addActiveDelegate(this)
}
override fun onPostCreate(savedInstanceState: Bundle?) = superDelegate.onPostCreate(savedInstanceState)
override fun onConfigurationChanged(newConfig: Configuration?) = superDelegate.onConfigurationChanged(newConfig)
override fun onStart() = superDelegate.onStart()
override fun onStop() = superDelegate.onStop()
override fun onPostResume() = superDelegate.onPostResume()
override fun setTheme(themeResId: Int) = superDelegate.setTheme(themeResId)
override fun <T : View?> findViewById(id: Int) = superDelegate.findViewById<T>(id)
override fun setContentView(v: View?) = superDelegate.setContentView(v)
override fun setContentView(resId: Int) = superDelegate.setContentView(resId)
override fun setContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.setContentView(v, lp)
override fun addContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.addContentView(v, lp)
override fun attachBaseContext2(context: Context) = wrap(superDelegate.attachBaseContext2(super.attachBaseContext2(context)))
override fun setTitle(title: CharSequence?) = superDelegate.setTitle(title)
override fun invalidateOptionsMenu() = superDelegate.invalidateOptionsMenu()
override fun onDestroy() {
superDelegate.onDestroy()
removeActivityDelegate(this)
}
override fun getDrawerToggleDelegate() = superDelegate.drawerToggleDelegate
override fun requestWindowFeature(featureId: Int) = superDelegate.requestWindowFeature(featureId)
override fun hasWindowFeature(featureId: Int) = superDelegate.hasWindowFeature(featureId)
override fun startSupportActionMode(callback: ActionMode.Callback) = superDelegate.startSupportActionMode(callback)
override fun installViewFactory() = superDelegate.installViewFactory()
override fun createView(parent: View?, name: String?, context: Context, attrs: AttributeSet): View? = superDelegate.createView(parent, name, context, attrs)
override fun setHandleNativeActionModesEnabled(enabled: Boolean) {
superDelegate.isHandleNativeActionModesEnabled = enabled
}
override fun isHandleNativeActionModesEnabled() = superDelegate.isHandleNativeActionModesEnabled
override fun onSaveInstanceState(outState: Bundle?) = superDelegate.onSaveInstanceState(outState)
override fun applyDayNight() = superDelegate.applyDayNight()
override fun setLocalNightMode(mode: Int) {
superDelegate.localNightMode = mode
}
override fun getLocalNightMode() = superDelegate.localNightMode
private fun wrap(context: Context): Context {
TODO("your wrapping implementation here")
}
}
Then inside our base activity class (make sure you remove any other previous workarounds first) add this code:
private var baseContextWrappingDelegate: AppCompatDelegate? = null
override fun getDelegate() = baseContextWrappingDelegate ?: BaseContextWrappingDelegate(super.getDelegate()).apply {
baseContextWrappingDelegate = this
}
// OPTIONAL createConfigurationContext and/or onStart code below may be needed depending on your ContextWrapper implementation to avoid issues with themes
override fun createConfigurationContext(overrideConfiguration: Configuration) : Context {
val context = super.createConfigurationContext(overrideConfiguration)
TODO("your wrapping implementation here")
}
private fun fixStaleConfiguration() {
// we only want to fix the configuration if our app theme is different than the system theme, otherwise it will result in an infinite configuration change loop causing a StackOverflowError and crashing your app
if (AppCompatDelegate.getDefaultNightMode() != AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
applicationContext?.configuration?.uiMode = resources.configuration.uiMode
TODO("your locale updating implementation here")
// OPTIONAL if you are using a context wrapper, the wrapper might have stale resources with wrong uiMode and/or locale which you need to clear or update at this stage
}
override fun onRestart() {
fixStaleConfiguration()
super.onRestart()
}
// OPTIONAL if you have specified configChanges in your manifest, especially orientation and uiMode
override fun onConfigurationChanged(newConfig: Configuration) {
fixStaleConfiguration()
super.onConfigurationChanged(newConfig)
}
For an example how to successfully "clear stale resources", which may or may not be needed in your case:
(context as? ContextThemeWrapper)?.run {
if (mContextThemeWrapperResources == null) {
mContextThemeWrapperResources = ContextThemeWrapper::class.java.getDeclaredField("mResources")
mContextThemeWrapperResources!!.isAccessible = true
}
mContextThemeWrapperResources!!.set(this, null)
} ?: (context as? androidx.appcompat.view.ContextThemeWrapper)?.run {
if (mAppCompatContextThemeWrapperResources == null) {
mAppCompatContextThemeWrapperResources = androidx.appcompat.view.ContextThemeWrapper::class.java.getDeclaredField("mResources")
mAppCompatContextThemeWrapperResources!!.isAccessible = true
}
mAppCompatContextThemeWrapperResources!!.set(this, null)
}
(context as? AppCompatActivity)?.run {
if (mAppCompatActivityResources == null) {
mAppCompatActivityResources = AppCompatActivity::class.java.getDeclaredField("mResources")
mAppCompatActivityResources!!.isAccessible = true
}
mAppCompatActivityResources!!.set(this, null)
}
OLD ANSWER AND CONFIRMED WORKING SOLUTION FOR APPCOMPAT 1.1.0:
Basically what's happening in the background is that while you've set the configuration correctly in attachBaseContext, the AppCompatDelegateImpl then goes and overrides the configuration to a completely fresh configuration without a locale:
final Configuration conf = new Configuration();
conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
try {
...
((android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
handled = true;
} catch (IllegalStateException e) {
...
}
In an unreleased commit by Chris Banes this was actually fixed: The new configuration is a deep copy of the base context's configuration.
final Configuration conf = new Configuration(baseConfiguration);
conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
try {
...
((android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
handled = true;
} catch (IllegalStateException e) {
...
}
Until this is released, it's possible to do the exact same thing manually. To continue using version 1.1.0 add this below your attachBaseContext:
Kotlin solution
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
if (overrideConfiguration != null) {
val uiMode = overrideConfiguration.uiMode
overrideConfiguration.setTo(baseContext.resources.configuration)
overrideConfiguration.uiMode = uiMode
}
super.applyOverrideConfiguration(overrideConfiguration)
}
Java solution
#Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
if (overrideConfiguration != null) {
int uiMode = overrideConfiguration.uiMode;
overrideConfiguration.setTo(getBaseContext().getResources().getConfiguration());
overrideConfiguration.uiMode = uiMode;
}
super.applyOverrideConfiguration(overrideConfiguration);
}
This code does exactly the same what Configuration(baseConfiguration) does under the hood, but because we are doing it after the AppCompatDelegate has already set the correct uiMode, we have to make sure to take the overridden uiMode over to after we fix it so we don't lose the dark/light mode setting.
Please note that this only works by itself if you don't specify configChanges="uiMode" inside your manifest. If you do, then there's yet another bug: Inside onConfigurationChanged the newConfig.uiMode won't be set by AppCompatDelegateImpl's onConfigurationChanged. This can be fixed as well if you copy all the code AppCompatDelegateImpl uses to calculate the current night mode to your base activity code and then override it before the super.onConfigurationChanged call. In Kotlin it would look like this:
private var activityHandlesUiMode = false
private var activityHandlesUiModeChecked = false
private val isActivityManifestHandlingUiMode: Boolean
get() {
if (!activityHandlesUiModeChecked) {
val pm = packageManager ?: return false
activityHandlesUiMode = try {
val info = pm.getActivityInfo(ComponentName(this, javaClass), 0)
info.configChanges and ActivityInfo.CONFIG_UI_MODE != 0
} catch (e: PackageManager.NameNotFoundException) {
false
}
}
activityHandlesUiModeChecked = true
return activityHandlesUiMode
}
override fun onConfigurationChanged(newConfig: Configuration) {
if (isActivityManifestHandlingUiMode) {
val nightMode = if (delegate.localNightMode != AppCompatDelegate.MODE_NIGHT_UNSPECIFIED)
delegate.localNightMode
else
AppCompatDelegate.getDefaultNightMode()
val configNightMode = when (nightMode) {
AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES
AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO
else -> applicationContext.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
}
newConfig.uiMode = configNightMode or (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
}
super.onConfigurationChanged(newConfig)
}
Finally, I find the problem in my app. When migrating the project to Androidx dependencies of my project changed like this:
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.1.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0-alpha04'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-alpha02'
}
As it is seen, version of androidx.appcompat:appcompat is 1.1.0-alpha03 when I changed it to the latest stable version, 1.0.2, my problem is resolved and the change language working properly.
I find the latest stable version of appcompat library in Maven Repository. I also change other libraries to the latest stable version.
Now my app dependencies section is like bellow:
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
There is an issue in new app compat libraries related to night mode that is causing to override the configuration on android 21 to 25.
This can be fixed by applying your configuration when this public function is called:
public void applyOverrideConfiguration(Configuration overrideConfiguration
For me, this little trick has worked by copying the settings from the overridden configuration to my configuration but you can do whatever you want. It is better to reapply your language logic to the new configuration to minimize errors
#Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
if (Build.VERSION.SDK_INT >= 21&& Build.VERSION.SDK_INT <= 25) {
//Use you logic to update overrideConfiguration locale
Locale locale = getLocale()//your own implementation here;
overrideConfiguration.setLocale(locale);
}
super.applyOverrideConfiguration(overrideConfiguration);
}
I am using "androidx.appcompat:appcompat:1.3.0-alpha01" but I suppose it will also work on Version 1.2.0.
The following code is based on Android Code Search.
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import java.util.*
open class MyBaseActivity :AppCompatActivity(){
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
val config = Configuration()
applyOverrideConfiguration(config)
}
override fun applyOverrideConfiguration(newConfig: Configuration) {
super.applyOverrideConfiguration(updateConfigurationIfSupported(newConfig))
}
open fun updateConfigurationIfSupported(config: Configuration): Configuration? {
// Configuration.getLocales is added after 24 and Configuration.locale is deprecated in 24
if (Build.VERSION.SDK_INT >= 24) {
if (!config.locales.isEmpty) {
return config
}
} else {
if (config.locale != null) {
return config
}
}
// Please Get your language code from some storage like shared preferences
val languageCode = "fa"
val locale = Locale(languageCode)
if (locale != null) {
// Configuration.setLocale is added after 17 and Configuration.locale is deprecated
// after 24
if (Build.VERSION.SDK_INT >= 17) {
config.setLocale(locale)
} else {
config.locale = locale
}
}
return config
}
}
Late answer but I thought might be helpful. As of androidx.appcompat:appcompat:1.2.0-beta01 The solution of 0101100101 overriding applyOverrideConfiguration no longer works on me. Instead, in then overriden attacheBaseContext, you have to call the applyOverrideConfiguration() without overriding it.
override fun attachBaseContext(newBase: Context) {
val newContext = LocaleHelper.getUpdatedContext(newBase)
super.attachBaseContext(newContext)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1){
applyOverrideConfiguration(newContext.resources.configuration)
}
}
It's just a shame that his solution only works on 1.1.0. Base on my research this should have been officially fix. It's just weird that this bug still here. I know I use the beta but for someone who wants to use the latest, this solution for me is working.
Tested on emulator api level 21-25. Above that api level, you don't have to worry about it.
Finally i got solution for locate, In my case actually issue was with bundle apk because it split the locate files. In bundle apk by default all splits will be generated. but within the android block of your build.gradle file you are able to declare which splits will be generated.
bundle {
language {
// Specifies that the app bundle should not support
// configuration APKs for language resources. These
// resources are instead packaged with each base and
// dynamic feature APK.
enableSplit = false
}
}
After adding this code to android block of build.gradle file my issue get resolved.
The androidx.appcompat:appcompat:1.1.0 bug can also be resolved by simply calling getResources() in Activity.applyOverrideConfiguration()
#Override public void
applyOverrideConfiguration(Configuration cfgOverride)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// add this to fix androidx.appcompat:appcompat 1.1.0 bug
// which happens on Android 6.x ~ 7.x
getResources();
}
super.applyOverrideConfiguration(cfgOverride);
}
Try something like this:
public class MyActivity extends AppCompatActivity {
public static final float CUSTOM_FONT_SCALE = 4.24f;
public static final Locale CUSTOM_LOCALE = Locale.CANADA_FRENCH; // or whatever
#Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(useCustomConfig(newBase));
}
private Context useCustomConfig(Context context) {
Locale.setDefault(CUSTOM_LOCALE);
if (Build.VERSION.SDK_INT >= 17) {
Configuration config = new Configuration();
config.fontScale = CUSTOM_FONT_SCALE;
config.setLocale(CUSTOM_LOCALE);
return context.createConfigurationContext(config);
} else {
Resources res = context.getResources();
Configuration config = new Configuration(res.getConfiguration());
config.fontScale = CUSTOM_FONT_SCALE;
config.locale = CUSTOM_LOCALE;
res.updateConfiguration(config, res.getDisplayMetrics());
return context;
}
}
}
Sources: issuetracker comment and the first sample linked from the issuetracker comment.
Whilst the above is working fine for me, another option from the second sample linked from the issuetracker comment is as follows (I haven't personally tried this out):
#RequiresApi(17)
public class MyActivity extends AppCompatActivity {
public static final float CUSTOM_FONT_SCALE = 4.24f;
public static final Locale CUSTOM_LOCALE = Locale.CANADA_FRENCH; // or whatever
#Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
Configuration config = new Configuration();
config.fontScale = CUSTOM_FONT_SCALE;
applyOverrideConfiguration(config);
}
#Override
public void applyOverrideConfiguration(Configuration newConfig) {
super.applyOverrideConfiguration(updateConfigurationIfSupported(newConfig));
}
private Configuration updateConfigurationIfSupported(Configuration config) {
if (Build.VERSION.SDK_INT >= 24) {
if (!config.getLocales().isEmpty()) {
return config;
}
} else {
if (config.locale != null) {
return config;
}
}
Locale locale = CUSTOM_LOCALE;
if (locale != null) {
if (Build.VERSION.SDK_INT >= 17) {
config.setLocale(locale);
} else {
config.locale = locale;
}
}
return config;
}
}
Now the language does not change with these libraries:
androidx.appcompat:appcompat:1.1.0,
androidx.appcompat:appcompat:1.2.0
The problem was only solved in this library:
androidx.appcompat:appcompat:1.3.0-rc01
Now there is a newer version that also works:
implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'
As #Fred mentioned appcompat:1.1.0-alpha03 has a glitch although it's not mentioned on their release versions log
Had the same bug on androidx.appcompat:appcompat:1.1.0. Switched to androidx.appcompat:appcompat:1.1.0-rc01 and now langs change on Android 5-6.
Answer from #0101100101 worked for me.
Only that I used
#Override
public void applyOverrideConfiguration(Configuration overrideConfiguration)
{
if (overrideConfiguration != null) {
int uiMode = overrideConfiguration.uiMode;
overrideConfiguration.setTo(getResources().getConfiguration());
overrideConfiguration.uiMode = uiMode;
}
super.applyOverrideConfiguration(overrideConfiguration);
}
so only getResources() instead of getBaseContext().getResources().
In my case I have extended ContextWrapper with overridden getResources().
But after applyOverrideConfiguration is called I can't access my custom getResources. I get standard ones instead.
If I use the code above everything works fine.
Solution:
In the App level Gradle, I included the following code within the android division,
bundle {
language {
// Specifies that the app bundle should not support
// configuration APKs for language resources. These
// resources are instead packaged with each base and
// dynamic feature APK.
enableSplit = false
}
}
https://medium.com/dwarsoft/how-to-provide-languages-dynamically-using-app-bundle-567d2ec32be6
1. Method you may use in attachBaseContext()
private void setLanguage(Context mContext, String localeName) {
Locale myLocale = new Locale(localeName);
Resources res = mContext.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
Configuration conf = res.getConfiguration();
conf.locale = myLocale;
res.updateConfiguration(conf, dm);
}
2. Override in activities
#Override
protected void attachBaseContext(Context newBase) {
setLanguage(newBase, "your language");
super.attachBaseContext(newBase);
}
NB: This is working fine for me after I recreate the activity

How to know if Android TalkBack is active?

I'm developing an application that uses TalkBack to guide people through it. However, in those situations I want to have some subtile differences in the layout of the application so navigation is easier and also have extra voice outputs (with TextToSpeech) to help guide the user.
My problem is that I only want those changes and extra outputs if the user has TalkBack active.
Is there any way to know if it is? I didn't find anything specific to access TalkBack settings directly, but I was hoping there was some form of accessing general phone settings that could let me know what I need.
The recommended way of doing this is to query the AccessibilityManager for the enabled state of accessibility services.
AccessibilityManager am = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
boolean isAccessibilityEnabled = am.isEnabled();
boolean isExploreByTouchEnabled = am.isTouchExplorationEnabled();
Novoda have released a library called accessibilitools which does this check. It queries the accessibility manager to check if there are any accessibility services enabled that support the "spoken feedback" flag.
AccessibilityServices services = AccessibilityServices.newInstance(context);
services.isSpokenFeedbackEnabled();
public boolean isSpokenFeedbackEnabled() {
List<AccessibilityServiceInfo> enabledServices = getEnabledServicesFor(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
return !enabledServices.isEmpty();
}
private List<AccessibilityServiceInfo> getEnabledServicesFor(int feedbackTypeFlags) {
return accessibilityManager.getEnabledAccessibilityServiceList(feedbackTypeFlags);
}
You can create an inline function in kotlin like:
fun Context.isScreenReaderOn():Boolean{
val am = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
if (am != null && am.isEnabled) {
val serviceInfoList =
am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN)
if (!serviceInfoList.isEmpty())
return true
}
return false}
And then you can just call it whenever you need it like:
if(context.isScreenReaderOn()){
...
}
Tested and works fine for now.
For an example, look at isScreenReaderActive() in HomeLauncher.java file in the Eyes-Free shell application (via groups thread).
To sum up: you detect all screen readers with Intents, then query the status provider of each to see if it is active.
If you really want to limit it to TalkBack only, you could try checking the ResolveInfo.serviceInfo.packageName for each result returned from queryIntentServices() to see if it matches the TalkBack package.
AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
if (am != null && am.isEnabled()) {
List<AccessibilityServiceInfo> serviceInfoList = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
if (!serviceInfoList.isEmpty())
return true;
}
return false;
For me, I solved this problem in this way , it works well in my project:
use getEnabledAccessibilityServiceList() to get all Accessibility service, a service whose status is open will be in this list
Talkback contain a activity named com.android.talkback.TalkBackPreferencesActivity, you can traversing the list to find whether the talkback service is open
The detailed code below:
private static final String TALKBACK_SETTING_ACTIVITY_NAME = "com.android.talkback.TalkBackPreferencesActivity";
public static boolean accessibilityEnable(Context context) {
boolean enable = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
try {
AccessibilityManager manager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
List<AccessibilityServiceInfo> serviceList = manager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
for (AccessibilityServiceInfo serviceInfo : serviceList) {
String name = serviceInfo.getSettingsActivityName();
if (!TextUtils.isEmpty(name) && name.equals(TALKBACK_SETTING_ACTIVITY_NAME)) {
enable = true;
}
}
} catch (Exception e) {
if (Logging.isDebugLogging()) {
e.printStackTrace();
}
}
}
return enable;
}
Thanks to #david-z answer (https://stackoverflow.com/a/41357058/2713403) I made this approach to know if the Android Accessibility Suite by Google is enabled
/**
* This method checks if Google Talkback is enabled by using the [accessibilityManager]
*/
private fun isGoogleTalkbackActive(accessibilityManager : AccessibilityManager) : Boolean
{
val accessibilityServiceInfoList = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN)
for (accessibilityServiceInfo in accessibilityServiceInfoList)
{
if ("com.google.android.marvin.talkback".equals(accessibilityServiceInfo.resolveInfo.serviceInfo.processName))
{
return true
}
}
return false
}
Remember to register the google URI as constant :) and get the Accessibility Manager instance as #caseyburkhardt says (https://stackoverflow.com/a/12362545/2713403). The difference with the #david-z answer is that I got the Android Accessibility Suite package name instead of its app name because it is more secure. If you want to review if another accessibility suite is enabled (like the Samsung Screen Reader) after this check, you can check
if (accessibilityManager.isTouchExplorationEnabled)
Cheers!
If you are working with compose you can add this utility extension on Context:
#Composable
internal fun Context.collectIsTalkbackEnabledAsState(): State<Boolean> {
val accessibilityManager =
this.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager?
fun isTalkbackEnabled(): Boolean {
val accessibilityServiceInfoList =
accessibilityManager?.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN)
return accessibilityServiceInfoList?.any {
it.resolveInfo.serviceInfo.processName.equals(TALKBACK_PACKAGE_NAME)
} ?: false
}
val talkbackEnabled = remember { mutableStateOf(isTalkbackEnabled()) }
val accessibilityManagerEnabled = accessibilityManager?.isEnabled ?: false
var accessibilityEnabled by remember { mutableStateOf(accessibilityManagerEnabled) }
accessibilityManager?.addAccessibilityStateChangeListener { accessibilityEnabled = it }
LaunchedEffect(accessibilityEnabled) {
talkbackEnabled.value = if (accessibilityEnabled) isTalkbackEnabled() else false
}
return talkbackEnabled
}
private const val TALKBACK_PACKAGE_NAME = "com.google.android.marvin.talkback"
And then use it in the target composable:
#Composable
fun SomeComposable() {
val talkbackEnabled by LocalContext.current.collectIsTalkbackEnabledAsState()
if (talkbackEnabled) {
/** do something here **/
}
}
Open system setting and go to accessibility and tap to off Talk back option

Categories

Resources