Usar o acesso a diretórios com escopo

Alguns apps, como os de fotos, normalmente só precisam acessar diretórios específicos de um armazenamento externo, como o diretório Pictures. As abordagens existentes para o acesso de armazenamentos externos não foram desenvolvidas para conceder facilmente acesso direcionado a diretórios para esses tipos de apps. Por exemplo:

  • Solicitar READ_EXTERNAL_STORAGE ou WRITE_EXTERNAL_STORAGE no seu manifesto permite o acesso a todos os diretórios públicos no armazenamento externo, o que pode ser mais do que o app precisa.
  • Usar o Framework de acesso ao armazenamento geralmente faz com que o usuário selecione diretórios por meio de uma IU de sistema, o que é desnecessário se seu app sempre acessa o mesmo diretório externo.

O Android 7.0 oferece uma API simplificada para acessar diretórios de armazenamento externo comuns.

Acessar um diretório de armazenamento externo

Use a classe StorageManager para associar a instância StorageVolume apropriada. Em seguida, crie um intent chamando o método StorageVolume.createAccessIntent() dessa instância. Use esse intent para acessar os diretórios de armazenamento externo. Para ver uma lista de todos os volumes disponíveis, inclusive volumes de mídias removíveis, use StorageManager.getStorageVolumes().

Se você tiver informações sobre um arquivo específico, use StorageManager.getStorageVolume(File) para associar o StorageVolume que contém o arquivo. Chame createAccessIntent() nesse StorageVolume para acessar o diretório de armazenamento externo do arquivo.

Em volumes secundários, como cartões SD externos, anule ao chamar createAccessIntent() para solicitar acesso ao volume inteiro, em vez de um diretório específico. createAccessIntent() retornará nulo se você anular no volume principal ou se passar um nome de diretório inválido.

O snippet de código a seguir é um exemplo de como abrir o diretório Pictures no armazenamento compartilhado principal:

Kotlin

    val sm = getSystemService(Context.STORAGE_SERVICE) as StorageManager
    val volume: StorageVolume = sm.primaryStorageVolume
    volume.createAccessIntent(Environment.DIRECTORY_PICTURES).also { intent ->
        startActivityForResult(intent, request_code)
    }
    

Java

    StorageManager sm = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
    StorageVolume volume = sm.getPrimaryStorageVolume();
    Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
    startActivityForResult(intent, request_code);
    

O sistema tenta conceder acesso ao diretório externo e, se necessário, confirma o acesso com o usuário por meio de uma IU simplificada:

Figura 1. Um aplicativo solicita acesso ao diretório Pictures.

Se o usuário conceder acesso, o sistema chamará sua modificação de onActivityResult() com um código de resultado de RESULT_OK e os dados de intent que contêm o URI. Use o URI recebido para acessar as informações do diretório, o que é semelhante a usar URIs retornados pelo Framework de acesso ao armazenamento.

Se o usuário não conceder acesso, o sistema chamará sua modificação de onActivityResult() com um código de resultado de RESULT_CANCELED e dados de intent nulos.

Ao conseguir acesso a um diretório externo específico, você também recebe acesso aos subdiretórios dentro do diretório em questão.

Acessar um diretório em mídia removível

Para usar o Acesso a diretórios com escopo para acessar diretórios em uma mídia removível, primeiro adicione um BroadcastReceiver que escute a notificação MEDIA_MOUNTED. Por exemplo:

    <receiver
        android:name=".MediaMountedReceiver"
        android:enabled="true"
        android:exported="true" >
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_MOUNTED" />
            <data android:scheme="file" />
        </intent-filter>
    </receiver>
    

Quando o usuário monta uma mídia removível, como um cartão SD, o sistema envia uma notificação MEDIA_MOUNTED. Essa notificação disponibiliza um objeto StorageVolume nos dados de intent que você pode usar para acessar os diretórios na mídia removível. O exemplo a seguir acessa o diretório Pictures na mídia removível:

Kotlin

    // BroadcastReceiver has already cached the MEDIA_MOUNTED
    // notification Intent in mediaMountedIntent
    val volume =
        mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME) as StorageVolume
    volume.createAccessIntent(Environment.DIRECTORY_PICTURES).also { intent ->
        startActivityForResult(intent, request_code)
    }
    

Java

    // BroadcastReceiver has already cached the MEDIA_MOUNTED
    // notification Intent in mediaMountedIntent
    StorageVolume volume = (StorageVolume)
        mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME);
    Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
    startActivityForResult(intent, request_code);
    

Práticas recomendadas

Quando possível, mantenha o URI de acesso a diretórios externos para que você não precise solicitar acesso ao usuário várias vezes. Quando o usuário conceder o acesso, chame getContentResolver() e, com o ContentResolver retornado, chame takePersistableUriPermission() com o URI de acesso ao diretório. O sistema manterá o URI, e as solicitações de acesso subsequentes retornarão RESULT_OK e não mostrarão a IU de confirmação para o usuário.

Se o usuário negar acesso a um diretório externo, não repita a solicitação imediatamente. Insistir em solicitações de acesso repetidas vezes gera uma experiência negativa para o usuário. Se uma solicitação for negada pelo usuário e o app solicitar acesso novamente, a IU exibirá uma caixa de seleção Não perguntar novamente.

Figura 1. Um aplicativo faz uma segunda solicitação de acesso à mídia removível.

Se o usuário selecionar Não perguntar novamente e negar a solicitação, todas as solicitações futuras para o diretório provenientes do seu app serão automaticamente negadas, e nenhuma IU de solicitação será apresentada ao usuário.