52

I have an app that uses external storage to store photographs. As required, in its manifest, the following permissions are requested

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

and it uses the following to retrieve the required directory

File sdDir = Environment
            .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);

SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd", Locale.US);
String date = dateFormat.format(new Date());
storageDir = new File(sdDir, getResources().getString(
            R.string.storagedir)
            + "-" + date);

// Create directory, error handling
if (!storageDir.exists() && !storageDir.mkdirs()) {
 ... fails here

The app works fine on Android 5.1 to 2.3; it has been on Google Play for over a year.

Following an upgrade of one of my testing phones (Android One) to 6, it's now returning an error when trying to create the requisite directory, "/sdcard/Pictures/myapp-yy-mm".

The sd card is configured as "Portable storage". I've formatted the sd card. I've replaced it. I've rebooted. All to no avail.

Also, the built-in android screenshot functionality (via Power+Lower volume) is failing "due to limited storage space, or it isn't allowed by the app or your organisation".

Any ideas?

RudyF
  • 805
  • 1
  • 10
  • 16
  • Can you post your Logcat ? – Satyen Udeshi Oct 15 '15 at 04:12
  • 1
    Is your `targetSdkVersion` 23? Or an earlier version? – ianhanniballake Oct 15 '15 at 04:14
  • 1
    There's nothing unusual in the logcat, presumably because the "error" is being trapped by the app. – RudyF Oct 15 '15 at 04:30
  • Are you asking for run time permission! – Muhammad Babar Oct 15 '15 at 05:19
  • '..returning an error when trying to create the requisite directory, "/sdcard/Pictures/". No. That is not what is happening in your code. You are trying to create a directory like /sdcard/Pictures/myfolder which fails. You are not even checking if /sdcard/Pictures exists. – greenapps Oct 15 '15 at 08:19
  • Please show the value of storageDir.getAbsolutePath(). It contains unallowed characters? What brings in this 'date' format? – greenapps Oct 15 '15 at 08:26
  • Ok, greeapps, I was a trifle presumptuous just showing "/sdcard/Pictures/" ... the actual directory varies (as you should be able to discern in the code) with app and date so that the actual directory, today for example, would be "/sdcard/Pictures/myappname-10-15". However, the point is: this code works in all phones not running 6.0; it's only when I upgraded to 6.0 (this morning!) that this error has started. Also, as mentioned in my original post, it's also affecting android's built-in screenshot function (power + lower volume). – RudyF Oct 15 '15 at 08:56
  • I've now discovered that Android's built-in camera app, Camera, also does not recognize the sdcard. Oddly enough, Settings->Storage & USB does see the card, and I've used its options to format the card. But that didn't help either. I've tried a different sd card, without luck. Also, if I turn on "Developer Options", then "MTP" in "Select USB Configuration", the card is visible on my laptop. What the effing heck! As @Babar suggested, do I need to fiddle with my code to get "run time permission"? – RudyF Oct 15 '15 at 09:20

5 Answers5

51

I faced the same problem. There are two types of permissions in Android:

  • Dangerous (access to contacts, write to external storage...)
  • Normal

Normal permissions are automatically approved by Android while dangerous permissions need to be approved by Android users.

Here is the strategy to get dangerous permissions in Android 6.0

  1. Check if you have the permission granted
  2. If your app is already granted the permission, go ahead and perform normally.
  3. If your app doesn't have the permission yet, ask for user to approve
  4. Listen to user approval in onRequestPermissionsResult

Here is my case: I need to write to external storage.

First, I check if I have the permission:

...
private static final int REQUEST_WRITE_STORAGE = 112;
...
boolean hasPermission = (ContextCompat.checkSelfPermission(activity,
            Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
if (!hasPermission) {
    ActivityCompat.requestPermissions(parentActivity,
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                REQUEST_WRITE_STORAGE);
}

Then check the user's approval:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode)
    {
        case REQUEST_WRITE_STORAGE: {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
            {
                //reload my activity with permission granted or use the features what required the permission
            } else
            {
                Toast.makeText(parentActivity, "The app was not allowed to write to your storage. Hence, it cannot function properly. Please consider granting it this permission", Toast.LENGTH_LONG).show();
            }
        }
    }

}

You can read more about the new permission model here: https://developer.android.com/training/permissions/requesting.html

szedjani
  • 550
  • 2
  • 6
  • 21
Dũng Trần Trung
  • 6,198
  • 3
  • 24
  • 20
  • 6
    Right. I understand that there is a different security protocol if my app is targeting 6.0. But let's just take a step back and look at the problem again. All I did was allow my Android One phone to be upgraded to 6.0 (like I did a while back from 4.4 to 5.0, then 5.1). And, boom, apps that write to SD stop working? [Mind you, these apps are not targeting API 23] – RudyF Oct 16 '15 at 07:56
  • 1
    Have you tried setting compileSdkVersion and targetSdkVersion to lower than 23? I've done that and my app runs fine on android 6.0 – Dũng Trần Trung Oct 16 '15 at 08:21
  • 1
    I am in the process of upgrading Eclipse's sdk to API23 (Marshmallow) after which I plan to incorporate the new permission protocol into my apps. However, this doesn't resolve my phone's issue: after it was upgraded to 6.0, a number of installed apps have stopped working. The phone now appears to be at the mercy of tardy developers (like me). Frankly, this just doesn't make sense... there just has to be some "backward compatibility tweak" in 6.0. [As things stand, almost 1% of my users have already upgraded to 6.0 ... I've got to move fast :) ] – RudyF Oct 16 '15 at 12:05
  • And how to do this to the extSdCard (secondary storage)? – Luis A. Florit Dec 19 '15 at 15:56
  • Please bear in mind **we must have noHistory=false** in order to receive callback. Also refer [this](http://stackoverflow.com/a/33080682/1911652) in case if you don't receive callback. I wasted hours to figure it out. – Atul Aug 17 '16 at 14:23
  • It's a not an answer, because you can't copy/paste file using java.io.File (actually deprecated now) somewhere out of app directory. Also, you can do that with operations with file Uri. – Ruslan Berozov Aug 23 '16 at 10:20
  • Save my day! Hail great warrior! – Charleston Sep 05 '16 at 14:02
7

Android changed how permissions work with Android 6.0 that's the reason for your errors. You have to actually request and check if the permission was granted by user to use. So permissions in manifest file will only work for api below 21. Check this link for a snippet of how permissions are requested in api23 http://android-developers.blogspot.nl/2015/09/google-play-services-81-and-android-60.html?m=1

Code:-

If (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) !=
                PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERMISSION_RC);
            return;
        }`


` @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == STORAGE_PERMISSION_RC) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //permission granted  start reading
            } else {
                Toast.makeText(this, "No permission to read external storage.", Toast.LENGTH_SHORT).show();
            }
        }
    }
}
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
codeFreak
  • 353
  • 1
  • 3
  • 15
  • 1
    The thing is, the app is not targeting api 23. It's just running on 6.0. Shouldn't there be backward compatibility? Also, built-in apps that use the SD card, like Camera (2.7.008), are also failing with "No SD card available". If I look at "App permissions" via "Settings->Apps", it shows the new granularity of individual permissions (e.g. Camera, Storage, etc), all granted correctly. – RudyF Oct 16 '15 at 00:36
  • Another thing: when configured as "portable storage", the sd card icon remains visible in the notification area. Why? – RudyF Oct 16 '15 at 00:56
  • Installed a popular camera app, Open Camera (1 million downloads) ... it can't save photos, either. I'm beginning to wonder if Marshmallow demands a certain spec in SD cards (if so, why is it accepting the ones I've tried?). – RudyF Oct 16 '15 at 01:27
  • Well I haven't tested the mentioned apps. Maybe the developer hasn't implemented the restructured way of requesting permission. Am only stating why & how it show be done. – codeFreak Oct 16 '15 at 13:50
  • 1
    This does not work for me.... My app shows the dialog, and accepts the user answer (ALLOW). However, the external SD card is still not writable to the app, except for the folder in `/PathToExtSdCard/Android/data/myapp`. It seems allowing or denying makes no difference. Suggestions? – Luis A. Florit May 06 '16 at 01:20
6

Maybe you cannot use manifest class from generated code in your project. So, you can use manifest class from android sdk "android.Manifest.permission.WRITE_EXTERNAL_STORAGE". But in Marsmallow version have 2 permission must grant are WRITE and READ EXTERNAL STORAGE in storage category. See my program, my program will request permission until user choose yes and do something after permissions is granted.

            if (Build.VERSION.SDK_INT >= 23) {
                if (ContextCompat.checkSelfPermission(LoginActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(LoginActivity.this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(LoginActivity.this,
                            new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE},
                            1);
                } else {
                    //do something
                }
            } else {
                    //do something
            }
icaksama
  • 712
  • 10
  • 15
3

Right. So I've finally got to the bottom of the problem: it was a botched in-place OTA upgrade.

My suspicions intensified after my Garmin Fenix 2 wasn't able to connect via bluetooth and after googling "Marshmallow upgrade issues". Anyway, a "Factory reset" fixed the issue.

Surprisingly, the reset did not return the phone to the original Kitkat; instead, the wipe process picked up the OTA downloaded 6.0 upgrade package and ran with it, resulting (I guess) in a "cleaner" upgrade.

Of course, this meant that the phone lost all the apps that I'd installed. But, freshly installed apps, including mine, work without any changes (i.e. there is backward compatibility). Whew!

RudyF
  • 805
  • 1
  • 10
  • 16
  • So part way through updating my app to target API23 and trying to implement permission handling for 6 I'm even more confused. As I've been getting a few hundred downloads from 6 users (I'm currently targeting api21) I'm wondering if I should bother, if they're actually using my app just fine? This has been a real task trying to resolve all of the deprecation in 23, including library code, and I'm afraid I'll implement something hacked together from various examples on SO and cause users problems, potentially causing bad reviews. I feel I'm too far in now to go back to <23 however. – Mr Chops Nov 06 '15 at 20:40
  • My projects' build target is 20, and things appear to be chugging along quite nicely in that (a) the app works fine on my Android One phone currently upgraded to 6.0 and (b) the few hundred 6 users have not actually complained. Consequently, I can't see any compelling reason to modify my code in any hurry :) When I do, I'd probably (a) fiddle with my least popular app first and (b) productionize changes piece-meal, over a few weeks. All the best. – RudyF Nov 07 '15 at 22:45
3

Android Documentation on Manifest.permission.Manifest.permission.WRITE_EXTERNAL_STORAGE states:

Starting in API level 19, this permission is not required to read/write files in your application-specific directories returned by getExternalFilesDir(String) and getExternalCacheDir().


I think that this means you do not have to code for the run-time implementation of the WRITE_EXTERNAL_STORAGE permission unless the app is writing to a directory that is not specific to your app.

You can define the max sdk version in the manifest per permission like:

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

Also make sure to change the target SDK in the build.graddle and not the manifest, the gradle settings will always overwrite the manifest settings.

android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
    minSdkVersion 17
    targetSdkVersion 22
}
9re
  • 2,408
  • 27
  • 27
Stefano D
  • 958
  • 5
  • 17
  • 1
    +1 helped me a lot, but should it not read: ` ` (instead of 19), i.e. you are getting permission "the old way" until in API level 19 permissionn is not required anymore if you use getExternalFilesDir(String) and getExternalCacheDir(). – kalabalik Jun 16 '16 at 22:51