Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force access to external removable microSD card

I'm using a Samsung A3, Android 5.0.2. I'm using this setup to compile apps, i.e. Android 4.1 Jelly Bean (API 16) target.

I precisely know the path of the external removable microSD card, it is /mnt/extSdCard/ (see also Note #7 below).

Problem: I notice that

File myDir = new File("/mnt/extSdCard/test");
myDir.mkdirs();

doesn't work: no directory is created.

Also:

File file = new File("/mnt/extSdCard/books/test.txt");   // the folder "books" already exists on the external microSD card, has been created from computer with USB connection
FileOutputStream fos = new FileOutputStream(file);

produces this error:

java.io.FileNotFoundException: /mnt/extSdCard/books/test.txt: open failed: EACCES (Permission denied) at libcore.io.IoBridge.open(...

How to force read+write access to external removable microSD card?

Notes:

  1. Environment.getExternalStorageDirectory().toString() gives /storage/emulated/0 which is my phone internal storage, i.e. not what I want.

  2. getExternalFilesDir(null) gives /storage/emulated/0/Android/data/com.blahblah.appname/files/ i.e. not what I want. Note that I can't use getExternalFilesDirs with a final s because this is not available in API16. Also runtime permissions are not available in API16 neither.

  3. I already have <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />, and also READ_EXTERNAL_STORAGE.

  4. I read lots of topics like this one or this one, in fact probably twenty similar questions, but finally it seems very complex and everything and its contrary is said. That's my I'm looking for a solution specific to this situation.

  5. I don't want ACTION_OPEN_DOCUMENT and ACTION_CREATE_DOCUMENT, in fact I don't want any GUI solution.

  6. Some apps I have (Sync Resilio) are allowed to modify /mnt/extSdCard/music/ successfully, to create new files there, etc.

  7. By the way, ls -la /mnt/extSdCard/ gives

    drwxrwx--x root     sdcard_r          2017-10-15 01:21 Android
    drwxrwx--- root     sdcard_r          2017-10-14 00:59 LOST.DIR
    drwxrwx--- root     sdcard_r          2017-12-05 16:44 books
    drwxrwx--- root     sdcard_r          2017-11-21 22:55 music
    
like image 546
Basj Avatar asked Dec 10 '17 01:12

Basj


People also ask

How do I force an app to allow an external storage?

Go To Device 'Settings' –> Scroll down and tap on 'Developer options' –> Scroll Down and enable 'Force allow apps on external'. This makes any app eligible to be written to external storage.


2 Answers

As I struggled a lot with the same problem I'll share my bit. I have taken help from these resources big thanks to them as well:

DocumentFile Android Docs and From Here

I have tested the code on 5.1.1 and 6.0.1 not on rest of the device I have not tested it but it should work fine.

On 5.0.2 to write on the external device you will have to ask user's permission.

Using below code and before asking this permission you need to instruct the user to select the root sd card so that you will have the access to the entire external storage.

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 25);

now in the onActivityResult save the UriTree return by the API as you will need it later.

 @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == 25&&resultCode == RESULT_OK) {
            getContentResolver().takePersistableUriPermission(data.getData(), Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    } 
    super.onActivityResult(requestCode, resultCode, data);
}

Once you have the root UriTree you can create modify delete files or directories from the external storage for that you will have get the DocumentFile from the UriTree.

To get the Document UriTree used the below code.

public static DocumentFile getDocumentFile(final File file) {
        String baseFolder = getExtSdCardFolder(file);
        String relativePath = null;

        if (baseFolder == null) {
            return null;
        }

        try {
            String fullPath = file.getCanonicalPath();
            relativePath = fullPath.substring(baseFolder.length() + 1);
        } catch (IOException e) {
            Logger.log(e.getMessage());
            return null;
        }
        Uri treeUri = Common.getInstance().getContentResolver().getPersistedUriPermissions().get(0).getUri();

        if (treeUri == null) {
            return null;
        }

        // start with root of SD card and then parse through document tree.
        DocumentFile document = DocumentFile.fromTreeUri(Common.getInstance(), treeUri);

        String[] parts = relativePath.split("\\/");

        for (String part : parts) {
            DocumentFile nextDocument = document.findFile(part);
            if (nextDocument != null) {
                document = nextDocument;
            }
        }

        return document;
    }


    public static String getExtSdCardFolder(File file) {
        String[] extSdPaths = getExtSdCardPaths();
        try {
            for (int i = 0; i < extSdPaths.length; i++) {
                if (file.getCanonicalPath().startsWith(extSdPaths[i])) {
                    return extSdPaths[i];
                }
            }
        } catch (IOException e) {
            return null;
        }
        return null;
    }


@TargetApi(Build.VERSION_CODES.KITKAT)
public static String[] getExtSdCardPaths() {
    List<String> paths = new ArrayList<>();
    for (File file : Common.getInstance().getExternalFilesDirs("external")) {

        if (file != null && !file.equals(Common.getInstance().getExternalFilesDir("external"))) {
            int index = file.getAbsolutePath().lastIndexOf("/Android/data");
            if (index < 0) {
                Log.w("asd", "Unexpected external file dir: " + file.getAbsolutePath());
            } else {
                String path = file.getAbsolutePath().substring(0, index);
                try {
                    path = new File(path).getCanonicalPath();
                } catch (IOException e) {
                    // Keep non-canonical path.
                }
                paths.add(path);
            }
        }
    }
    return paths.toArray(new String[paths.size()]);
}

Above code will return you the DocumentFile version of any file using that you can perform the desired operation.

If you want to see this code in action check out I have used this in my open source project to modify the mp3 files present on the external storage.

Hope it helps in case of doubts let me know.

Forgot to tell I had asked the same question a while ago Here is that question.

Edit: Using this code you can check if the user has given the permission on not

public static boolean hasPermission() {
    List<UriPermission> uriPermission = Common.getInstance().getContentResolver().getPersistedUriPermissions();
    return uriPermission != null && uriPermission.size() > 0;
}

if the permission is revoked then there will be no UriTree so you will have to ask for the permission again.

like image 172
Reyansh Mishra Avatar answered Oct 05 '22 23:10

Reyansh Mishra


bear in mind that some android devices will have a different path for the SD Card and some doesn´t have removable SD Card.

You don´t have to set the path directly!

File myDir = new File("/mnt/extSdCard/test");
myDir.mkdirs();

You can check first if your device has mounted a removable SD Card:

public static boolean isSDCardAvailable(Context context) {
    File[] storages = ContextCompat.getExternalFilesDirs(context, null);
    if (storages.length > 1 && storages[0] != null && storages[1] != null)
        return true;
    else
        return false;
}

Why get External Directories > 1, well because most of all the android devices has external storage as a primary directory and removable SD Card as second directory:

introducir la descripción de la imagen aquí

But you can use a method to get the real path of your removable microSD card:

public static String getRemovableSDCardPath(Context context) {
    File[] storages = ContextCompat.getExternalFilesDirs(context, null);
    if (storages.length > 1 && storages[0] != null && storages[1] != null)
        return storages[1].toString();
    else
        return "";
}

Then just do this:

File myDir = new File(getRemovableSDCardPath(getApplicationContext()),"test");
if(myDir.mkdirs()){
  Log.i(TAG, "Directory was succesfully create!");
}else{
  Log.i(TAG, "Error creating directory!");
}

For example using the method:

   String pathSDCard = getRemovableSDCardPath(getApplicationContext());

I have as a result the path of my removable SD Card (if i wouldn´t have a removable SD Card my path would be "", so you can implemente a validation to avoid the creation of the folder):

/storage/extSdCard/Android/data/com.jorgesys.myapplication/files

Now creating a new folder inside :

    File myDir = new File(getRemovableSDCardPath(getApplicationContext()),"test");
    if(myDir.mkdirs()){
        Log.i(TAG, "Directory was succesfully create!");
    }else{
        Log.i(TAG, "Error creating directory!");
    }

now i have the directory /test created:

enter image description here

like image 42
Jorgesys Avatar answered Oct 06 '22 01:10

Jorgesys