Mark
Mark

Reputation: 2562

Accessing Storage From Orchestrated Espresso Test

I have a test apk being built, and I'd like that test apk to be able to read some configuration values from an external file, rather than having to compile that configuration into the apk file.

I have a method in an Espresso TestRule that attempts to do this, but I appear to be being stymied by the external storage permission changes. The following code results in a permissions error on SDK 28 and 29, and on SDK 30 I get a security exception

Permission android.permission.MANAGE_EXTERNAL_STORAGE requested by $myPackageNameHere is not a changeable permission type

val instrumentation = InstrumentationRegistry.getInstrumentation()
val packageName =  instrumentation.context.packageName
   with(instrumentation.uiAutomation) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
         grantRuntimePermission(packageName, Manifest.permission.MANAGE_EXTERNAL_STORAGE)
      }
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
         grantRuntimePermission(packageName, Manifest.permission.READ_EXTERNAL_STORAGE)
         grantRuntimePermission(packageName, Manifest.permission.WRITE_EXTERNAL_STORAGE)
      }

   }

val dir = Environment.getExternalStorageDirectory()
val configFile = File(dir, "test-config.json")

FileInputStream(configFile).use {
   config = json.decodeFromStream(it)
}

I've tried getting the external files directory for my test's context, but it's always null (I've tried calling it multiple times, it doesn't help, still null. And I've checked the mounted state, and the external storage is mounted.

instrumentation.context.getExternalFilesDir(null)
val dir = instrumentation.context.getExternalFilesDir(null)

I can get the external files directory of the target context:

instrumentation.targetContext.getExternalFilesDir(null)
val dir = instrumentation.targetContext.getExternalFilesDir(null)

However I'm also using the orchestrator with clearPackageData: true, so this directory gets wiped, and it appears to happen before my test gets instantiated. So the file doesn't exist - not that I'd be able to read it from my test anyway!

I noticed this section of the data storage use-cases here, which should allow opting out for the test - https://developer.android.com/training/data-storage/use-cases#opt-out-in-tests - but I'm not sure how to get this passed via the test runner. It suggests using it to write test data, but we have test-services to handle that during orchestration - I need to read data, not write data. Also, it looks like it impacts the app that's being tested too, and it implies that this is why getExternalFilesDir(null) on my test context return null.

Is there any way to accomplish this? Or am I stuck compiling the config file into the test apk and reading it like this:

this::class.java.classLoader?.getResource("test-config.json")?.openStream()?.use {
   config = json.decodeFromStream(it)
}

Upvotes: 1

Views: 356

Answers (1)

allofmex
allofmex

Reputation: 597

As you noticed, it seems not to be possible to request MANAGE_EXTERNAL_STORAGE via grantRuntimePermission(). The only solution I found to grant this permission for testing is via shell command.

Create your own Rule:

public class GrantManageStoragePermissionRule implements TestRule {

    @NonNull
    @Override
    public Statement apply(@NonNull Statement base, @NonNull Description description) {
        return new RequestManageStoragePermStatement(base);
    }

    private static class RequestManageStoragePermStatement extends Statement {

        private final Statement base;

        public RequestManageStoragePermStatement(Statement base) {
            this.base = base;
        }

        @Override
        public void evaluate() throws Throwable {
            ParcelFileDescriptor desc = getInstrumentation().getUiAutomation().executeShellCommand(
                    "appops set --uid "+ BuildConfig.APPLICATION_ID+" MANAGE_EXTERNAL_STORAGE allow");
            desc.close();
            base.evaluate();
        }
    }
}

And use it like:

@Rule
public GrantManageStoragePermissionRule manageStoragePermRule = new GrantManageStoragePermissionRule();

But I doubt that this should be the solution for your use case. Granting such a heavy permission just for testing may cause unwanted side effects in other tests.

Upvotes: 1

Related Questions