3

My app requires a feature that backups WhatsApp status, voice notes, and images. As you know after Android Q google enforcing to access external media files using MediaStore API.

WhatsApp also moved their file to /Android/media/com.whatsapp/WhatsApp. I tried using MANAGE_EXTERNAL_STORAGE permission it works fine, but backing up these files is not the core functionality of the app, so I don't think google going to let me use this permission.

I wonder if there any way to read those files using MediaStore.VOLUME_EXTERNAL?

I tried something like this. I am not sure if this is even possible.

val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)

val selection= (MediaStore.Files.FileColumns.MEDIA_TYPE + "="
        + MediaStore.Files.FileColumns.MEDIA_TYPE_NONE)
val selectionArgs= arrayOf("%/WhatsApp/Media/.Statuses%")
val cursor = applicationContext.contentResolver.query(
    collection, null, selection, selectionArgs, null)
debug(cursor?.columnCount)
cursor?.close()

it throws an exception.

Caused by: android.database.sqlite.SQLiteException: no such column: media_type
Jeeva
  • 3,975
  • 3
  • 23
  • 47
  • When querying, where are you replacing `selectionArgs`? There is no `?` in the `selection` query. Try creating a query as suggested in the [docs](https://developer.android.com/reference/android/content/ContentResolver#query(android.net.Uri,%20java.lang.String[],%20java.lang.String,%20java.lang.String[],%20java.lang.String,%20android.os.CancellationSignal)). Also, try to look at [this question](https://stackoverflow.com/a/7163245/10301322), maybe it can be helpful – CcmU Jul 13 '21 at 09:40

1 Answers1

7

Try this...

To read whatsapp status you don't need to do more things, just changed its path.

Also you don't need to use MANAGE_EXTERNAL_STORAGE permission. This can work with older permission too.

BELOW ANSWER FOR SDK 29

This way I have done

String path=Environment.getExternalStorageDirectory().getAbsolutePath()+"/Android/media/"+"com.whatsapp" + "/WhatsApp/Media/.Statuses/"

File directory = new File(wa_path);
    if (!directory.exists()) {
        directory.mkdirs();
    }
    File[] files = directory.listFiles();
    if (files != null) {
        Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_REVERSE);
        for (int i = 0; i < files.length; i++) {
            if (!files[i].getName().equals(".nomedia")) {
                your_list.add(files[i]);
            }
        }
    }

This is working in android 11 too. And I only ask for "READ_EXTERNAL_STORAGE" and "WRITE_EXTERNAL_STORAGE" like before.

So now you need to check whether "WhatsApp" folder is available in root storage(before where it was), if not available then need to check in path I mentioned above.

FOR SDK 30 AND ABOVE

First ask for app specific folder permission

    try {
        Intent createOpenDocumentTreeIntent = ((StorageManager) getSystemService("storage")).getPrimaryStorageVolume().createOpenDocumentTreeIntent();
        String replace = ((Uri) createOpenDocumentTreeIntent.getParcelableExtra("android.provider.extra.INITIAL_URI")).toString().replace("/root/", "/document/");
        createOpenDocumentTreeIntent.putExtra("android.provider.extra.INITIAL_URI", Uri.parse(replace + "%3A" + "Android%2Fmedia"));
        startActivityForResult(createOpenDocumentTreeIntent, 500);
    } catch (Exception unused) {
        Toast.makeText(MainActivity.this, "can't find an app to select media, please active your 'Files' app and/or update your phone Google play services", 1).show();
    }

Now in OnActivityResult

    @Override
        protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == 500) {
                if (data != null) {
     Uri wa_status_uri = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fmedia/document/primary%3AAndroid%2Fmedia%2Fcom.whatsapp%2FWhatsApp%2FMedia%2F.Statuses");
 getContentResolver().takePersistableUriPermission(data.getData(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
    
                    //save shared preference to check whether app specific folder permission granted or not              FastSave.getInstance().saveBoolean(Utils.KEY_IS_APP_SPECTIFIC_PERMISSION_GRANTED, true);
    
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            getDataForAndroid11OnlyStatus(wa_status_uri);
                        }
                    }, 100);
                }
    
            }
        }


//now get status from whatsApp folder

    ArrayList<Object> statusObjectsList = new ArrayList<>();
    
    
    private void getDataForAndroid11OnlyStatus(Uri uriMain) {
        //check for app specific folder permission granted or not
        if (!FastSave.getInstance().getBoolean(Utils.KEY_IS_APP_SPECTIFIC_PERMISSION_GRANTED, false)) {
            //ask for folder permission
        }
    
        Log.d("====", "uriMain ::: " + uriMain);
        ContentResolver contentResolver = getContentResolver();
        Uri uri = uriMain;
        Uri buildChildDocumentsUriUsingTree = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri));
    
        ArrayList arrayList = new ArrayList();
        Cursor cursor = null;
        try {
            cursor = contentResolver.query(buildChildDocumentsUriUsingTree, new String[]{"document_id"}, (String) null, (String[]) null, (String) null);
            while (cursor.moveToNext()) {
                arrayList.add(DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)));
                if (!DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)).toString().endsWith(".nomedia")) {
    
                    FileHelper fileHelper = new FileHelper(MainActivity.this);
                    String filePath = fileHelper.getRealPathFromUri(DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)));
    
                    statusObjectsList.add(DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)));
    
                }
            }
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //here set your adapter in list and set data from statusObjectsList
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        } catch (Throwable th) {
            throw th;
        }
    }

Let me know if you didn't understand or not works.

This is working tested in Android 11 emulator, OnePlus Nord, Redmi Note 10 Pro and Poco. In All of these Android 11 device WhatsApp statuses are displaying.

added: So what is the api you targrt I think you target api 29 It will not work if you target api 30 prove your answer

Thank you

pratik vekariya
  • 1,105
  • 1
  • 8
  • 14
  • @Jeeva could you please approve my answer? – pratik vekariya Jul 17 '21 at 04:59
  • Can you explain how the `WRITE_EXTERNAL_STORAGE` works in case of Android 11 specifically? – gtxtreme Sep 04 '21 at 02:54
  • Yes, for now if your app is already in play store then with sdk 29 above answer will work. But if your sdk 30 then this will not work. I will update answer for sdk 30. – pratik vekariya Sep 04 '21 at 04:07
  • @pratikvekariya Hi. thanks for this code. for sdk>29 we need to ask for folder permission each time? – Milad Mohammadi Apr 23 '22 at 16:54
  • I figured out how to do it. – Milad Mohammadi Apr 24 '22 at 06:58
  • @MiladMohammadi that's great..!!! – pratik vekariya Apr 24 '22 at 07:25
  • @pratikvekariya Any way to sort files by last modified? i used this as sortOrder parameter but not working: `DocumentsContract.Document.COLUMN_LAST_MODIFIED + " DESC"` – Milad Mohammadi Apr 24 '22 at 08:37
  • Yes, add "last_modified" in cursor query. And get it as Long.parseLong(cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED))). Let me know if you didn't understand – pratik vekariya Apr 25 '22 at 05:20
  • @pratikvekariya thanks for the help but its taking permission each time in Android 12 , How to fix ? if permission already granted and you call function getDataForAndroid11OnlyStatus() then its through error : W/System.err: java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri from pid=27452, uid=10496 – Mujahid Khan Jul 05 '22 at 12:38
  • Please check again, you may forgot something because I have same code and all things are same and it still working in Android 12 too. – pratik vekariya Jul 05 '22 at 12:44
  • This solution is not working in android 12 and 13 as path returned from it is wrong. It should like - content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fmedia%2Fcom.whatsapp%2FWhatsApp%2FMedia/document/primary%3AAndroid%2Fmedia%2Fcom.whatsapp%2FWhatsApp%2FMedia%2F.Statuses%2Fc7a8aa99d18c4906815845ae1cb9c8c1.png – Smeet Nov 09 '22 at 13:04
  • @Smeet As you said I have tried above answer(my answer) and it is working in android 12 and 13 both. Check your folder uri wether it is valid or not. And i think uri should be "content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fmedia/document/primary%3AAndroid%2Fmedia%2Fcom.whatsapp%2FWhatsApp%2FMedia%2F.Statuses" this. Check with this your uri is correct or not. – pratik vekariya Nov 10 '22 at 06:13
  • @pratikvekariya Yes, if I apply your logic, the URI is what you mentioned but unfortunately it is not working in Pixel 4a, but did not testing in any other devices. The path that I given above is working fine in my case. Not sure what is going wrong. – Smeet Nov 10 '22 at 07:25