使用MTP的Android Storage Access Framework/DocumentProvider在目录层次结构中遍历的问题 [英] Issues traversing through directory hierarchy with Android Storage Access Framework / DocumentProvider using MTP

查看:345
本文介绍了使用MTP的Android Storage Access Framework/DocumentProvider在目录层次结构中遍历的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新: 我最初的问题可能会引起误解,因此我想改写一下: 我想通过Android的存储访问框架从MTP连接的设备中遍历层次结构树.我似乎无法实现这一点,因为我得到了SecurityException声明子节点不是其父节点的后代.有没有解决此问题的方法?还是这是一个已知问题?谢谢.

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.

我正在编写一个Android应用程序,该应用程序尝试通过MtpDocumentsProvider使用Android的存储访问框架(SAF)遍历和访问层次结构树中的文档.我或多或少地遵循 https://github.com/googlesamples/android-DirectorySelection中描述的代码示例关于如何从我的应用程序启动SAF选择器的信息,选择MTP数据源,然后在onActivityResult中使用返回的Uri遍历层次结构.不幸的是,这似乎不起作用,因为当我访问一个子文件夹并尝试遍历该文件夹时,总是得到一个SecurityException指出该document xx is not a descendant of yy

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

所以我的问题是,使用MtpDocumentProvider,我如何才能成功遍历应用程序中的层次结构树并避免出现此异常?

So my question is, using the MtpDocumentProvider, how can I successfully traverse through the hierarchy tree from my app and avoid this exception?

具体来说,首先,在我的应用程序中,我调用以下方法来启动SAF Picker:

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);
}

然后启动Android SAF选择器,我看到已连接的设备被识别为MTP数据源.我选择了所说的数据源,然后从我的onActivityResult中获得了Uri:

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
    }
}

然后,使用返回的Uri,我调用DocumentsContract.buildChildDocumentsUriUsingTree以获得一个Uri,然后可以将其用于查询和访问树的层次结构:

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
        }
    }
}

外部while循环的第一次迭代成功:对query的调用返回了一个有效的Cursor,供我遍历.问题是第二次迭代:当我尝试查询恰好是rootUri的子节点的Uri时,我得到一个SecurityException,说明文档xx不是yy的后代.

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):节点uri:content://com.android.mtp.documents/tree/2/document/2/children D/MyApp(19241):docId:4,名称:DCIM,mime:vnd.android.document/directory D/MyApp(19241):节点uri:content://com.android.mtp.documents/tree/2/document/4/children E/DatabaseUtils(20944):将异常写入宗地 E/DatabaseUtils(20944):java.lang.SecurityException:文档4不是2的后代

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

任何人都可以提供一些关于我做错了什么的见解吗?例如,如果我使用其他数据源提供程序,例如来自外部存储的数据源提供程序(即通过标准USB OTG读取器连接的SD卡),则一切正常.

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.

其他信息: 我正在Nexus 6P,Android 7.1.1上运行此程序,而我的应用程序minSdkVersion是19.

Additional information: I'm running this on a Nexus 6P, Android 7.1.1, and my app minSdkVersion is 19.

推荐答案

经过多次尝试,我不确定MTP数据源是否可以解决SecurityException问题(除非有人对此提出异议).查看DocumentProvider.java源代码和堆栈跟踪,似乎在Android框架的MtpDocumentProvider实现中可能未正确覆盖对DocumentProvider#enforceTree内部的DocumentProvider#isChildDocument的调用.默认实现始终返回false. #sadface

After different attempts, I'm not sure there is a way around this SecurityException for an MTP Data Source (unless someone can refute me on this). Looking at the DocumentProvider.java source code and the stack trace, it appears that the call to DocumentProvider#isChildDocument inside DocumentProvider#enforceTree may not have been properly overriden in the MtpDocumentProvider implementation in the Android framework. Default implementation always returns false. #sadface

这篇关于使用MTP的Android Storage Access Framework/DocumentProvider在目录层次结构中遍历的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆