oppia / oppia-android

A free, online & offline learning platform to make quality education accessible for all.
https://www.oppia.org
Apache License 2.0
314 stars 517 forks source link

[BUG]: App can apparently crash due to an incompatible IETF BCP-47 language ID #5046

Open BenHenning opened 1 year ago

BenHenning commented 1 year ago

Describe the bug Three devices in the latest release (beta 0.11 RC02) candidate's pre-launch report triggered the following stack trace:

Exception Process: org.oppia.android, PID: 8313
java.lang.RuntimeException: Unable to start activity ComponentInfo{org.oppia.android/org.oppia.android.app.onboarding.OnboardingActivity}: java.lang.IllegalStateException: Invalid ID: # fg.fX@759b4323.
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3555)
  at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:3707)
  at android.app.servertransaction.LaunchActivityItem.execute (LaunchActivityItem.java:83)
  at android.app.servertransaction.TransactionExecutor.executeCallbacks (TransactionExecutor.java:135)
  at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:95)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2220)
  at android.os.Handler.dispatchMessage (Handler.java:107)
  at android.os.Looper.loop (Looper.java:237)
  at android.app.ActivityThread.main (ActivityThread.java:8016)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1076)
Caused by java.lang.IllegalStateException: Invalid ID: # fg.fX@759b4323.
  at org.oppia.android.util.locale.AndroidLocaleFactory$LocaleSource.expectedProfile (AndroidLocaleFactory.java:212)
  at org.oppia.android.util.locale.AndroidLocaleFactory$LocaleSource.toForcedProposal (AndroidLocaleFactory.java:200)
  at org.oppia.android.util.locale.AndroidLocaleFactory$LocaleSource.computeForcedProposal (AndroidLocaleFactory.java:182)
  at org.oppia.android.util.locale.AndroidLocaleFactory$AndroidResourceCompatibilityPreferredChooser.findBestProposal (AndroidLocaleFactory.java:321)
  at org.oppia.android.util.locale.AndroidLocaleFactory$createAndroidLocale$1.apply (AndroidLocaleFactory.java:63)
  at org.oppia.android.util.locale.AndroidLocaleFactory$createAndroidLocale$1.apply (AndroidLocaleFactory.java:22)
  at java.util.concurrent.ConcurrentHashMap.computeIfAbsent (ConcurrentHashMap.java:1716)
  at org.oppia.android.util.locale.AndroidLocaleFactory.createAndroidLocale (AndroidLocaleFactory.java:59)
  at org.oppia.android.util.locale.DisplayLocaleImpl$formattingLocale$2.invoke (DisplayLocaleImpl.java:25)
  at org.oppia.android.util.locale.DisplayLocaleImpl$formattingLocale$2.invoke (DisplayLocaleImpl.java:17)
  at kotlin.SynchronizedLazyImpl.getValue (SynchronizedLazyImpl.java:74)
  at org.oppia.android.util.locale.DisplayLocaleImpl.getFormattingLocale (DisplayLocaleImpl.java)
  at org.oppia.android.util.locale.DisplayLocaleImpl.setAsDefault (DisplayLocaleImpl.java:46)
  at org.oppia.android.domain.locale.LocaleController.setAsDefault (LocaleController.java:239)
  at org.oppia.android.app.translation.ActivityLanguageLocaleHandler.initializeLocaleForActivity (ActivityLanguageLocaleHandler.java:41)
  at org.oppia.android.app.activity.InjectableAppCompatActivity.onInitializeLocalization (InjectableAppCompatActivity.java:77)
  at org.oppia.android.app.activity.InjectableAppCompatActivity.attachBaseContext (InjectableAppCompatActivity.java:33)
  at android.app.Activity.attach (Activity.java:7857)
  at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:3486)

fg.fX corresponds to org.oppia.android.app.model.LanguageSupportDefinition$LanguageId but unfortunately we don't have the actual stringified version of the proto since this is a production build.

The crash happens due to a new check introduced in #5010, and seems to only happen when the primary & secondary languages fail to fallback. What's interesting is this seems to be happening for an IETF BCP-47 language configuration, and the PR above changed this behavior to actually potentially fail (whereas before it silently defaulted). I think it's quite likely this exception is simply exposing an existing breakage in the app.

To Reproduce Unfortunately, we have no idea yet how to reproduce the issue. I've tried running a monkey test (per https://developer.android.com/studio/test/other-testing-tools/monkey) a few times, but haven't yet this particular crash.

Expected behavior The app shouldn't crash for this case--it's an exceptional case that's not expected to be hit.

Demonstration N/A

Environment

Note that each of these devices were running with a Swahili locale which I think is noteworthy given the crash is occurring within core internationalization logic. Unfortunately I don't have a sense of whether any Swahili devices passed (since the pre-launch report doesn't seem to include non-failures).

Additional context Add any other context about the problem here.

BenHenning commented 1 year ago

@adhiamboperes could you look into this issue and see if you can come up with possible scenarios where it might repro & then gauge potential impact?

BenHenning commented 1 year ago

Looked a little into this. I haven't fully created a situation in which it can occur, but I think it has something to do with the set system locale missing a country value (which is possible per https://developer.android.com/reference/java/util/Locale#getCountry()) and the app deciding that it must try to create a forced profile which, when using IETF BCP-47, expects a region code to be present when a region definition is provided per: https://github.com/oppia/oppia-android/blob/979bd098682a02a4cad95cfcd0370757c923dc43/utility/src/main/java/org/oppia/android/util/locale/AndroidLocaleProfile.kt#L76

This being null causes the crash since the last profile being resolved is forced per: https://github.com/oppia/oppia-android/blob/979bd098682a02a4cad95cfcd0370757c923dc43/utility/src/main/java/org/oppia/android/util/locale/AndroidLocaleFactory.kt#L212

So, it seems that a region definition must also be provided and no country present in the matched system locale in order for this situation to occur. More investigation is needed into how that's possible.

adhiamboperes commented 1 year ago

I think it's quite likely this exception is simply exposing an existing breakage in the app.

How does the app handle language selection on region-locked devices? Example case:

Device: Vivo y55s
android version: 6.0.1
Time Zone: Indian standard time
Region: india
Language: us english

Phone is locked to Asia Only. 

The stack trace is different for the crash, but it exposes a problem in the Locale handling logic.

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: org.oppia.android, PID: 1041
    java.lang.NoClassDefFoundError: org.oppia.android.util.locale.AndroidLocaleFactory$createAndroidLocale$1
        at org.oppia.android.util.locale.AndroidLocaleFactory.createAndroidLocale(AndroidLocaleFactory.kt:59)
        at org.oppia.android.util.locale.DisplayLocaleImpl$formattingLocale$2.invoke(DisplayLocaleImpl.kt:25)
        at org.oppia.android.util.locale.DisplayLocaleImpl$

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: org.oppia.android, PID: 1041
    java.lang.NoClassDefFoundError: org.oppia.android.util.locale.AndroidLocaleFactory$createAndroidLocale$1
        at org.oppia.android.util.locale.AndroidLocaleFactory.createAndroidLocale(AndroidLocaleFactory.kt:59)
        at org.oppia.android.util.locale.DisplayLocaleImpl$formattingLocale$2.invoke(DisplayLocaleImpl.kt:25)
        at org.oppia.android.util.locale.DisplayLocaleImpl$formattingLocale$2.invoke(DisplayLocaleImpl.kt:17)
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
        at org.oppia.android.util.locale.DisplayLocaleImpl.getFormattingLocale(DisplayLocaleImpl.kt)
        at org.oppia.android.util.locale.DisplayLocaleImpl.setAsDefault(DisplayLocaleImpl.kt:46)
        at org.oppia.android.domain.locale.LocaleController.setAsDefault(LocaleController.kt:239)
        at org.oppia.android.app.translation.ActivityLanguageLocaleHandler.initializeLocaleForActivity(ActivityLanguageLocaleHandler.kt:41)
        at org.oppia.android.app.activity.InjectableAppCompatActivity.onInitializeLocalization(InjectableAppCompatActivity.kt:77)
        at org.oppia.android.app.activity.InjectableAppCompatActivity.attachBaseContext(InjectableAppCompatActivity.kt:33)
        at android.app.Activity.attach(Activity.java:6288)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2443)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2577)
        at android.app.ActivityThread.access$1000(ActivityThread.java:166)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1414)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5628)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:853)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:737)