Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write External Storage in AndroidTest

I wanted to write an AndroidTest for an App which involves writing to external storage but somehow I am not able to make it work. (Testing on several devices like Pixel2, Lg G6, Huawai P8)

So I simplified everything just to test if writing to external storage is possible at all. I experimented with GrantPermissionRule as well as writing my on Granter with no success at all.

I probably ended up doing way too much unnecessary stuff, so here is what I have done:

  1. Create new Project with no Activity in AndroidStudio
  2. added permission to Manifest
  3. added AndroidManifest with uses-permission tag
  4. Wrote my own PermissionRequester
  5. Wrote test to try to add a Folder in /sdcard/Download and delete it

Following are snippets referenced by numbered List above

2.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.company.android.testpermissions">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="com.company.TestPermissions"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme" />
</manifest>

3.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.company.android.testpermissions.test">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

</manifest>

Also tried

<manifest package="com.company.android.testpermissions">...

omitting the 'test' postfix

4.

public class MyPermissionRequester {

    public static final String TAG = MyPermissionRequester.class.getSimpleName();

    public static void request(String... permissions) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            UiAutomation auto = InstrumentationRegistry.getInstrumentation().getUiAutomation();
            String cmd = "pm grant " + InstrumentationRegistry.getTargetContext().getPackageName() + " %1$s";
            String cmdTest = "pm grant " + InstrumentationRegistry.getContext().getPackageName() + " %1$s";
            String currCmd;
            for (String perm : permissions) {
                execute(String.format(cmd, perm), auto);
                execute(String.format(cmdTest, perm), auto);
            }
        }
        GrantPermissionRule.grant(permissions);
    }

    private static void execute(String currCmd, UiAutomation auto){
        Log.d(TAG, "exec cmd: " + currCmd);
        auto.executeShellCommand(currCmd);
    }
}

5.

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void sdcardTest(){

        //check sdcard availability 
        Assert.assertEquals("Media is not mounted!", Environment.getExternalStorageState(), Environment.MEDIA_MOUNTED);
       //get and check Permissions
MyPermissionRequester.request(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        int grantResult = ActivityCompat.checkSelfPermission(InstrumentationRegistry.getTargetContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
        Assert.assertEquals(PackageManager.PERMISSION_GRANTED, grantResult);
        grantResult = ActivityCompat.checkSelfPermission(InstrumentationRegistry.getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
        Assert.assertEquals(PackageManager.PERMISSION_GRANTED, grantResult);

        //finally try to create folder
        File f = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "CompanyTest");
        if (!f.exists()){
            boolean mkdir = f.mkdirs();
            Assert.assertTrue("Folder '"+f.getAbsolutePath() + "' not Present!", mkdir);
        }
        boolean delete = f.delete();
        Assert.assertTrue("Folder '"+f.getAbsolutePath() + "' is Present!", delete);
    }
}

The result of the Test is:

junit.framework.AssertionFailedError: Folder ' /storage/emulated/0/Download/CompanyTest' not Present! at

junit.framework.Assert.fail(Assert.java:50) at

junit.framework.Assert.assertTrue(Assert.java:20) at

com.company.android.testpermissions.ExampleInstrumentedTest.sdcardTest(ExampleInstrumentedTest.java:69)

at java.lang.reflect.Method.invoke(Native Method) at ... (omitted rest coming from the TestRunner)

What am I missing here or doing wrong?

like image 943
Rafael T Avatar asked Mar 09 '18 17:03

Rafael T


People also ask

How do I enable write External Storage?

To read and write data to external storage, the app required WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE system permission. These permissions are added to the AndroidManifest. xml file. Add these permissions just after the package name.

What does write External Storage mean?

The Android SDK documentation has this to say in terms of a definition of “external storage”: Every Android-compatible device supports a shared “external storage” that you can use to save files. This can be a removable storage media (such as an SD card) or an internal (non-removable) storage.

What is External Storage of Android?

External Storage: That means, both storage types like Nexus 6P's 64 GB internal memory and removable microSD card which we insert in phone's card slot are considered as External Storage. Removable Storage means just microSD card storage, not the internal memory.


3 Answers

After a lot of pain trying stuff which doesn't work I finally got the solution:

Google's claims that

if your application already requests write access, it will automatically get read access as well.

https://developer.android.com/about/versions/android-4.1.html#Permissions

or in WriteExternalStorage docs:

Note: If your app uses the WRITE_EXTERNAL_STORAGE permission, then it implicitly has permission to read the external storage as well.

https://developer.android.com/training/data-storage/files.html#WriteExternalStorage

Or in manifest Permissions:

Any app that declares the WRITE_EXTERNAL_STORAGE permission is implicitly granted this permission.

https://developer.android.com/reference/android/Manifest.permission.html#READ_EXTERNAL_STORAGE

except it doesn't!

If I explicitly add READ_EXTERNAL_STORAGE to my manifest as well as requesting it on Runtime, it works like I was used to.

So 3. must add

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.company.android.testpermissions.test">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
</manifest>

and the Test should look like this:

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void sdcardTest(){

        //check sdcard availability 
        Assert.assertEquals("Media is not mounted!", Environment.getExternalStorageState(), Environment.MEDIA_MOUNTED);
       //get and check Permissions
MyPermissionRequester.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE);
        int grantResult = ActivityCompat.checkSelfPermission(InstrumentationRegistry.getTargetContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
        Assert.assertEquals(PackageManager.PERMISSION_GRANTED, grantResult);
        grantResult = ActivityCompat.checkSelfPermission(InstrumentationRegistry.getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
        Assert.assertEquals(PackageManager.PERMISSION_GRANTED, grantResult);

        //finally try to create folder
        File f = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "CompanyTest");
        if (!f.exists()){
            boolean mkdir = f.mkdirs();
            Assert.assertTrue("Folder '"+f.getAbsolutePath() + "' not Present!", mkdir);
        }
        boolean delete = f.delete();
        Assert.assertTrue("Folder '"+f.getAbsolutePath() + "' is Present!", delete);
    }
}

and everything works.

Only question which is left is if/why this is intended behavior since Android 8.1

like image 90
Rafael T Avatar answered Oct 20 '22 23:10

Rafael T


Like I stated in my comments,

The only thing I can think of is that in both the examples that you cited, they seem to be referencing older versions of Android (it looks like they are referencing around Android 4.1). Here is a link explicitly stating that the user must explicitly approve each permission that doesn't fall within the "sandbox" of the application after Android 6.0:

https://developer.android.com/training/permissions/requesting.html

Also, they explicitly state in the link I provided above that you HAVE to include both READ and WRITE in your manifest. Then you can use run time permissions and that will cover both, but only if you included both in your manifest. (under the "Handle the permissions request response" section)

like image 1
cjnash Avatar answered Oct 21 '22 01:10

cjnash


Since Android 6.0, you need to grant the runtime permission manually for running androidTest

adb shell pm grant <pkg_name> android.permission.READ_EXTERNAL_STORAGE adb shell pm grant <pkg_name> android.permission.WRITE_EXTERNAL_STORAGE You can verify the permissions by adb shell dumpsys package <pkg_name> The AndroidManifest.xml only request for those permissions, but not granted by default when install the androidTest APK.

like image 1
Liu Avatar answered Oct 21 '22 01:10

Liu