8

更新: 私の最初の質問は誤解を招く可能性があるので、言い換えたいと思います: Android のストレージ アクセス フレームワークを介して、MTP 接続デバイスから階層ツリーをトラバースしたいです。SecurityExceptionサブノードは親ノードの子孫ではないというメッセージが表示されるため、これを達成できないようです。この問題を回避する方法はありますか? それとも、これは既知の問題ですか? ありがとう。

を介して Android の Storage Access Framework (SAF) を使用して、階層ツリーを介してドキュメントをトラバースしてアクセスしようとする Android アプリケーションを作成していますMtpDocumentsProvider。アプリからSAFピッカーを起動し、MTP データ ソースを選択してからonActivityResult、返さUriれた階層をトラバースします。残念ながら、これは機能していないようです。サブフォルダーにアクセスしてそれをトラバースしようとするとすぐに、常に次のように表示されるためSecurityExceptionですdocument xx is not a descendant of yy

私の質問は、 を使用してMtpDocumentProvider、アプリから階層ツリーを正常にトラバースし、この例外を回避するにはどうすればよいですか?

具体的に言うと、私のアプリでは、まず次のメソッドを呼び出して SAF ピッカーを起動します。

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 データ ソースとして認識されていることがわかります。上記のデータ ソースを選択すると、次のデータが取得Uriされます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これを使用してツリー階層を照会してアクセスできます。

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 ループの最初の繰り返しが成功queryCursorました。問題は 2 回目の反復です。Uriたまたま のサブノードであるを照会しようとrootUriするSecurityExceptionと、ドキュメント xx は 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 の子孫ではありません

私が間違っていることについて誰かが洞察を提供できますか? たとえば、外部ストレージ (つまり、標準の USB OTG リーダーを介して接続された SD カード) からのものなど、別のデータ ソース プロバイダーを使用すると、すべて正常に動作します。

追加情報: Nexus 6P、Android 7.1.1 でこれを実行しています。私のアプリminSdkVersionは 19 です。

4

3 に答える 3

0

SecurityExceptionさまざまな試行の後、MTP データ ソースでこれを回避する方法があるかどうかはわかりません (誰かがこれについて反論できない限り)。DocumentProvider.javaソース コードとスタック トレースを見ると、Android フレームワークの実装でDocumentProvider#isChildDocumentinsideの呼び出しDocumentProvider#enforceTreeが適切にオーバーライドされていない可能性があります。MtpDocumentProviderデフォルトの実装は常に を返しますfalse。#悲しい顔

于 2016-12-13T00:45:07.700 に答える