Abrir arquivos usando a biblioteca de acesso ao armazenamento

O Android 4.4 (API de nível 19) introduz a Biblioteca de acesso ao armazenamento (SAF, na sigla em inglês). A SAF simplifica a busca e a abertura de documentos, imagens e outros arquivos em todos os provedores de armazenamento de documentos dos usuários. Uma IU padrão e fácil de usar permite aos usuários procurar arquivos e acessar documentos recentes de maneira consistente em todos os aplicativos e provedores.

Os serviços de armazenamento local ou em nuvem podem participar desse ecossistema por meio da implementação de um DocumentsProvider que encapsula os serviços. Os aplicativos clientes que precisam acessar documentos de um provedor podem fazer a integração com a SAF com somente algumas linhas de código.

A SAF contém o seguinte:

  • Provedor de documentos — Provedor de conteúdo que oferece um serviço de armazenamento (como o Google Drive) para exibir os arquivos que gerencia. Um provedor de documentos é implementado como uma subclasse da classe DocumentsProvider. O esquema do provedor de documentos é baseado em uma hierarquia de arquivos tradicional, mas depende de como seu provedor de documentos armazena fisicamente os dados. A plataforma Android inclui vários provedores de documentos integrados, como downloads, imagens e vídeos.
  • Aplicativo cliente — Aplicativo personalizado que chama o intent ACTION_OPEN_DOCUMENT e/ou ACTION_CREATE_DOCUMENT e recebe os arquivos retornados pelos provedores de documentos.
  • Seletor — IU de sistema que permite aos usuários acessar documentos de todos os provedores de documentos que satisfazem os critérios de pesquisa do aplicativo cliente.

Estes são alguns dos recursos oferecidos pela SAF:

  • Permite que usuários procurem conteúdo de todos os provedores de documentos, não somente de um único aplicativo.
  • Isso permite que o aplicativo tenha acesso persistente e de longo prazo a documentos de propriedade de um provedor de documentos. Por meio desse acesso, os usuários podem adicionar, editar, salvar e excluir arquivos no provedor.
  • É compatível com diversas contas de usuário e raízes transitórias como provedores de armazenamento USB, que só aparecem se o dispositivo estiver conectado.

Visão geral

A SAF consiste em um provedor de conteúdo que é uma subclasse da classe DocumentsProvider. Dentro de um provedor de documentos, os dados são estruturados como uma hierarquia de arquivo tradicional:

modelo de dados

Imagem 1. Modelo de dados do provedor de documentos. Uma raiz aponta para um único documento, que então inicia a distribuição de dados em toda a árvore.

Observe o seguinte:

  • Cada provedor de documentos relata uma ou mais “raízes”, que são pontos de partida para explorar uma árvore de documentos. Cada raiz tem uma COLUMN_ROOT_ID única e aponta para um documento (um diretório) representando o conteúdo sob essa raiz. As raízes são dinâmicas por design para oferecer suporte a casos de uso como várias contas, dispositivos de armazenamento USB transitórios ou login/logout do usuário.
  • Sob cada raiz há um documento único. Esse documento indica 1 a N documentos, e cada um deles pode indicar 1 a N documentos.
  • Cada back-end de armazenamento exibe arquivos e diretórios individuais e os referenciando com um COLUMN_DOCUMENT_ID único. Os códigos de documentos precisam ser únicos e não podem ser alterados depois de emitidos. Isso porque eles são usados para concessões persistentes do URI em reinicializações do dispositivo.
  • Os documentos são arquivos ou diretórios que podem ser abertos (com um tipo MIME específico) contendo documentos adicionais (com o tipo MIME MIME_TYPE_DIR).
  • Cada documento pode ter diferentes funcionalidades, conforme descrito por COLUMN_FLAGS. Por exemplo, FLAG_SUPPORTS_WRITE, FLAG_SUPPORTS_DELETE e FLAG_SUPPORTS_THUMBNAIL. O mesmo COLUMN_DOCUMENT_ID pode ser incluído em vários diretórios.

Controle de fluxo

Como indicado anteriormente, o modelo de dados do provedor de documentos se baseia em uma hierarquia de arquivo tradicional. No entanto, você pode armazenar fisicamente seus dados da maneira que quiser, contanto que possa acessá-los usando a API DocumentsProvider. Por exemplo, seria possível usar armazenamento em nuvem com base em tag para os dados.

A imagem 2 mostra como um aplicativo de fotos poderia usa a SAF para acessar dados armazenados:

aplicativo

Imagem 2. Fluxo da estrutura de acesso ao armazenamento

Observe o seguinte:

  • Na SAF, provedores e clientes não interagem diretamente. O cliente solicita permissão para interagir com arquivos (ou seja, para ler, editar, criar ou excluir arquivos).
  • A interação começa quando um aplicativo (neste exemplo, um aplicativo de foto) dispara o intent ACTION_OPEN_DOCUMENT ou ACTION_CREATE_DOCUMENT. O intent pode conter filtros para refinar ainda mais os critérios. Por exemplo, “quero todos os arquivos que podem ser abertos e que tenham o tipo MIME de tal imagem”.
  • Ao disparar o intent, o seletor do sistema contata cada provedor registrado e exibe as raízes de conteúdo correspondentes ao usuário.
  • O seletor fornece aos usuários uma interface padrão para acessar documentos, embora os provedores de documentos subjacentes possam ser bem diferentes. Por exemplo, a imagem 2 exibe um provedor do Google Drive, um de USB e outro de nuvem.

A imagem 3 exibe um seletor com uma conta do Google Drive selecionada por um usuário que fazia uma pesquisa. Ela também mostra todas as raízes disponíveis para o aplicativo cliente.

seletor

Imagem 3. Seletor

Quando o usuário seleciona o Google Drive, as imagens são exibidas como ilustrado na imagem 4. Desse momento em diante, o usuário pode interagir com elas de todas as formas compatíveis com o provedor e o aplicativo cliente.

seletor

Imagem 4. Imagens

Programação de um aplicativo cliente

No Android 4.3 e em versões anteriores, para o aplicativo recuperar um arquivo de outro aplicativo, ele precisa chamar um intent como ACTION_PICK ou ACTION_GET_CONTENT. Em seguida, o usuário precisa selecionar um único aplicativo para retirar um arquivo, e o aplicativo selecionado deve fornecer uma interface do usuário para a busca e a seleção dos arquivos disponíveis.

No Android 4.4 e em versões posteriores, existe a opção adicional de usar o intent ACTION_OPEN_DOCUMENT, que exibe uma IU do seletor controlada pelo sistema que permite ao usuário procurar todos os arquivos disponibilizados por outros aplicativos. Nessa IU exclusiva, o usuário pode selecionar um arquivo de qualquer aplicativo compatível.

ACTION_OPEN_DOCUMENT não serve para substituir ACTION_GET_CONTENT. O uso de cada um deles depende da necessidade do aplicativo:

  • Use ACTION_GET_CONTENT se quiser que o aplicativo simplesmente leia ou importe dados. Nessa abordagem, o aplicativo importa uma cópia dos dados, assim como um arquivo de imagem.
  • Use ACTION_OPEN_DOCUMENT se quiser que o aplicativo tenha acesso persistente e de longo prazo a documentos de propriedade de um provedor de documentos. Um exemplo seria um aplicativo de edição de fotos que permite aos usuários editar imagens armazenadas em um provedor de documentos.

Esta seção descreve como programar aplicativos clientes com base nos intents ACTION_OPEN_DOCUMENT e ACTION_CREATE_DOCUMENT.

O snippet a seguir usa ACTION_OPEN_DOCUMENT para pesquisar provedores de documentos que contenham arquivos de imagem:

Kotlin

private const val READ_REQUEST_CODE: Int = 42
...
/**
 * Fires an intent to spin up the "file chooser" UI and select an image.
 */
fun performFileSearch() {

    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
    // browser.
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        // Filter to only show results that can be "opened", such as a
        // file (as opposed to a list of contacts or timezones)
        addCategory(Intent.CATEGORY_OPENABLE)

        // Filter to show only images, using the image MIME data type.
        // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
        // To search for all documents available via installed storage providers,
        // it would be "*/*".
        type = "image/*"
    }

    startActivityForResult(intent, READ_REQUEST_CODE)
}

Java

private static final int READ_REQUEST_CODE = 42;
...
/**
 * Fires an intent to spin up the "file chooser" UI and select an image.
 */
public void performFileSearch() {

    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
    // browser.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

    // Filter to only show results that can be "opened", such as a
    // file (as opposed to a list of contacts or timezones)
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Filter to show only images, using the image MIME data type.
    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
    // To search for all documents available via installed storage providers,
    // it would be "*/*".
    intent.setType("image/*");

    startActivityForResult(intent, READ_REQUEST_CODE);
}

Observe o seguinte:

  • Quando o aplicativo dispara o intent ACTION_OPEN_DOCUMENT, ele aciona um seletor que exibe todos os provedores de documentos compatíveis.
  • A adição da categoria CATEGORY_OPENABLE ao intent filtra os resultados, que exibem somente documentos que podem ser abertos, como os arquivos de imagem.
  • A declaração intent.setType("image/*") filtra ainda mais para exibir somente documentos que tenham o tipo de dados MIME da imagem.

Processamento de resultados

Depois que o usuário seleciona um documento no selecionador, onActivityResult() é chamado. O parâmetro resultData contém o URI que aponta para o documento selecionado. Extraia o URI usando getData(). Depois, será possível usá-lo para recuperar o documento desejado pelo usuário. Por exemplo:

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {

    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
    // response to some other intent, and the code below shouldn't run at all.

    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        // The document selected by the user won't be returned in the intent.
        // Instead, a URI to that document will be contained in the return intent
        // provided to this method as a parameter.
        // Pull that URI using resultData.getData().
        resultData?.data?.also { uri ->
            Log.i(TAG, "Uri: $uri")
            showImage(uri)
        }
    }
}

Java

@Override
public void onActivityResult(int requestCode, int resultCode,
        Intent resultData) {

    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
    // response to some other intent, and the code below shouldn't run at all.

    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        // The document selected by the user won't be returned in the intent.
        // Instead, a URI to that document will be contained in the return intent
        // provided to this method as a parameter.
        // Pull that URI using resultData.getData().
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            Log.i(TAG, "Uri: " + uri.toString());
            showImage(uri);
        }
    }
}

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

fun dumpImageMetaData(uri: Uri) {

    // The query, since it only applies to a single document, will only return
    // one row. There's no need to filter, sort, or select fields, since 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 since an
            // int can't be null in Java, the behavior is implementation-specific,
            // which is just a fancy term for "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, since it only applies to a single document, will only return
    // one row. There's no need to filter, sort, or select fields, since 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 since an
            // int can't be null in Java, the behavior is implementation-specific,
            // which is just a fancy term for "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();
    }
}

Abertura de um documento

Assim que tiver o URI de um documento, você poderá abri-lo ou fazer o que quiser com ele.

Bitmap

Veja um exemplo de como abrir um Bitmap:

Kotlin

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

Observe que não se deve realizar essa operação no thread de IU. Faça isso em segundo plano usando AsyncTask. Assim que abrir o bitmap, você poderá exibi-lo em uma ImageView.

Obtenção de uma InputStream

Abaixo há um exemplo de como conseguir uma InputStream desse URI. Neste snippet, as linhas do arquivo estão sendo lidas em uma string:

Kotlin

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

Criação de um documento

Você pode usar o intent ACTION_CREATE_DOCUMENT para criar um novo documento em um provedor correspondente. Para criar um arquivo, forneça um tipo MIME e um nome para o arquivo ao intent e ative-o com um código de solicitação exclusivo. O resto você não precisa fazer:

Kotlin

// Here are some examples of how you might call this method.
// The first parameter is the MIME type, and the second parameter is the name
// of the file you are creating:
//
// createFile("text/plain", "foobar.txt");
// createFile("image/png", "mypicture.png");

// Unique request code.
private const val WRITE_REQUEST_CODE: Int = 43
...
private fun createFile(mimeType: String, fileName: String) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        // Filter to only show results that can be "opened", such as
        // a file (as opposed to a list of contacts or timezones).
        addCategory(Intent.CATEGORY_OPENABLE)

        // Create a file with the requested MIME type.
        type = mimeType
        putExtra(Intent.EXTRA_TITLE, fileName)
    }

    startActivityForResult(intent, WRITE_REQUEST_CODE)
}

Java

// Here are some examples of how you might call this method.
// The first parameter is the MIME type, and the second parameter is the name
// of the file you are creating:
//
// createFile("text/plain", "foobar.txt");
// createFile("image/png", "mypicture.png");

// Unique request code.
private static final int WRITE_REQUEST_CODE = 43;
...
private void createFile(String mimeType, String fileName) {
    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);

    // Filter to only show results that can be "opened", such as
    // a file (as opposed to a list of contacts or timezones).
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Create a file with the requested MIME type.
    intent.setType(mimeType);
    intent.putExtra(Intent.EXTRA_TITLE, fileName);
    startActivityForResult(intent, WRITE_REQUEST_CODE);
}

Ao criar um novo documento, é possível conseguir o URI em onActivityResult() para que seja possível continuar a gravação.

Exclusão de um documento

Se você tem um URI de um documento e os Document.COLUMN_FLAGS do documento contêm SUPPORTS_DELETE, é possível excluir o documento. Por exemplo:

Kotlin

DocumentsContract.deleteDocument(contentResolver, uri)

Java

DocumentsContract.deleteDocument(getContentResolver(), uri);

Edição de um documento

Você pode usar a SAF para editar um documento de texto. O snippet aciona o intent ACTION_OPEN_DOCUMENT e usa a categoria CATEGORY_OPENABLE para exibir somente arquivos que possam ser abertos. Ele filtra ainda mais para exibir somente arquivos de texto:

Kotlin

private const val EDIT_REQUEST_CODE: Int = 44
/**
 * Open a file for writing and append some text to it.
 */
private fun editDocument() {
    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
    // file browser.
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        // Filter to only show results that can be "opened", such as a
        // file (as opposed to a list of contacts or timezones).
        addCategory(Intent.CATEGORY_OPENABLE)

        // Filter to show only text files.
        type = "text/plain"
    }

    startActivityForResult(intent, EDIT_REQUEST_CODE)
}

Java

private static final int EDIT_REQUEST_CODE = 44;
/**
 * Open a file for writing and append some text to it.
 */
 private void editDocument() {
    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
    // file browser.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

    // Filter to only show results that can be "opened", such as a
    // file (as opposed to a list of contacts or timezones).
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Filter to show only text files.
    intent.setType("text/plain");

    startActivityForResult(intent, EDIT_REQUEST_CODE);
}

Em seguida, de onActivityResult() (consulte Processamento de resultados), você pode chamar o código para realizar a edição. O snippet a seguir recebe um FileOutputStream do ContentResolver. Por padrão, o modo de gravação é usado. Solicite a menor quantidade de acesso necessária. Por isso, não solicite acesso de leitura e gravação se só for necessária a gravação:

Kotlin

private fun alterDocument(uri: Uri) {
    try {
        contentResolver.openFileDescriptor(uri, "w")?.use {
            // use{} lets the document provider know you're done by automatically closing the stream
            FileOutputStream(it.fileDescriptor).use {
                it.write(
                    ("Overwritten by MyCloud 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 by MyCloud 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();
    }
}

Manutenção de permissões

Quando o aplicativo 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. Contudo, suponhamos que seu aplicativo seja de edição de imagens, e você queira que os usuários acessem as últimas cinco imagens editadas diretamente do aplicativo. Se o dispositivo do usuário foi reiniciado, você teria que enviar o usuário de volta ao seletor do sistema para encontrar os arquivos, o que, obviamente, não é o ideal.

Para evitar que isso aconteça, você pode manter as permissões que o sistema dá ao aplicativo. Assim, o aplicativo “toma” a concessão de permissão do URI persistente que o sistema está oferecendo. Isso concede ao usuário um acesso contínuo aos arquivos por meio do aplicativo, mesmo se o dispositivo for reiniciado:

Kotlin

val takeFlags: Int = intent.flags and
        (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);

Há um último passo. Os URIs acessados mais recentemente pelo seu aplicativo não serão mais válidos, já que outro aplicativo pode ter excluído ou modificado um documento. Portanto, será preciso sempre chamar getContentResolver().takePersistableUriPermission() para verificar se há dados mais recentes.

Abertura de arquivos virtuais

O Android 7.0 adiciona o conceito de arquivos virtuais à Biblioteca de acesso ao armazenamento. Mesmo que os arquivos virtuais não tenham uma representação binária, seu aplicativo cliente pode abrir o conteúdo coagindo-os em um tipo de arquivo diferente ou exibindo esses arquivos usando um intent ACTION_VIEW.

Para abrir arquivos virtuais, seu aplicativo 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.

Para conseguir o URI para um documento virtual em seu aplicativo, é preciso criar um Intent para abrir a IU do seletor de arquivos, como o código exibido anteriormente em Pesquisa de documentos.

Depois que o usuário faz uma seleção, o sistema chama o método onActivityResult(), como mostrado anteriormente em Resultados do processo. Seu aplicativo pode recuperar o URI do arquivo e, em seguida, determinar se o arquivo é virtual usando um método semelhante ao 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 arquivo é virtual, você poderá forçar o arquivo a um tipo MIME alternativo, como um arquivo de imagem. 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 &lt; 1) {
        throw new FileNotFoundException();
    }

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

Para mais informações sobre arquivos virtuais e como gerenciá-los no aplicativo cliente da Biblioteca de acesso ao armazenamento, assista ao vídeo Virtual files in the storage access framework (“Arquivos virtuais na Biblioteca de acesso ao armazenamento”, vídeo em inglês).

Para códigos de amostra relacionados a essa página, consulte:

Para vídeos relacionados a essa página, consulte:

Para outras informações relacionadas, consulte: