fastlane: Android locale changing is broken

Using fastlane 2.197.0 and Screengrab 2.1.1 and when doing so on my device, I’m getting the following exception:

             LocaleUtil  E  Failed to change device locale to tools.fastlane.screengrab.locale.LocaleListCompat@94127e4
                         E  java.lang.ClassNotFoundException: $Proxy1
                         E      at java.lang.Class.classForName(Native Method)
                         E      at java.lang.Class.forName(Class.java:454)
                         E      at java.lang.Class.forName(Class.java:379)
                         E      at tools.fastlane.screengrab.locale.LocaleUtil.changeDeviceLocaleTo(LocaleUtil.java:39)
                         E      at tools.fastlane.screengrab.locale.LocaleTestRule$1.evaluate(LocaleTestRule.java:45)
                         E      at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
                         E      at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:349)
                         E      at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
                         E      at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
                         E      at org.junit.runners.ParentRunner$3.run(ParentRunner.java:314)
                         E      at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
                         E      at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:312)
                         E      at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
                         E      at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:292)
                         E      at org.junit.runners.ParentRunner.run(ParentRunner.java:396)
                         E      at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:162)
                         E      at org.junit.runners.Suite.runChild(Suite.java:128)
                         E      at org.junit.runners.Suite.runChild(Suite.java:27)
                         E      at org.junit.runners.ParentRunner$3.run(ParentRunner.java:314)
                         E      at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
                         E      at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:312)
                         E      at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
                         E      at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:292)
                         E      at org.junit.runners.ParentRunner.run(ParentRunner.java:396)
                         E      at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
                         E      at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
                         E      at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
                         E      at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:444)
                         E      at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189)
                         E  Caused by: java.lang.ClassNotFoundException: Didn't find class "$Proxy1" on path: DexPathList[[zip file "/system/framework/android.test.runner.jar", zip file "/system/framework/android.test.mock
                            .jar", zip file "/data/app/com.vanniktech.boardmoney.test-gyKOXKh28FCUnNo7iRFN6A==/base.apk", zip file "/data/app/com.vanniktech.boardmoney-MYfhZojA65BjJ6VYcg1g-Q==/base.apk"],nativeLibraryDirec
                            tories=[/data/app/com.vanniktech.boardmoney.test-gyKOXKh28FCUnNo7iRFN6A==/lib/x86, /data/app/com.vanniktech.boardmoney-MYfhZojA65BjJ6VYcg1g-Q==/lib/x86, /system/lib, /system/product/lib]]
                         E      at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:196)
                         E      at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
                         E      at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
                         E      ... 29 more

I’ve been trying to isolate this into a reproduction project but wasn’t successful to do so. Do you know what the reason behind this could be?

I’m running an 29 emulator with target/compileSdk set to 31 and building with Gradle 7.2 & AGP 7.0.3

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 26 (10 by maintainers)

Most upvoted comments

We have the same issue, and after investigating, we found out that this is not a bug of screengrab, but instead it was a conflict with LeakCanary. LeakCanary replaces the default IActivityManager with with a proxy instance here, which causes the ClassNotFoundException when screengrab tries to access it using reflection.

So check if you are using LeakCanary or check for other libraries that may be doing the same thing.

For LeakCanary, the fix is to avoid installing it on test builds, for now, we are doing it by using leakcanary-android-core artifact, and handling the initialization manually on non-test builds, but I believe there are other ways to handle this by using a specific testBuildType.

I guess I should probably fix it 🙃 I’ll see if I can figure out what’s going on and get a fix out this week

It’s still broken.

Commenting just because this was not fixed 😄

@vanniktech The problem is that LocaleTestRule is using Configuration#updateConfiguration() in LocaleUtil method, that since API 28 belongs to the non-sdk interfaces.

There are 2 ways to overcome the problem.

  1. via adb One should execute the following commands before executing the screengrab script, depending on the API level:

For Android 9 (API level 28)

adb shell settings put global hidden_api_policy_pre_p_apps 1
adb shell settings put global hidden_api_policy_p_apps 1

For Android 10 (API level 29) or higher

adb shell settings put global hidden_api_policy 1
  1. In Java code, by using libraries like AndroidHiddenApiBypass You can find a fixed version of fastlane’s LocaleUtil.java here

The interesting part is this:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
   HiddenApiBypass.invoke(
      amnClass,
      activityManagerNative,
      "updateConfiguration",
      config
    );
} else {
      Method updateConfigurationMethod =
           amnClass.getMethod("updateConfiguration", Configuration.class);
      updateConfigurationMethod.setAccessible(true);
      updateConfigurationMethod.invoke(activityManagerNative, config);
}

@fastlane-bot If you prefer to go for the second solution, which is more practical as a fastlane user, I’d be glad to create a PR

Still somewhat of an issue.

Yes still an issue.