O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Acessar documentos e outros arquivos do armazenamento compartilhado

Em dispositivos que executam o Android 4.4 (API de nível 19) e versões mais recentes, seu app pode interagir com um provedor de documentos, incluindo volumes de armazenamento externo e armazenamento baseado na nuvem, usando o framework de acesso ao armazenamento. Esse framework permite que os usuários interajam com um seletor de sistema para escolher um provedor de documentos e selecionar documentos específicos e outros arquivos para seu app criar, abrir ou modificar.

Como o usuário está envolvido na seleção dos arquivos ou diretórios que seu app pode acessar, esse mecanismo não requer nenhuma permissão do sistema, e o controle e a privacidade do usuário foram aprimorados. Além disso, esses arquivos, armazenados fora de um diretório específico do app e fora do armazenamento de mídia, permanecem no dispositivo após a desinstalação do app.

O uso do framework envolve as seguintes etapas:

  1. Um app invoca uma intent que contém uma ação relacionada ao armazenamento. Essa ação corresponde a um caso de uso específico que o framework disponibiliza.
  2. O usuário vê um seletor de sistema que permite que ele procure um provedor de documentos e escolha um local ou documento em que a ação relacionada ao armazenamento ocorra.
  3. O app recebe acesso de leitura e gravação a um URI que representa o local ou documento escolhido pelo usuário. Com esse URI, o app pode executar operações no local escolhido.

No entanto, se o app usa o armazenamento de mídia, você precisará solicitar a permissão READ_EXTERNAL_STORAGE para acessar os arquivos de mídia de outros apps. Em dispositivos que executam o Android 9 (API de nível 28) ou versões anteriores, seu app precisa solicitar a permissão READ_EXTERNAL_STORAGE para acessar qualquer arquivo de mídia, incluindo os arquivos de mídia que seu app criou.

Este guia explica os diferentes casos de uso compatíveis com o framework para trabalhar com arquivos e outros documentos. Além disso, também explica como executar operações no local selecionado pelo usuário.

Casos de uso para acessar documentos e outros arquivos

O framework de acesso ao armazenamento é compatível com os seguintes casos de uso para acessar arquivos e outros documentos.

Criar um novo arquivo
A ação da intent ACTION_CREATE_DOCUMENT permite que os usuários salvem um arquivo em um local específico.
Abrir um documento ou arquivo
A ação da intent ACTION_OPEN_DOCUMENT permite que os usuários selecionem um documento ou arquivo específico para abrir.
Conceder acesso ao conteúdo de um diretório
A ação da intent ACTION_OPEN_DOCUMENT_TREE, disponível no Android 5.0 (API de nível 21) e em versões mais recentes, permite que os usuários selecionem um diretório específico, concedendo ao app acesso a todos os arquivos e subdiretórios nesse diretório.

As seções a seguir dão orientações sobre como configurar cada caso de uso.

Criar um novo arquivo

Use a ação da intent ACTION_CREATE_DOCUMENT para carregar o seletor de arquivos do sistema e permitir que o usuário escolha um local para gravar o conteúdo de um arquivo. Esse processo é semelhante ao usado nas caixas de diálogo "Salvar como" usadas por outros sistemas operacionais.

Observação:ACTION_CREATE_DOCUMENT não é possível substituir um arquivo existente. Se o app tentar salvar um arquivo com o mesmo nome, o sistema acrescentará um número entre parênteses no final do nome do arquivo.

Por exemplo, se o app tentar salvar um arquivo chamado confirmation.pdf em um diretório que já tenha um arquivo com esse nome, o sistema salvará o novo arquivo com o nome confirmation.pdf (1).

Ao configurar a intent, especifique o nome do arquivo e o tipo MIME e, como opção, especifique o URI do arquivo ou diretório que o seletor de arquivos precisa exibir ao carregar pela primeira vez usando o extra da intent EXTRA_INITIAL_URI.

O snippet de código a seguir mostra como criar e invocar a intent de criação de arquivo:

Kotlin

// Request code for creating a PDF document.
const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, "invoice.pdf")

        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}

Java

// Request code for creating a PDF document.
private static final int CREATE_FILE = 1;

private void createFile(Uri pickerInitialUri) {
    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("application/pdf");
    intent.putExtra(Intent.EXTRA_TITLE, "invoice.pdf");

    // Optionally, specify a URI for the directory that should be opened in
    // the system file picker when your app creates the document.
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);

    startActivityForResult(intent, CREATE_FILE);
}

Abrir um arquivo

Seu app pode usar 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 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, especifique um tipo MIME. Como opção, você pode especificar o URI do arquivo que o seletor de arquivos exibirá ao carregar pela primeira vez usando o extra da intent EXTRA_INITIAL_URI.

O snippet de código a seguir mostra como criar e invocar a intent para abrir um documento PDF:

Kotlin

// Request code for selecting a PDF document.
const val PICK_PDF_FILE = 2

fun openFile(pickerInitialUri: uri) {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"

        // Optionally, specify a URI for the file that should appear in the
        // system file picker when it loads.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }

    startActivityForResult(intent, PICK_PDF_FILE)
}

Java

// Request code for selecting a PDF document.
private static final int PICK_PDF_FILE = 2;

private void openFile(Uri pickerInitialUri) {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("application/pdf");

    // Optionally, specify a URI for the file that should appear in the
    // system file picker when it loads.
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);

    startActivityForResult(intent, PICK_PDF_FILE);
}

Conceder acesso ao conteúdo de um diretório

Em geral, apps de gerenciamento de arquivos e criação de mídia gerenciam grupos de arquivos em uma hierarquia de diretórios. Para fornecer esse recurso no seu app, use a ação da intent ACTION_OPEN_DOCUMENT_TREE, que permite que o usuário conceda acesso a uma árvore de diretórios inteira. Seu app pode acessar qualquer arquivo no diretório selecionado e em qualquer subdiretório.

Ao usar ACTION_OPEN_DOCUMENT_TREE, o app recebe acesso somente aos arquivos no diretório selecionado pelo usuário. Você não tem acesso aos arquivos de outros apps que residem fora desse diretório selecionado pelo usuário. Esse acesso controlado permite que os usuários escolham exatamente qual conteúdo eles se sentem à vontade para compartilhar com o app.

Além disso, como opção, você pode especificar o URI do arquivo que o seletor de arquivos exibirá ao carregar pela primeira vez usando o extra da intent EXTRA_INITIAL_URI.

O snippet de código a seguir mostra como criar e invocar a intent para abrir um diretório:

Kotlin

fun openDirectory(pickerInitialUri: Uri) {
    // Choose a directory using the system's file picker.
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
        // Provide read access to files and sub-directories in the user-selected
        // directory.
        flags = Intent.FLAG_GRANT_READ_URI_PERMISSION

        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker when it loads.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }

    startActivityForResult(intent, your-request-code)
}

Java

public void openDirectory(Uri uriToLoad) {
    // Choose a directory using the system's file picker.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);

    // Provide read access to files and sub-directories in the user-selected
    // directory.
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    // Optionally, specify a URI for the directory that should be opened in
    // the system file picker when it loads.
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad);

    startActivityForResult(intent, your-request-code);
}

Realizar operações no local escolhido

Depois que o usuário selecionar um arquivo ou diretório usando o seletor de arquivos do sistema, você poderá recuperar o URI do item selecionado usando o seguinte código em onActivityResult():

Kotlin

override fun onActivityResult(
        requestCode: Int, resultCode: Int, resultData: Intent?) {
    if (requestCode == your-request-code
            && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for the document or directory that
        // the user selected.
        resultData?.data?.also { uri ->
            // Perform operations on the document using its URI.
        }
    }
}

Java

@Override
public void onActivityResult(int requestCode, int resultCode,
        Intent resultData) {
    if (requestCode == your-request-code
            && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for the document or directory that
        // the user selected.
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            // Perform operations on the document using its URI.
        }
    }
}

Ao receber uma referência ao URI do item selecionado, o app pode realizar várias operações nele. Por exemplo, você pode tanto acessar os metadados do item quanto editá-lo e excluí-lo.

As seções a seguir mostram como concluir ações nos arquivos selecionados pelo usuário.

Determinar as operações compatíveis com um provedor

Diferentes provedores de conteúdo permitem que operações distintas sejam realizadas em documentos, como copiá-los ou visualizar a miniatura deles. Para determinar quais operações são compatíveis com um provedor, verifique o valor de Document.COLUMN_FLAGS. A IU do seu app pode mostrar apenas as opções que são compatíveis com o provedor.

Manutenção de permissões

Quando o app abre um arquivo para leitura ou gravação, o sistema fornece uma concessão de permissão do URI para aquele arquivo, que dura até o dispositivo do usuário ser reiniciado. Suponha, no entanto, que seu app seja de edição de imagens e você queira que os usuários acessem as cinco imagens com edições mais recentes, feitas diretamente do app. Se o dispositivo do usuário tiver sido reiniciado, você precisará enviá-lo de volta ao seletor do sistema para encontrar os arquivos.

Para preservar o acesso a arquivos em reinicializações de dispositivos e criar uma experiência do usuário melhor, o app pode receber a concessão de permissão de URI persistente oferecida pelo sistema, como mostrado no snippet de código a seguir:

Kotlin

val contentResolver = applicationContext.contentResolver

val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
// Check for the freshest data.
contentResolver.takePersistableUriPermission(uri, takeFlags)

Java

final int takeFlags = intent.getFlags()
            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(uri, takeFlags);

Verificação de metadados de documentos

Quando tiver o URI de um documento, você terá acesso aos seus metadados. Esse snippet identifica os metadados de um documento especificado pelo URI e os registra:

Kotlin

val contentResolver = applicationContext.contentResolver

fun dumpImageMetaData(uri: Uri) {

    // The query, because it only applies to a single document, returns only
    // one row. There's no need to filter, sort, or select fields,
    // because we want all fields for one document.
    val cursor: Cursor? = contentResolver.query(
            uri, null, null, null, null, null)

    cursor?.use {
        // moveToFirst() returns false if the cursor has 0 rows. Very handy for
        // "if there's anything to look at, look at it" conditionals.
        if (it.moveToFirst()) {

            // Note it's called "Display Name". This is
            // provider-specific, and might not necessarily be the file name.
            val displayName: String =
                    it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
            Log.i(TAG, "Display Name: $displayName")

            val sizeIndex: Int = it.getColumnIndex(OpenableColumns.SIZE)
            // If the size is unknown, the value stored is null. But because an
            // int can't be null, the behavior is implementation-specific,
            // and unpredictable. So as
            // a rule, check if it's null before assigning to an int. This will
            // happen often: The storage API allows for remote files, whose
            // size might not be locally known.
            val size: String = if (!it.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                it.getString(sizeIndex)
            } else {
                "Unknown"
            }
            Log.i(TAG, "Size: $size")
        }
    }
}

Java

public void dumpImageMetaData(Uri uri) {

    // The query, because it only applies to a single document, returns only
    // one row. There's no need to filter, sort, or select fields,
    // because we want all fields for one document.
    Cursor cursor = getActivity().getContentResolver()
            .query(uri, null, null, null, null, null);

    try {
        // moveToFirst() returns false if the cursor has 0 rows. Very handy for
        // "if there's anything to look at, look at it" conditionals.
        if (cursor != null && cursor.moveToFirst()) {

            // Note it's called "Display Name". This is
            // provider-specific, and might not necessarily be the file name.
            String displayName = cursor.getString(
                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
            Log.i(TAG, "Display Name: " + displayName);

            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
            // If the size is unknown, the value stored is null. But because an
            // int can't be null, the behavior is implementation-specific,
            // and unpredictable. So as
            // a rule, check if it's null before assigning to an int. This will
            // happen often: The storage API allows for remote files, whose
            // size might not be locally known.
            String size = null;
            if (!cursor.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                size = cursor.getString(sizeIndex);
            } else {
                size = "Unknown";
            }
            Log.i(TAG, "Size: " + size);
        }
    } finally {
        cursor.close();
    }
}

Abrir um documento

Com uma referência ao URI de um documento, você pode abrir um documento para processamento adicional. Esta seção mostra exemplos de como abrir um bitmap e um fluxo de entrada.

Bitmap

O snippet de código a seguir mostra como abrir um arquivo Bitmap de acordo com seu URI:

Kotlin

val contentResolver = applicationContext.contentResolver

@Throws(IOException::class)
private fun getBitmapFromUri(uri: Uri): Bitmap {
    val parcelFileDescriptor: ParcelFileDescriptor =
            contentResolver.openFileDescriptor(uri, "r")
    val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor
    val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor)
    parcelFileDescriptor.close()
    return image
}

Java

private Bitmap getBitmapFromUri(Uri uri) throws IOException {
    ParcelFileDescriptor parcelFileDescriptor =
            getContentResolver().openFileDescriptor(uri, "r");
    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
    parcelFileDescriptor.close();
    return image;
}

Depois de abrir o bitmap, você poderá exibi-lo em uma ImageView.

Fluxo de entrada

O snippet de código a seguir mostra como abrir um objeto InputStream de acordo com seu URI. Neste snippet, as linhas do arquivo estão sendo lidas em uma string:

Kotlin

val contentResolver = applicationContext.contentResolver

@Throws(IOException::class)
private fun readTextFromUri(uri: Uri): String {
    val stringBuilder = StringBuilder()
    contentResolver.openInputStream(uri)?.use { inputStream ->
        BufferedReader(InputStreamReader(inputStream)).use { reader ->
            var line: String? = reader.readLine()
            while (line != null) {
                stringBuilder.append(line)
                line = reader.readLine()
            }
        }
    }
    return stringBuilder.toString()
}

Java

private String readTextFromUri(Uri uri) throws IOException {
    StringBuilder stringBuilder = new StringBuilder();
    try (InputStream inputStream =
            getContentResolver().openInputStream(uri);
            BufferedReader reader = new BufferedReader(
            new InputStreamReader(Objects.requireNonNull(inputStream)))) {
        String line;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
        }
    }
    return stringBuilder.toString();
}

Edição de um documento

Você pode usar o Framework de acesso ao armazenamento para editar um documento de texto.

O snippet de código a seguir substitui o conteúdo do documento representado pelo URI fornecido:

Kotlin

val contentResolver = applicationContext.contentResolver

private fun alterDocument(uri: Uri) {
    try {
        contentResolver.openFileDescriptor(uri, "w")?.use {
            FileOutputStream(it.fileDescriptor).use {
                it.write(
                    ("Overwritten at ${System.currentTimeMillis()}\n")
                        .toByteArray()
                )
            }
        }
    } catch (e: FileNotFoundException) {
        e.printStackTrace()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

Java

private void alterDocument(Uri uri) {
    try {
        ParcelFileDescriptor pfd = getActivity().getContentResolver().
                openFileDescriptor(uri, "w");
        FileOutputStream fileOutputStream =
                new FileOutputStream(pfd.getFileDescriptor());
        fileOutputStream.write(("Overwritten at " + System.currentTimeMillis() +
                "\n").getBytes());
        // Let the document provider know you're done by closing the stream.
        fileOutputStream.close();
        pfd.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Exclusão de um documento

Se você tiver o URI de um documento e as Document.COLUMN_FLAGS do documento contiverem SUPPORTS_DELETE, será possível excluí-lo. Exemplo:

Kotlin

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri)

Java

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);

Abrir um arquivo virtual

No Android 7.0 (API de nível 25) e versões mais recentes, seu app pode usar arquivos virtuais disponibilizados pelo framework de acesso ao armazenamento. Mesmo que os arquivos virtuais não tenham uma representação binária, seu app pode abrir o conteúdo forçando a conversão deles em um tipo de arquivo diferente ou exibindo esses arquivos usando uma ação da intent ACTION_VIEW.

Para abrir arquivos virtuais, seu app cliente precisa incluir uma lógica especial para gerenciá-los. Se você quiser acessar uma representação em bytes do arquivo (para visualizar o arquivo, por exemplo), será necessário solicitar um tipo MIME alternativo do provedor de documentos.

Depois que o usuário fizer uma seleção, use o URI nos dados dos resultados para determinar se o arquivo é virtual, conforme mostrado no snippet de código a seguir:

Kotlin

private fun isVirtualFile(uri: Uri): Boolean {
    if (!DocumentsContract.isDocumentUri(this, uri)) {
        return false
    }

    val cursor: Cursor? = contentResolver.query(
            uri,
            arrayOf(DocumentsContract.Document.COLUMN_FLAGS),
            null,
            null,
            null
    )

    val flags: Int = cursor?.use {
        if (cursor.moveToFirst()) {
            cursor.getInt(0)
        } else {
            0
        }
    } ?: 0

    return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0
}

Java

private boolean isVirtualFile(Uri uri) {
    if (!DocumentsContract.isDocumentUri(this, uri)) {
        return false;
    }

    Cursor cursor = getContentResolver().query(
        uri,
        new String[] { DocumentsContract.Document.COLUMN_FLAGS },
        null, null, null);

    int flags = 0;
    if (cursor.moveToFirst()) {
        flags = cursor.getInt(0);
    }
    cursor.close();

    return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
}

Depois de verificar se o documento é um arquivo virtual, você poderá forçar o arquivo a um tipo MIME alternativo, como "image/png". O snippet de código a seguir mostra como verificar se um arquivo virtual pode ser representado como uma imagem e, em caso afirmativo, recebe um stream de entrada a partir do arquivo virtual:

Kotlin

@Throws(IOException::class)
private fun getInputStreamForVirtualFile(
        uri: Uri, mimeTypeFilter: String): InputStream {

    val openableMimeTypes: Array<String>? =
            contentResolver.getStreamTypes(uri, mimeTypeFilter)

    return if (openableMimeTypes?.isNotEmpty() == true) {
        contentResolver
                .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
                .createInputStream()
    } else {
        throw FileNotFoundException()
    }
}

Java

private InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter)
    throws IOException {

    ContentResolver resolver = getContentResolver();

    String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter);

    if (openableMimeTypes == null ||
        openableMimeTypes.length < 1) {
        throw new FileNotFoundException();
    }

    return resolver
        .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
        .createInputStream();
}

Outros recursos

Para saber mais sobre como armazenar e acessar documentos e outros arquivos, consulte os seguintes recursos.

Amostras

Vídeos