UPDATE:
My initial question may be misleading so I want to rephrase it:
I want to traverse through the hierarchy tree from an MTP connected device through Android's Storage Access Framework. I can't seem to achieve this because I get a SecurityException
stating that a subnode is not a descendant of its parent node. Is there a way to workaround this issue? Or is this a known issue? Thanks.
I'm writing an Android application that attempts to traverse and access documents through the hierarchy tree using Android's Storage Access Framework (SAF) via the MtpDocumentsProvider
. I am more or less following the code example described in https://github.com/googlesamples/android-DirectorySelection on how to launch the SAF Picker from my app, select the MTP data source, and then, in onActivityResult
, use the returned Uri
to traverse through the hierarchy. Unfortunately, this doesn't seem to work because as soon as I access a sub-folder and try to traverse that, I always get a SecurityException
stating that document xx is not a descendant of yy
So my question is, using the MtpDocumentProvider
, how can I successfully traverse through the hierarchy tree from my app and avoid this exception?
To be specific, in my app, first, I call the following method to launch the SAF Picker:
private void launchStoragePicker() {
Intent browseIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
browseIntent.addFlags(
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
startActivityForResult(browseIntent, REQUEST_CODE_OPEN_DIRECTORY);
}
The Android SAF picker then launches, and I see my connected device recognized as the MTP data source. I select said data source and I get the Uri
from my onActivityResult
:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_OPEN_DIRECTORY && resultCode == Activity.RESULT_OK) {
traverseDirectoryEntries(data.getData()); // getData() returns the root uri node
}
}
Then, using the returned Uri
, I call DocumentsContract.buildChildDocumentsUriUsingTree
to get a Uri
which I can then use to query and access the tree hierarchy:
void traverseDirectoryEntries(Uri rootUri) {
ContentResolver contentResolver = getActivity().getContentResolver();
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, DocumentsContract.getTreeDocumentId(rootUri));
// Keep track of our directory hierarchy
List<Uri> dirNodes = new LinkedList<>();
dirNodes.add(childrenUri);
while(!dirNodes.isEmpty()) {
childrenUri = dirNodes.remove(0); // get the item from top
Log.d(TAG, "node uri: ", childrenUri);
Cursor c = contentResolver.query(childrenUri, new String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE}, null, null, null);
try {
while (c.moveToNext()) {
final String docId = c.getString(0);
final String name = c.getString(1);
final String mime = c.getString(2);
Log.d(TAG, "docId: " + id + ", name: " + name + ", mime: " + mime);
if(isDirectory(mime)) {
final Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId);
dirNodes.add(newNode);
}
}
} finally {
closeQuietly(c);
}
}
}
// Util method to check if the mime type is a directory
private static boolean isDirectory(String mimeType) {
return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
}
// Util method to close a closeable
private static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException re) {
throw re;
} catch (Exception ignore) {
// ignore exception
}
}
}
The first iteration on the outer while loop succeeds: the call to query
returned a valid Cursor
for me to traverse. The problem is the second iteration: when I try to query for the Uri
, which happens to be a subnode of rootUri
, I get a SecurityException
stating the document xx is not a descendent of yy.
D/MyApp(19241): node uri: content://com.android.mtp.documents/tree/2/document/2/children D/MyApp(19241): docId: 4, name: DCIM, mime: vnd.android.document/directory D/MyApp(19241): node uri: content://com.android.mtp.documents/tree/2/document/4/children E/DatabaseUtils(20944): Writing exception to parcel E/DatabaseUtils(20944): java.lang.SecurityException: Document 4 is not a descendant of 2
Can anyone provide some insight as to what I'm doing wrong? If I use a different data source provider, for example, one that is from external storage (i.e. an SD Card attached via a standard USB OTG reader), everything works fine.
Additional information:
I'm running this on a Nexus 6P, Android 7.1.1, and my app minSdkVersion
is 19.
I am using your code to go in sub folders with adding lines:
Uri childrenUri;
try {
//for childs and sub child dirs
childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri));
} catch (Exception e) {
// for parent dir
childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
}
The flags added before start activity for result do nothing.
I do not understand that you use DocumentsContract. The user picks a directory. From the uri you get you can construct a DocumentFile
for that directory.
After that use DocumentFile::listFiles()
on that instance to get a list of subdirectories and files.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With