Как вы можете получить метаданные MediaStore из DocumentsContract.Document?

Моя цель - получить выбранный пользователем каталог с помощью Intent.ACTION_OPEN_DOCUMENT_TREE, а затем получить все аудиофайлы и их метаданные из этого каталога. Я уже могу получить выбранный пользователем каталог с помощью Intent.ACTION_OPEN_DOCUMENT_TREE, но не могу извлечь метаданные аудиофайла. Я получаю null вместо метаданных. Я считаю, что проблема в том, что Uri, возвращенный onActivityResult Intent, несовместим с Uri MediaStore.

В настоящее время я могу получить метаданные из MediaStore, используя следующий код:

    void getAudioFiles(){
        String[] projection = new String[]{
                MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.IS_MUSIC,
                MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.TITLE, MediaStore.Images.Media.DATA
        };
        String selection = MediaStore.Audio.Media.IS_MUSIC + " != ?";
        String[] selectionArgs = new String[]{"0"};
        String sortOrder = MediaStore.Video.Media.TITLE + " ASC";
        try (Cursor cursor = getApplicationContext().getContentResolver().query(
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder)) {
            if (cursor != null) {
                List<Uri> newURIs = getURIs(cursor);
            }
        }
    }

    private List<Uri> getURIs(Cursor cursor) {
        ArrayList<Uri> newURIs = new ArrayList<>();
        int idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
        int nameCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME);
        int titleCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
        int artistCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
        int dataCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
        while (cursor.moveToNext()) {
            long id = cursor.getLong(idCol);
            String displayName = cursor.getString(nameCol);
            String title = cursor.getString(titleCol);
            String artist = cursor.getString(artistCol);
            String data = cursor.getString(dataCol);
            Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
            if (!uriMap.containsKey(uri)) {
                AudioURI audioURI = new AudioURI(uri, data, displayName, artist, title, id);
                uriMap.put(uri, audioURI);
            }
            newURIs.add(uri);
        }
        return newURIs;
    }

Здесь я запрашиваю MediaStore.Audio.Media.EXTERNAL_CONTENT_URI. Это наводит меня на мысль, что мне нужен способ выбора из MediaStore на основе Uri, возвращаемого onActivityResult Intent.

Вот моя первая попытка получить метаданные, используя выбранный пользователем каталог.

Во-первых, я установил onClickListener, чтобы попросить пользователя выбрать каталог:

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                String title = getResources().getString(R.string.pick_folder);
                Intent chooser = Intent.createChooser(intent, title);
                if (intent.resolveActivity(activityMain.getPackageManager()) != null) {
                    startActivityForResult(chooser, REQUEST_CODE_OPEN_FOLDER);
                }
            }

Затем я получаю результат и пытаюсь проанализировать данные:

    @Override
    public void onActivityResult(int requestCode, int resultCode,
                                 Intent resultData) {
        if (requestCode == REQUEST_CODE_OPEN_FOLDER && resultCode == Activity.RESULT_OK) {
            Uri uri = null;
            if (resultData != null) {
                uri = resultData.getData();
                final int takeFlags = resultData.getFlags()
                        & (Intent.FLAG_GRANT_READ_URI_PERMISSION);
                ContentResolver contentResolver = activityMain.getContentResolver();
                contentResolver.takePersistableUriPermission(uri, takeFlags);
                traverseDirectoryEntries(uri);
            }
        }
    }

    void traverseDirectoryEntries(Uri rootUri) {
        Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
                rootUri, DocumentsContract.getTreeDocumentId(rootUri));
        getFiles(childrenUri, rootUri);
    }

    private void getFiles(Uri childrenUri, Uri rootUri) {
        ContentResolver contentResolver = activityMain.getContentResolver();
        List<TreeViewNode> treeViewNodes = new LinkedList<>();
        String selection = MediaStore.Audio.Media.IS_MUSIC + " != ? OR" +
                DocumentsContract.Document.COLUMN_MIME_TYPE + " == ?";
        String[] selectionArgs = new String[]{"0"}, DocumentsContract.Document.MIME_TYPE_DIR};
        try (Cursor cursor = contentResolver.query(childrenUri, new String[]{
                        DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                        DocumentsContract.Document.COLUMN_DISPLAY_NAME,
                        DocumentsContract.Document.COLUMN_MIME_TYPE,
                        MediaStore.Audio.Media._ID,
                        MediaStore.Audio.Media.DISPLAY_NAME,
                        MediaStore.Audio.Media.TITLE,
                        MediaStore.Audio.Media.ARTIST,
                        MediaStore.Audio.Media.DATA},
                selection, selectionArgs, null)) {
            if (cursor != null) {
                int idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
                int nameCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME);
                int titleCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
                int artistCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
                int dataCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
                while (cursor.moveToNext()) {
                    String docId = cursor.getString(0);
                    String name = cursor.getString(1);
                    String mime = cursor.getString(2);
                    long id = cursor.getLong(idCol);
                    this.uriID = id;
                    String displayName = cursor.getString(nameCol);
                    String title = cursor.getString(titleCol);
                    String artist = cursor.getString(artistCol);
                    String data = cursor.getString(dataCol);
                    Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
                    if (isDirectory(mime)) {
                        Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(
                                rootUri, docId);
                        getFiles(newNode, rootUri);
                    } else {
                        audioFiles.add(childrenUri);
                    }
                }
            }
        }
    }

    private static boolean isDirectory(String mimeType) {
        return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
    }

Это отлично работает для поиска файлов, но не для получения метаданных. Каждый столбец MediaStore.Audio.Media имеет нулевое значение.

Во второй попытке я попытался использовать MediaMetadataRetriever, но получаю java.lang.RuntimeException: setDataSource failed: status = 0x80000000:

        MediaMetadataRetriever mediaMetadataRetriever;
        mediaMetadataRetriever = new MediaMetadataRetriever();
        mediaMetadataRetriever.setDataSource(activityMain.getApplicationContext(), childrenUri);

Я думаю, что это может быть проблемой безопасности, потому что MediaMetadataRetriever не имеет корневого uri с разрешением на чтение. Это связано с тем, что те же файлы, полученные из Uri MediaStore, совместимы с MediaMetadataRetriever.

Для моей третьей попытки я пытаюсь выбрать из MediaStore на основе идентификатора документа, возвращенного onActivityResult Intent, но я получаю android.database.sqlite.SQLiteException: нет такого столбца: document_id (код 1 SQLITE_ERROR):

        ContentResolver contentResolver = activityMain.getContentResolver();
        String selection = DocumentsContract.Document.COLUMN_DOCUMENT_ID + " == ?";
        String[] selectionArgs = new String[]{docID};
        try (Cursor cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                new String[]{
                        MediaStore.Audio.Media._ID,
                        MediaStore.Audio.Media.DISPLAY_NAME,
                        MediaStore.Audio.Media.TITLE,
                        MediaStore.Audio.Media.ARTIST,
                        MediaStore.Audio.Media.DATA
                },
                selection, selectionArgs, null)) {
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    int idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
                    int nameCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME);
                    int titleCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
                    int artistCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
                    int dataCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
                    long id = cursor.getLong(idCol);
                    this.uriID = id;
                    String displayName = cursor.getString(nameCol);
                    String title = cursor.getString(titleCol);
                    String artist = cursor.getString(artistCol);
                    String data = cursor.getString(dataCol);
                    Uri uri1 = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
                }
            }
        }

Как можно получить метаданные аудиофайла из выбранного пользователем каталога?


person John Glen    schedule 20.10.2020    source источник
comment
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) Здесь ничего нельзя дарить. Удалите это заявление. Это ничего не делает. Разве я не говорил об этом раньше?   -  person blackapps    schedule 21.10.2020
comment
Вы можете попробовать getMediaUri().   -  person CommonsWare    schedule 21.10.2020
comment
Это может сработать, но для этого требуется Q, и я собираюсь обеспечить обратную совместимость.   -  person John Glen    schedule 21.10.2020


Ответы (2)


Intent.ACTION_OPEN_DOCUMENT_TREE предоставляет вам SAF uri от другого поставщика, а не от медиа-магазина.

Нет смысла запрашивать медиастор с помощью saf uri.

На последних версиях Android вы можете конвертировать некоторые файлы saf uries в uries mediastore. Наоборот.

Затем используйте urie хранилища медиа для запроса хранилища медиа.

person blackapps    schedule 20.10.2020
comment
Однако я хочу поддерживать более старую версию Android. - person John Glen; 21.10.2020

Я нашел один способ решить эту проблему:

    private void getAudioUri(String displayName) {
        ContentResolver contentResolver = activityMain.getContentResolver();
        String selection =  MediaStore.Audio.Media.DISPLAY_NAME + " == ?";
        String[] selectionArgs = new String[]{displayName};
        try (Cursor cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                new String[]{
                        MediaStore.Audio.Media._ID,
                        MediaStore.Audio.Media.DISPLAY_NAME,
                        MediaStore.Audio.Media.TITLE,
                        MediaStore.Audio.Media.ARTIST,
                        MediaStore.Audio.Media.DATA
                },
                selection, selectionArgs, null)) {
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    int idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
                    int nameCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME);
                    int titleCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
                    int artistCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
                    int dataCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
                    long id = cursor.getLong(idCol);
                    this.mediaStoreUriID = id;
                    String displayName2 = cursor.getString(nameCol);
                    String title = cursor.getString(titleCol);
                    String artist = cursor.getString(artistCol);
                    String data = cursor.getString(dataCol);
                    Uri uri1 = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
                }
            }
        }
    }

В этом случае переданное displayName находится в столбце MediaStore.Audio.Media.DISPLAY_NAME документа. По какой-то причине эти метаданные доступны из Uri, полученного из Intent.

Я не совсем уверен, что у документов всегда есть MediaStore.Audio.Media.DISPLAY_NAME. Это работает на Android Pie и Marshmellow.

person John Glen    schedule 20.10.2020