Oreo (WRITE EXTERNAL STORAGE) Permission

2019-03-25 04:21发布

问题:

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

回答1:

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"/> 


回答2:

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.



回答3:

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



回答4:

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 :)



回答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.