Marvin Stelter
Marvin Stelter

Reputation: 415

Android 11 / API 30 Access Android/data and media Directory to list files

I had some troubles to access Android/media or Android/data directory, today I found an solution, you can see it down below. And yes I know it is not recommended to load many bitmaps in the main thread, it was just for testing.

Hope I could help you with that.

Upvotes: 0

Views: 1267

Answers (2)

KoalaKoalified
KoalaKoalified

Reputation: 697

I know this question is a bit old but here is a working solution to organize your files anywhere except the root phone directory or gain persistant access to your directory of choice (so you don't have to prompt over and over again)

First In Your build.gradle file, implement the SAF framework's DocumentFile class:

implementation 'androidx.documentfile:documentfile:1.0.1'

Next Call this method which request permissions for the SAF to operate (You will only need to do this once on user install):

 private void requestDocumentTreePermissions() {
    // Choose a directory using the system's file picker.
    new AlertDialog.Builder(this)
            .setMessage("*Please Select A Folder For The App To Organize The Videos*")
            .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                @RequiresApi(api = Build.VERSION_CODES.Q)
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    StorageManager sm = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
                    Intent intent = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();

                    String startDir = "Documents";
                    Uri uri = intent.getParcelableExtra("android.provider.extra.INITIAL_URI");

                    String scheme = uri.toString();


                    scheme = scheme.replace("/root/", "/document/");
                    scheme += "%3A" + startDir;

                    uri = Uri.parse(scheme);
                    Uri rootUri = DocumentsContract.buildDocumentUri(
                            EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
                            uri.toString()
                    );
                    Uri treeUri = DocumentsContract.buildTreeDocumentUri(
                            EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
                            uri.toString()
                    );
                    uri = Uri.parse(scheme);
                    Uri treeUri2 = DocumentsContract.buildTreeDocumentUri(
                            EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
                            uri.toString()
                    );
                    List<Uri> uriTreeList = new ArrayList<>();
                    uriTreeList.add(treeUri);
                    uriTreeList.add(treeUri2);
                    getPrimaryVolume().createOpenDocumentTreeIntent()
                            .putExtra(EXTRA_INITIAL_URI, rootUri);
                    Intent intent2 = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
                    // Optionally, specify a URI for the directory that should be opened in
                    // the system file picker when it loads.
                    intent2.addFlags(
                            Intent.FLAG_GRANT_READ_URI_PERMISSION
                                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                                    | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
                    intent2.putExtra(EXTRA_INITIAL_URI, rootUri);
                    startActivityForResult(intent2, 99);
                }
            })
            .setCancelable(false)
            .show();


}

Next Store some Persistant Permissions:

    @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 99 && resultCode == RESULT_OK) {
        //get back the document tree URI (in this case we expect the documents root directory)
        Uri uri = data.getData();
        //now we grant permanent persistant permissions to our contentResolver and we are free to open up sub directory Uris as we please until the app is uninstalled
        getSharedPreferences().edit().putString(ACCESS_FOLDER, uri.toString()).apply();
        final int takeFlags = (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        getApplicationContext().getContentResolver().takePersistableUriPermission(uri, takeFlags);
        //simply recreate the activity although you could call some function at this point
        recreate();
    }
}

You can call the documentFile's rename method on the correct file

DocumentFile df = DocumentFile.fromTreeUri(MainActivity.this, uri);
df = df.findFile("CurrentName")
df.renameTo("NewName");

You Can also open InputStreams and OutputStreams using your content resolver because of the persistant URI permissions granted to your content resolver for that DocumentFile using the following snippet:

getContentResolver().openInputStream(df.getUri());
getContentResolver().openOutputStream(df.getUri());

InputStreams are for reading and OutputStreams are for saving

You can list files using

df.listFiles();

Or You can list out files using:

public static DocumentFile findFileInDirectoryMatchingName(Context mContext, Uri mUri, String name) {
    final ContentResolver resolver = mContext.getContentResolver();
    final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(mUri,
            DocumentsContract.getDocumentId(mUri));
    Cursor c = null;
    try {
        c = resolver.query(childrenUri, new String[]{
                DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                DocumentsContract.Document.COLUMN_DISPLAY_NAME,
                DocumentsContract.Document.COLUMN_MIME_TYPE,
                DocumentsContract.Document.COLUMN_LAST_MODIFIED

        }, DocumentsContract.Document.COLUMN_DISPLAY_NAME + " LIKE '?%'", new String[]{name}, null);
        c.moveToFirst();
        while (!c.isAfterLast()) {
            final String filename = c.getString(1);
            final String mimeType = c.getString(2);
            final Long lastModified = c.getLong(3);
            if (filename.contains(name)) {
                final String documentId = c.getString(0);
                final Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(mUri,
                        documentId);

                return DocumentFile.fromTreeUri(mContext, documentUri);
            }
            c.moveToNext();
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (c != null) {
            c.close();
        }
    }

    return null;
}

Which will run faster than the df.listFiles() method

Src (This is my own implementation but here is the original SF question) Renaming Video / Image While Targeting Android 11 (Api 30)

Upvotes: 1

Marvin Stelter
Marvin Stelter

Reputation: 415

String stringWA = "primary:Android/media/com.whatsapp/WhatsApp/Media/.Statuses/";

LinearLayout ll;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);


    
    ll = findViewById(R.id.ll_add);

    Button btn = findViewById(R.id.button);
    btn.setOnClickListener(v -> {

        openDirectory();
    });



}
public void openDirectory() {

    // Choose a directory using the system's file picker.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);

    // Optionally, specify a URI for the directory that should be opened in
    // the system file picker when it loads.


    startActivityForResult(intent.putExtra("android.provider.extra.INITIAL_URI", (Parcelable)DocumentsContract.buildDocumentUri((String)"com.android.externalstorage.documents", (String)stringWA)), 1234);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
    super.onActivityResult(requestCode, resultCode, resultData);

    if (requestCode == 1234 && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for the document or directory that
        // the user selected.
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            try{
                loadImageIntoView(uri);
            }catch (Exception e){

            }

        }
    }
}
private void loadImageIntoView(Uri uri)  {
    DocumentFile dir = DocumentFile.fromTreeUri(this, uri);
    DocumentFile[] fileListed = dir.listFiles();

    for(int i = 0;i < fileListed.length;i++){
        ImageView iv = new ImageView(this);
        iv.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT));
        try{
            InputStream is = getContentResolver().openInputStream(fileListed[i].getUri());
            Bitmap bitmap = BitmapFactory.decodeStream(is);
            is.close();
            iv.setImageBitmap(bitmap);
        }catch (Exception e){
            iv.setImageResource(R.drawable.ic_baseline_error_24);
        }
        ll.addView(iv);

    }
}

Upvotes: 1

Related Questions