26

I'm trying to add RTL language support in my app (specifically Arabic right now). I'll be supporting English as well. What I've done:

  • Set minSdkVersion to 17
  • Added android:supportsRtl="true" to my application tag in AndroidManifest.xml
  • Switched my left/right attributes to start/end

At first I made these changes manually, then I used Android Studio's "Refactor -> Add RTL Support Where Possible..." menu item.

When I preview my layout files, I can see the RTL preview is properly mirroring the UI; however, even when I use the "Force RTL Layout Direction", my app doesn't display the RTL layout. The system UI is flipped, so that option does work in general.

Is there anything else I need to do to show the RTL layout? I'm hoping I have missed something obvious. I am testing this on an API 21 emulator.

Update

I inherited some of this code. Something might be overriding a setting and forcing this into LTR mode. I made a test application to test out RTL mode and it worked fine. What sort of code could cause the "Force RTL Layout Direction" setting to be ignored (or be overridden)?

Update 2

I've checked that the locale is being set properly, and it is. I also checked the configuration, and ldrtl is set. I verified in the signed apk file that android:supportsRtl made it in and none of the layout files had android:layoutDirection="ltr". I've even tried manually putting android:layoutDirection="rtl" to try to force a layout to mirror, but that didn't work either.`

Update 3

I added another activity to the project, made it the launcher activity, and made sure it isn't connected to any existing code. It is a subclass of Activity. The issue still exists. So theoretically this is a configuration issue. Like I said I checked the AndroidManifest.xml file and all the layout files that are generated, and the RTL support and layout changes all made it in. What could be going wrong with configuration?

Ben Kane
  • 9,331
  • 6
  • 36
  • 58
  • Hi @Ben Kane, did you manage to fix this issue ? I have this exact issue. I force everything to RTL, do everything i can, but when i print out the value of view.getLayoutDirection(), it's still LTR. – namanhams Apr 02 '16 at 10:44
  • Hi @namanhams. Yes, I did! Please see my answer I posted and accepted. Search for &= in the code. The specific line is in the comments. I should really add it to my answer. It was caused by an error in code that was copied from an Android Developers blog post about checking if you're running in debug mode. – Ben Kane Apr 02 '16 at 13:24

2 Answers2

13

Most obscure bug ever. As I mentioned in the question, most of this code was inherited. It ended up being a bitwise operator issue that was screwing up flags for the application. &= was being used instead of & to check if a flag was set.

As noted in the comments, the code was taken from an example in an Android Developers blog post, which explains why so many others have run into this same issue. I filed a bug report, and the blog post has since been silently updated. Here is the original code, which I removed &= from. Do not use the following code as is. You need to change &= to &:

isDebuggable = (appContext.getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE) != 0;
Ben Kane
  • 9,331
  • 6
  • 36
  • 58
  • 1
    Had exactly the same issue. In a class I called I did: `isDebuggable = (appContext.getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE) != 0;` Notice the `&=` instead of `&`. – Arjan Mels Jan 02 '16 at 12:11
  • @Arjan Yes! That's literally exactly what it was! Did you grab that from some buggy example code somewhere? Otherwise if you just accidentally did it it's a strong coincidence. Maybe I should add that code to my answer. I assumed nobody else would have done that. – Ben Kane Jan 02 '16 at 14:38
  • 1
    @arjan possibly from here: http://stackoverflow.com/a/7089300/2448305. That answer originally had the assignment. And it didn't get edited for 2 years :( So many people probably have that bug in their code. I'll expand on this in my answer later today. Not by my computer at the moment – Ben Kane Jan 02 '16 at 14:47
  • 3
    Oh wow. That code is originally found in this android developers blog post: http://android-developers.blogspot.com/2010/09/securing-android-lvl-applications.html. So bad :( I'll see if I can contact them about fixing it – Ben Kane Jan 02 '16 at 14:50
  • Nice detective work! That is indeed probably where I got this line of code from. Thanks for picking this up. – Arjan Mels Jan 03 '16 at 17:50
  • As of this time the blog post has been (silently) updated – Ben Kane Aug 23 '16 at 15:35
  • I see now the blog post update is not silent (not sure if I missed this originally or they added after the fact). "Updated June 23, 2016 - edit made to the "Make your application tamper-resistant" code snippet", which is this snippet. – Ben Kane Jun 09 '17 at 14:50
  • Thank you, This saved the day. Solution and merge request is available [here](https://github.com/WritingMinds/ffmpeg-android-java/issues/184) but no one has made any change to the main repository for at least 4 years now. So [this](https://github.com/youtopin/ffmpeg-android-java) our fixed version of it if anyone needs it. – Sepehr GH Dec 15 '19 at 11:07
2

Try this...

  1. Create a class to maintain global application state.

     public class YourGlobalClass extends Application {
    
       @Override
       public void onCreate() {
       updateLanguage(this, null);
       super.onCreate();
       }
    
     public static void updateLanguage(Context ctx, String lang) {
    
      Configuration cfg = new Configuration();
      LocalSharedManager manager = new LocalSharedManager(ctx);
      String language = manager.GetValueFromSharedPrefs("force_locale");
    
         if (TextUtils.isEmpty(language) && lang == null) {
            cfg.locale = Locale.getDefault();
            String tmp_locale = "";
            tmp_locale = Locale.getDefault().toString().substring(0, 2);
            manager.SaveValueToSharedPrefs("force_locale", tmp_locale);
    
        } else if (lang != null) {
            cfg.locale = new Locale(lang);
            manager.SaveValueToSharedPrefs("force_locale", lang);
    
        } else if (!TextUtils.isEmpty(language)) {
            cfg.locale = new Locale(language);
        }
        ctx.getResources().updateConfiguration(cfg, null);
       }
    
     }
    
  2. Specify global class to your AndroidManifest.xml's tag, which will cause that class to be instantiated with your saved locale, when the process for your application/package is created. Like, android:name="com.your.package.YourGlobalClass" android:supportsRtl="true"

  3. Create following two methods in your MainActivity.java (wherever you want).

     public class MainActivity extends ActionBarActivity{
    
        .......
    
        // Implement OnclickListener for english_locale button
        findViewById(R.id.english_locale).setOnClickListener(new OnClickListener()
          {
    
             @Override
             public void onClick(View v)
             {
                 changeEnglish();
             }
          });
    
         // Implement OnclickListener for arabic_locale button
         findViewById(R.id.arabic_locale).setOnClickListener(new OnClickListener()
            {
    
               @Override
               public void onClick(View v)
                {
                   changeArabic();
                 }
            });
    
         /**
         * Method that Update UI for Arabic locale.
         */
         public void changeArabic() {
              new AsyncTask<Void, Void, Void>() {
    
              @Override
              protected Void doInBackground(Void... params) {
                   String app_locale = "ar";
                   Locale locale = new Locale(app_locale);
                   Locale.setDefault(locale);
    
                   //Configuration to query the current layout direction.
                   Configuration config = new Configuration();
                   config.locale = locale;
                   getResources().updateConfiguration(config,
                     getResources().getDisplayMetrics());
                   Bidi bidi = new Bidi(app_locale,
                     Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT);
                   bidi.isRightToLeft();
                   YourGlobalClass.updateLanguage(getActivity(), "ar");
    
                   //Refreshing current fragment
    
                   Intent i = getActivity().getIntent();
                   startActivity(i);
                   getActivity().finish();
                return null;
               }
    
            }.execute();
          }
    
             /**
             * Method that Update UI for Default(English) locale.
             */
           public void changeEnglish() {
    
               new AsyncTask<Void, Void, Void>() {
    
               @Override
               protected Void doInBackground(Void... params) {
                   String app_locale = "en";
                   Locale locale = new Locale(app_locale);
                   Locale.setDefault(locale);
    
                   //Configuration to query the current layout direction.
                   Configuration config = new Configuration();
                   config.locale = locale;
                   getResources().updateConfiguration(config,
                     getResources().getDisplayMetrics());
                   Bidi bidi = new Bidi(app_locale,
                     Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT);
                   bidi.isLeftToRight();
                   YourGlobalClass.updateLanguage(getActivity(), "en");
    
                   //Refreshing current fragment
                   Intent i = getActivity().getIntent();
                   startActivity(i);
                   getActivity().finish();
    
               return null;
               }
    
            }.execute();
          }
    
        ......
       //MainActivity end
     }
    
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Silambarasan Poonguti
  • 9,386
  • 4
  • 45
  • 38
  • I can't call `getActivity()` from within those `AsyncTasks` – Ben Kane May 21 '15 at 15:37
  • Use this code to refresh activity: Intent intent = getIntent(); finish(); startActivity(intent); – Silambarasan Poonguti May 22 '15 at 04:24
  • This didn't work. I forced the language into arabic with your code, but the layout didn't flip. Either way, I wouldn't want to do that. I need to support English as well. The curious thing is with the `Bidi` part, the layout direction is staying at 0, aka LTR – Ben Kane May 22 '15 at 14:35
  • What I mean is, after the Bidi declaration, the boolean returned by `bidi.isRightToLeft()` is false – Ben Kane May 22 '15 at 14:46
  • try following inside onCreate of your activity : getWindow().getDecorView().setLayoutDirection(View.LAYOUT_DIRECTION_RTL); – abhishesh May 27 '15 at 17:17