studio - toolbar android material design




Como usar o suporte FileProvider para compartilhar conteúdo para outros aplicativos? (6)

Esta solução funciona para mim desde o OS 4.4. Para que funcione em todos os dispositivos, adicionei uma solução alternativa para dispositivos mais antigos. Isso garante que sempre seja usada a solução mais segura.

Manifest.xml:

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.package.name.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>

file_paths.xml:

<paths>
    <files-path name="app_directory" path="directory/"/>
</paths>

Java:

public static void sendFile(Context context) {
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType("text/plain");
    String dirpath = context.getFilesDir() + File.separator + "directory";
    File file = new File(dirpath + File.separator + "file.txt");
    Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
    intent.putExtra(Intent.EXTRA_STREAM, uri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    // Workaround for Android bug.
    // grantUriPermission also needed for KITKAT,
    // see https://code.google.com/p/android/issues/detail?id=76683
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            context.grantUriPermission(packageName, attachmentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }
    if (intent.resolveActivity(context.getPackageManager()) != null) {
        context.startActivity(intent);
    }
}

public static void revokeFileReadPermission(Context context) {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        String dirpath = context.getFilesDir() + File.separator + "directory";
        File file = new File(dirpath + File.separator + "file.txt");
        Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
        context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
}

A permissão é revogada com revokeFileReadPermission () nos métodos onResume e onDestroy () do Fragment ou da Activity.

Eu estou procurando uma maneira de compartilhar corretamente (não abrir) um arquivo interno com aplicativo externo usando FileProvider da biblioteca de suporte do FileProvider .

Seguindo o exemplo nos documentos,

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.example.android.supportv4.my_files"
    android:grantUriPermissions="true"
    android:exported="false">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/my_paths" />
</provider>

e usando ShareCompat para compartilhar um arquivo para outros aplicativos da seguinte forma:

ShareCompat.IntentBuilder.from(activity)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

não funciona, uma vez que o FLAG_GRANT_READ_URI_PERMISSION apenas concede permissão para o Uri especificado nos data da intenção, não o valor do extra EXTRA_STREAM (como foi definido por setStream ).

Eu tentei comprometer a segurança, definindo android:exported para true para o provedor, mas FileProvider internamente verifica se ele é exportado, quando assim, ele lança uma exceção.


Exemplo de código totalmente funcional como compartilhar o arquivo da pasta interna do aplicativo. Testado no Android 7 e Android 5.

AndroidManifest.xml

</application>
   ....
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="android.getqardio.com.gmslocationtest"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
</application>

xml / provider_paths

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path
        name="share"
        path="external_files"/>
</paths>

Código em si

    File imagePath = new File(getFilesDir(), "external_files");
    imagePath.mkdir();
    File imageFile = new File(imagePath.getPath(), "test.jpg");

    // Write data in your file

    Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile);

    Intent intent = ShareCompat.IntentBuilder.from(this)
                .setStream(uri) // uri from FileProvider
                .setType("text/html")
                .getIntent()
                .setAction(Intent.ACTION_VIEW) //Change if needed
                .setDataAndType(uri, "image/*")
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

   startActivity(intent);

Quero compartilhar algo que nos bloqueou por alguns dias: o código do provedor de arquivos DEVE ser inserido entre as tags do aplicativo, não depois dele. Pode ser trivial, mas nunca especificado, e pensei que poderia ter ajudado alguém! (obrigado novamente a piolo94)


Se você obtiver uma imagem da câmera, nenhuma dessas soluções funcionará para o Android 4.4. Neste caso, é melhor verificar as versões.

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        uri = Uri.fromFile(file);
    } else {
        uri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".provider", file);
    }
    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
    startActivityForResult(intent, CAMERA_REQUEST);
}

Uma vez que, como diz Phil em seu comentário sobre a questão original, isso é único e não há outras informações sobre SO no google, achei que também deveria compartilhar meus resultados:

No meu aplicativo FileProvider trabalhou fora da caixa para compartilhar arquivos usando a intenção de compartilhar. Não havia nenhuma configuração especial ou código necessário, além disso, para configurar o FileProvider. No meu manifest.xml eu coloquei:

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.my.apps.package.files"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/my_paths" />
    </provider>

Em my_paths.xml eu tenho:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files" path="." />
</paths>

No meu código eu tenho:

    Intent shareIntent = new Intent();
    shareIntent.setAction(Intent.ACTION_SEND);
    shareIntent.setType("application/xml");

    Uri uri = FileProvider.getUriForFile(this, "com.my.apps.package.files", fileToShare);
    shareIntent.putExtra(Intent.EXTRA_STREAM, uri);

    startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file)));

E eu sou capaz de compartilhar meus arquivos de armazenamento no meu armazenamento privado de aplicativos com aplicativos como o Gmail e o Google Drive sem nenhum problema.


Usando o FileProvider da biblioteca de suporte, você precisa conceder e revogar manualmente permissões (em tempo de execução) para outros aplicativos lerem o Uri específico. Use os métodos Context.revokeUriPermission e Context.revokeUriPermission .

Por exemplo:

//grant permision for app with package "packegeName", eg. before starting other app via intent
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

//revoke permisions
context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

Como último recurso, se você não puder fornecer o nome do pacote, poderá conceder a permissão a todos os aplicativos que podem manipular intenções específicas:

//grant permisions for all apps that can handle given intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
...
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

Método alternativo de acordo com a documentation :

  • Coloque o URI de conteúdo em um Intenção chamando setData ().
  • Em seguida, chame o método Intent.setFlags () com FLAG_GRANT_READ_URI_PERMISSION ou FLAG_GRANT_WRITE_URI_PERMISSION ou ambos.
  • Por fim, envie a intenção para outro aplicativo. Na maioria das vezes, você faz isso chamando setResult ().

    As permissões concedidas em um Intent permanecerão em vigor enquanto a pilha da Activity de recebimento estiver ativa. Quando a pilha termina, o
    as permissões são removidas automaticamente. Permissões concedidas a um
    A atividade em um aplicativo cliente é automaticamente estendida para outros
    componentes desse aplicativo.

Btw. se você precisar, copie a origem do FileProvider e altere o método attachInfo para impedir que o provedor verifique se ele é exportado.





android-fileprovider