23

According to "developer.android.com"

If the app targets Android 8.0 (API level 26), the system grants only 
READ_EXTERNAL_STORAGE at that time; however, if the app later requests 
WRITE_EXTERNAL_STORAGE, the system immediately grants that privilege 
without prompting the user.

Now, I have the READ_EXTERNAL_STORAGE and I'm requesting the DownloadManager to download a file in the Public Download folder

downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, mAttachmentItem.getFilename());

Unfortunately, I got

E/UncaughtException: java.lang.IllegalStateException: Unable to create directory: /storage/emulated/0/data/user/0/com.abc.cba/files
                                                                           at android.app.DownloadManager$Request.setDestinationInExternalPublicDir(DownloadManager.java:699)

I have declared both of permissions in the Manifest

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

Read permission is already Granted and in Runtime I'm requesting Write permission, the system doesn't prompt any message or dialog, it's always Denied(Not Granted) in "onRequestPermissionsResult"

public static boolean isPermissionsToAccessStorageAreGranted(Context context, Activity activity) {
    ArrayList<String> permissions = new ArrayList<>();

    if (!PermissionUtil.checkPermissions(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
        permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
    }

    if (!permissions.isEmpty()) {
        String[] permissionsList = permissions.toArray(new String[permissions.size()]);
        PermissionUtil.requestPermissions(activity, permissionsList,
                PermissionUtil.REQUESTCODE_ACCESS_STORAGE);
        return false;
    } else {
        return true;
    }
}


@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if(requestCode==REQUESTCODE_ACCESS_STORAGE && grantResults[0]== PackageManager.PERMISSION_GRANTED){
 /* downloadFile(); */       
    }
}

I'm trying to grant those two permissions via ADB, it shows me an error:

adb shell pm grant com.abc.cba 
android.permission.WRITE_EXTERNAL_STORAGE
Operation not allowed: java.lang.SecurityException: Package com.abc.cbs 
has not requested permission android.permission.WRITE_EXTERNAL_STORAGE

adb shell pm grant com.abc.cba 
android.permission.READ_EXTERNAL_STORAGE
Operation not allowed: java.lang.SecurityException: Can't change 
android.permission.READ_EXTERNAL_STORAGE. It is required by the 
application
Angel Koh
  • 12,479
  • 7
  • 64
  • 91
SoulaimenK
  • 584
  • 2
  • 6
  • 20
  • 1
    You need to request `WRITE_EXTERNAL_STORAGE` at runtime. – CommonsWare Jan 09 '18 at 16:23
  • I'm requesting WRITE_EXTERNAL_STORAGE permission at runtime, the system doesn't prompt any message or dialog, it's always Denied(Not Granted) in "onRequestPermissionsResult" – SoulaimenK Jan 09 '18 at 17:25
  • Try fully uninstalling and reinstalling your app. Also, make sure that the `` elements are in the proper location (as direct children of ``). – CommonsWare Jan 09 '18 at 17:27
  • They are in the proper location – SoulaimenK Jan 09 '18 at 17:37
  • `/storage/emulated/0/data/user/0/com.abc.cba/files` That is a very strange path. It will not exist. Please dont tell that it is equivalent with `Environment.DIRECTORY_DOWNLOADS, mAttachmentItem.getFilename()`. – greenapps Jan 09 '18 at 17:50
  • Yes Actually, com.abc.cba is the package name of the Application – SoulaimenK Jan 09 '18 at 17:51
  • `adb shell pm grant com.lookiimobile.rosyboa android.permission.WRITE_EXTERNAL_STORAGE Operation not allowed: java.lang.SecurityException: Package com.abc.cba has not requested permission android.permission.WRITE_EXTERNAL_STORAGE` `adb shell pm grant com.abc.cba android.permission.READ_EXTERNAL_STORAGE Operation not allowed: java.lang.SecurityException: Can't change android.permission.READ_EXTERNAL_STORAGE. It is required by the application` – SoulaimenK Jan 10 '18 at 11:24
  • @Soulaimen I have similar problem – svkaka Apr 11 '18 at 12:18
  • 1
    @svkaka: did you tried to set `tools:node="replace"` in the Manifest file (in uses-permission) ? – SoulaimenK Apr 12 '18 at 13:33

7 Answers7

30

Actually, It was an external problem. One of App Libs I'm using request the WRITE_EXTERNAL_PERMISSION with android:maxSdkVersion. So when merging with my Manifest it will remove the permission for the whole application.

The Solution is to add:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="replace"/> 
Faysal Ahmed
  • 7,501
  • 5
  • 28
  • 50
SoulaimenK
  • 584
  • 2
  • 6
  • 20
13

You are correct in adding the permissions to the manifest:

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

For versions of Lollipop and higher, you need to also request the permissions at runtime. To solve this problem, I created a new method requestAppPermission that I call when the main activity is created. This method runs only for Lollipop and higher, and returns early otherwise:

private void requestAppPermissions() {
    if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        return;
    }

    if (hasReadPermissions() && hasWritePermissions()) {
        return;
    }

    ActivityCompat.requestPermissions(this,
            new String[] {
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, REQUEST_WRITE_STORAGE_REQUEST_CODE); // your request code
}

private boolean hasReadPermissions() {
    return (ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
}

private boolean hasWritePermissions() {
    return (ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
}

I call this method in the activity's onCreate:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Other things
    requestAppPermissions();
}

On first load, this will prompt the user to accept permissions.

Then, before you run any code that needs to read and write to storage, you can check these permissions, either by storing the values or running those checks again using methods hasReadPermissions() and hasWritePermissions() defined above.

Tina
  • 1,186
  • 10
  • 11
  • _"For versions of Lollipop and higher, you need to also request the permissions at runtime"_ Nope. The runtime permission model was introduced in Marshmallow (API 23), not in Lollipop. – Shashanth Dec 30 '19 at 04:12
6

UPDATE: See this answer for a better solution


EDIT: This is not a solution, just a quick workaround.

If you get permission denied error even when the permissions are granted and you already implemented permission checks,

make sure you're not targetting api level 29:

Change targetSdkVersion and compilesdkversion from 29 to 28 or any other lower level.

John Doe
  • 1,003
  • 12
  • 14
  • 7
    How is this a solution - not to target the latest API? Please consider suggesting how to implement this correctly in API 29. – hb0 Jan 07 '20 at 06:05
5

You have to grant permissions at runtime on Marshmallow or higher. Sample snippet :

private static final int REQUEST_WRITE_STORAGE = 112;

if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            boolean hasPermission = (ContextCompat.checkSelfPermission(getBaseContext(),
                    Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
            if (!hasPermission) {
                ActivityCompat.requestPermissions(SplashScreen.this,
                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.INTERNET
                        },
                        REQUEST_WRITE_STORAGE);
            }else{ startMainActivity(); }
        }

Hope it helps.

Roshan
  • 753
  • 5
  • 14
5

For Oreo,

you need to explicitly do a READ_EXTERNAL_STORAGE request (by code) even if you have already requested and is granted the WRITE_EXTERNAL_STORAGE permission, and vis versa.

prior to this, READ_EXTERNAL_STORAGE is automatically granted when WRITE_EXTERNAL_STORAGE is granted.

so what you need to do, is to

1) ask for WRITE permission in your codes.

2) when user grants the WRITE permission, ask again for READ permission - this will be automatically granted (you will get an exception if you do not ask for a READ explicitly)

The changes for Oreo doesn't make much sense to me (no idea why do we need to ask for a permission that is automatically granted), but that is what it is.

https://developer.android.com/about/versions/oreo/android-8.0-changes.html#rmp

Angel Koh
  • 12,479
  • 7
  • 64
  • 91
  • 1
    when I ask for WRITE first I always get -1 – svkaka Apr 11 '18 at 12:58
  • 2
    @svkaka, I think the general idea is to ask for permission only when you need it (e.g. prompt for camera/write permission only when user clicks the "take picture" button), rather than asking immediately when the user opens the app. – Angel Koh Apr 11 '18 at 13:30
3

Use this

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

instead of

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

Some of your third party libraries are override maxSdkVersion like com.vungle:publisher-sdk-android, to finding them just check the Merged Manifest below of your manifest screen. see this

For SDK 29 and Later

If you are still having issue to accessing external storage, consider using android:requestLegacyExternalStorage="true" in your <application> tag of your manifest.

Amir Hossein Ghasemi
  • 20,623
  • 10
  • 57
  • 53
2

Just a notice: on Android P I had to show a terminate dialog box to user asking to restart the app because even after all written above app couldn't read external storage, only after restart. Seriously, those guys in google makes Android better each time. 6, 8, and now so much troubles with 9, one more such stupid update and I will go to iOS :)

Tertium
  • 6,049
  • 3
  • 30
  • 51