Trabalhar com arquivos de mídia em armazenamento externo

As APIs MediaStore oferecem uma interface para acessar os seguintes tipos de arquivos de mídia bem-definidos:

A MediaStore também inclui uma coleção denominada MediaStore.Files, que concede acesso a todos os tipos de arquivos de mídia.

Este guia descreve como acessar e compartilhar arquivos de mídia, que normalmente são salvos em um dispositivo de armazenamento externo.

Acessar arquivos

Para carregar arquivos de mídia, chame um dos seguintes métodos a partir do ContentResolver:

  • Para um único arquivo de mídia, use openFileDescriptor().
  • Para a miniatura de um único arquivo de mídia, use loadThumbnail(), passando o tamanho da miniatura que você quer carregar.
  • Para uma coleção de arquivos de mídia, use query().

O snippet de código a seguir mostra como acessar arquivos de mídia:

    val resolver = context.getContentResolver()

    // Open a specific media item.
    resolver.openFileDescriptor(item, mode).use { pfd ->
        // ...
    }

    // Load thumbnail of a specific media item.
    val mediaThumbnail = resolver.loadThumbnail(item, Size(640, 480), null)

    // Find all videos on a given storage device, including pending files.
    val collection = MediaStore.Video.Media.getContentUri(volumeName)
    val collectionWithPending = MediaStore.setIncludePending(collection)
    resolver.query(collectionWithPending, null, null, null).use { c ->
        // ...
    }

    // Publish a video onto an external storage device.
    val values = ContentValues().apply {
        put(MediaStore.Audio.Media.RELATIVE_PATH, "Video/My Videos")
        put(MediaStore.Audio.Media.DISPLAY_NAME, "My Video.mp4")
    }
    val item = resolver.insert(collection, values)
    

Acesso a partir do código nativo

Você pode se deparar com situações em que seu app precise trabalhar com um arquivo de mídia específico em código nativo, como um arquivo que outro app tenha compartilhado com seu app ou um arquivo da coleção de mídia do usuário. Nesses casos, inicie a descoberta do arquivo de mídia no seu código baseado em Java ou em Kotlin e, em seguida, passe o descritor de arquivo associado para seu código nativo.

O snippet de código a seguir mostra como passar o descritor de arquivo de um objeto de mídia para o código nativo do seu app:

Kotlin

    val contentUri: Uri = ContentUris.withAppendedId(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            cursor.getLong(BaseColumns._ID))
    val fileOpenMode = "r"
    val parcelFd = resolver.openFileDescriptor(uri, fileOpenMode)
    val fd = parcelFd?.detachFd()
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
    

Java

    Uri contentUri = ContentUris.withAppendedId(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            cursor.getLong(Integer.parseInt(BaseColumns._ID)));
    String fileOpenMode = "r";
    ParcelFileDescriptor parcelFd = resolver.openFileDescriptor(uri, fileOpenMode);
    if (parcelFd != null) {
        int fd = parcelFd.detachFd();
        // Pass the integer value "fd" into your native code. Remember to call
        // close(2) on the file descriptor when you're done using it.
    }
    

Para saber mais sobre como acessar arquivos em código nativo, veja a palestra Files for Miles (em inglês), da Conferência de Desenvolvedores Android 2018, a partir de 15:20.

Nomes de colunas em consultas de conteúdo

Caso o código do app use uma projeção de nome de coluna, como mime_type AS MimeType, lembre-se de que dispositivos com o Android 10 (API nível 29) e posteriores exigem nomes de coluna definidos na API MediaStore.

Se uma biblioteca dependente no seu app espera um nome de coluna indefinido na API Android, como MimeType, use CursorWrapper para traduzir o nome da coluna de forma dinâmica no processo do app.

Atribuir status pendente para arquivos de mídia armazenados

Em dispositivos com o Android 10 (API nível 29) e posteriores, seu app pode ter acesso exclusivo a um arquivo de mídia enquanto ele é gravado no disco, usando a sinalização IS_PENDING.

O snippet de código a seguir mostra como usar a sinalização IS_PENDING ao armazenar uma imagem no diretório correspondente à coleção MediaStore.Images:

Kotlin

    val values = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG")
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.IS_PENDING, 1)
    }

    val resolver = context.getContentResolver()
    val collection = MediaStore.Images.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    val item = resolver.insert(collection, values)

    resolver.openFileDescriptor(item, "w", null).use { pfd ->
        // Write data into the pending image.
    }

    // Now that we're finished, release the "pending" status, and allow other apps
    // to view the image.
    values.clear()
    values.put(MediaStore.Images.Media.IS_PENDING, 0)
    resolver.update(item, values, null, null)
    

Java

    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    values.put(MediaStore.Images.Media.IS_PENDING, 1);

    ContentResolver resolver = context.getContentResolver();
    Uri collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
    Uri item = resolver.insert(collection, values);

    try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(item, "w", null)) {
        // Write data into the pending image.
    } catch (IOException e) {
        e.printStackTrace();
    }

    // Now that we're finished, release the "pending" status, and allow other apps
    // to view the image.
    values.clear();
    values.put(MediaStore.Images.Media.IS_PENDING, 0);
    resolver.update(item, values, null, null);
    

Atualizar arquivos de mídia de outros apps

Em geral, se seu app usa armazenamento com escopo, ele não pode atualizar um arquivo de mídia que foi disponibilizado no armazenamento por outro app. Contudo, é possível conseguir consentimento do usuário para modificar esse arquivo capturando a RecoverableSecurityException gerada pela plataforma. Você pode então solicitar que o usuário conceda ao app acesso de gravação para esse item específico, conforme mostrado no snippet de código a seguir:

Kotlin

    try {
        // ...
    } catch (rse: RecoverableSecurityException) {
        val requestAccessIntentSender = rse.userAction.actionIntent.intentSender

        // In your code, handle IntentSender.SendIntentException.
        startIntentSenderForResult(requestAccessIntentSender, your-request-code,
                null, 0, 0, 0, null)
    }
    

Java

    try {
        // ...
    } catch (RecoverableSecurityException rse) {
        IntentSender requestAccessIntentSender = rse.getUserAction()
                .getActionIntent().getIntentSender();

        // In your code, handle IntentSender.SendIntentException.
        startIntentSenderForResult(requestAccessIntentSender, your-request-code,
                null, 0, 0, 0, null);
    }
    

Oferecer uma dica de onde os arquivos estão localizados

Quando seu app contribui com mídia em um dispositivo com o Android 10 (API nível 29), por padrão, a mídia é organizada de acordo com o tipo. Por exemplo, novos arquivos de imagem são colocados no diretório Environment.DIRECTORY_PICTURES por padrão, que corresponde à coleção MediaStore.Images.

Se seu app reconhece um local específico onde os arquivos devem ser armazenados, como Pictures/MyVacationPictures, defina MediaColumns.RELATIVE_PATH para oferecer uma dica ao sistema sobre onde armazenar os arquivos recém-gravados. Da mesma forma, você pode mover arquivos no disco durante uma chamada para update() alterando MediaColumns.RELATIVE_PATH ou MediaColumns.DISPLAY_NAME.

Casos de uso comuns

Esta seção descreve como atender diversos casos de uso comuns relacionados a arquivos de mídia.

Compartilhar arquivos de mídia

Alguns apps permitem que os usuários compartilhem arquivos de mídia entre si. Por exemplo, apps de mídia social permitem que os usuários compartilhem fotos e vídeos com amigos.

Para acessar os arquivos de mídia que os usuários querem compartilhar, use o processo descrito na seção sobre como acessar arquivos e como acessar volumes usando os nomes exclusivos.

Nos casos em que você fornece um conjunto de apps complementares, como um app de mensagens e um de perfil, configure o compartilhamento de arquivos usando URIs content://. Também recomendamos esse fluxo de trabalho como uma prática recomendada de segurança.

Utilizar documentos

Alguns apps usam documentos como a unidade de armazenamento em que os usuários inserem dados que eles podem querer compartilhar com amigos ou importar para outros documentos. Vários exemplos incluem um usuário abrindo um documento de produtividade empresarial ou um livro salvo como um arquivo EPUB.

Nesses casos, permita que o usuário escolha o arquivo a ser aberto invocando a intent ACTION_OPEN_DOCUMENT, que abre o app seletor de arquivos do sistema. Para mostrar apenas os tipos de arquivos compatíveis com seu app, inclua o Intent.EXTRA_MIME_TYPES extra na sua intent.

O exemplo ActionOpenDocument no GitHub (em inglês) mostra como usar ACTION_OPEN_DOCUMENT para abrir um arquivo depois de receber o consentimento do usuário.

Gerenciar grupos de arquivos

Em geral, apps de gerenciamento de arquivos e criação de mídia gerenciam grupos de arquivos em uma hierarquia de diretórios. Esses apps podem invocar a intent ACTION_OPEN_DOCUMENT_TREE para permitir que o usuário conceda acesso a uma árvore de diretórios inteira. Esse tipo de app conseguiria editar qualquer arquivo no diretório selecionado e em qualquer um dos subdiretórios dele.

Usando essa interface, os usuários podem acessar arquivos de uma instância de DocumentsProvider, compatível com qualquer solução local ou em nuvem.

O exemplo ActionOpenDocumentTree no GitHub (em inglês) mostra como usar ACTION_OPEN_DOCUMENT_TREE para abrir uma árvore de diretórios depois de receber o consentimento do usuário.