Search code examples
androidvirtualboxandroid-permissionsandroid-x86

Android app permission problem running under VirtualBox


I'm trying to run connected tests for my App using and Android ISO installed on VirtualBox. I got Android 7.1 ISO from http://www.android-x86.org/ . It is installed under VirtualBox and seems to be working correctly.

Our app uses PocketSphinx which creates some directories and stores some files in them. The app is working correctly on some Android 7.0 tablets and under the emulator that comes with Android Studio. Some of the time, it works correctly under VirtualBox, but it gets in some strange state where it cannot read or write to the directories it needs.

The AndroidManifest contains this permission (along with others):

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

The permissions show up as enabled in the Settings=>Apps=>[Our App]=>Permissions.

Here is the actual error message:

W/System.err: java.io.FileNotFoundException: /storage/emulated/0/Android/data/com.hcs.android.orconnect/files/sync/cmudict-en-us.dict (No such file or directory)
                  at java.io.FileOutputStream.open(Native Method)
                  at java.io.FileOutputStream.<init>(FileOutputStream.java:221)
                  at java.io.FileOutputStream.<init>(FileOutputStream.java:169)
                  at edu.cmu.pocketsphinx.Assets.copy(Assets.java:224)
                  at edu.cmu.pocketsphinx.Assets.syncAssets(Assets.java:269)

If I set a breakpoint before this call, I can see that all of these return false:

new File("/storage/emulated/0/Android/data/").canRead()
new File("/storage/emulated/0/Android/data/").canWrite()
new File("/storage/emulated/0/Android/data/com.hcs.android.orconnect").canRead()
new File("/storage/emulated/0/Android/data/com.hcs.android.orconnect").canWrite()

However, something created the /storage/emulated/0/Android/data/com.hcs.android.orconnect/files folder. If I manually delete this folder from an adb shell prompt, it will be recreated on the next run of my App test, but the app still has the same problems.

I'm really at a loss as to why I'm having these permission problems. Any ideas on what is going on and how to fix the permissions?

(Note: I don't want to use the Android emulator, because we are running VirtualBox for other reasons and the two won't play with each other.)

(Note: Unsurprisingly, running the app from the debugger shows the same problems I'm seeing when I run the connected test.)

Steps to reproduce:

  1. Set up and start VirtualBox image running the Android 7.1 ISO.
  2. Run adb connect <ip address>
  3. Run ./gradlew connectAndroidTest
  4. Test will pass
  5. Run ./gradlew connectAndroidTest
  6. Test will fail and will fail for all future runs as well.

Work around:

  1. Install the app (if not currently installed)
  2. Twiddle the "Storage" permission in Settings => Apps => [My App]

Solution

  • Tracked the problem down to an inconsistency in Android's handling of permissions.

    Our App's Manifest contained:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    

    This results in /data/system/users/0/runtime-permissions.xml containing:

    <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />^M     
    <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />^M
    

    Read is granted implicitly. However, when writing a connected test, I used the grant permission rules:

    @Rule public GrantPermissionRule permissionRule2 = GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
    

    This, however, does not implicitly grant read permission. So, when the app was run for the first time on the VM, it was granted WRITE explicitly and READ implicitly. However, after gradlew ran the test for the first time, it would uninstall the app. Then the next test run would reinstall the app, but only grant it WRITE permission per the rules. Then the test fails as described above.

    So, the solution is to ask for both read and write permission in the test:

    @Rule public GrantPermissionRule permissionRule2 = GrantPermissionRule.grant(android.Manifest.permission.READ_EXTERNAL_STORAGE);
    @Rule public GrantPermissionRule permissionRule25 = GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
    

    Never mind that this doesn't match my AndroidManifest.xml file. Google's left and right hand never met, so the behavior is different.

    Thanks, Google! /s