Comment utiliser le support FileProvider pour partager du contenu avec d'autres applications?

je cherche un moyen de partager correctement (pas ouvrir) un fichier interne avec une application externe en utilisant la bibliothèque de Soutien Android FileProvider .

suivant l'exemple sur le docs,

<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>

et en utilisant ShareCompat pour partager un fichier avec d'autres applications comme suit:

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

ne fonctionne pas, puisque la FLAG_GRANT_READ_URI_PERMISSION n'accorde l'autorisation que pour L'Uri spécifié sur le data de l'intention, pas la valeur du EXTRA_STREAM supplémentaire (comme a été fixé par setStream ).

j'ai essayé de compromettre la sécurité en mettant android:exported à true pour le fournisseur, mais FileProvider contrôle interne si elle est exportée, quand donc, il jette une exception.

106
demandé sur Randy Sugianto 'Yuku' 2013-08-15 12:34:46

9 réponses

en utilisant FileProvider de la bibliothèque de soutien, vous devez accorder et révoquer manuellement les permissions(à l'exécution) pour d'autres applications pour lire L'Uri spécifique. Utilisez Le Contexte .grantUriPermission et Contexte.revokeUriPermission méthodes.

par exemple:

//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);

en dernier recours, si vous ne pouvez pas fournir le nom du paquet, vous pouvez accorder la permission à toutes les applications qui peuvent gérer des intentions spécifiques:

//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éthode Alternative selon la documentation :

  • mettez le contenu URI dans une intention en appelant setData().
  • ensuite, appelez la méthode intention.setFlags () avec FLAG_GRANT_READ_URI_PERMISSION ou FLAG_GRANT_WRITE_URI_PERMISSION ou les deux.
  • finalement, envoyer L'intention à une autre application. Le plus souvent, vous faites ceci par l'appel de setResult().

    les Permissions accordées dans une intention restent en vigueur tant que la cheminée de l'activité de réception est active. Lorsque la pile est terminée, le

    les autorisations sont automatiquement supprimés. Autorisations accordées à un

    Activité dans une application client sont automatiquement étendues à d'autres

    composants de cette application.

Btw. si vous en avez besoin, vous pouvez copier source de FileProvider et changer attachInfo méthode pour empêcher le fournisseur de vérifier si elle est exportée.

122
répondu Leszek 2015-06-27 14:37:19

Cette solution fonctionne pour moi depuis OS 4.4. Pour que cela fonctionne sur tous les appareils, j'ai ajouté une solution de contournement pour les appareils plus anciens. Cela garantit que la solution la plus sûre est toujours utilisée.

manifeste.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);
    }
}

la permission est révoquée avec revokeFileReadPermission () dans les méthodes onResume et onDestroy() du Fragment ou du Activité.

22
répondu Oliver Kranz 2016-04-06 08:41:51

exemple de code entièrement fonctionnel comment partager un fichier à partir du dossier interne de l'application. Testé sur Android 7 et 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>

Code lui-même

    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);
15
répondu Divers 2016-09-21 14:30:11

comme Phil le dit dans son commentaire sur la question originale, c'est unique et il n'y a pas d'autres informations sur ce sujet dans google, j'ai pensé que je devrais aussi partager mes résultats:

dans mon application FileProvider travaillé hors de la boîte pour partager des fichiers en utilisant l'intention de partager. Il n'y avait pas de configuration spéciale ou de code nécessaire, au-delà de cela pour configurer le FileProvider. Dans mon manifeste.j'ai placé:

    <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>

dans my_paths.xml j'ai:

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

dans mon code j'ai:

    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)));

et je suis en mesure de partager mes fichiers store dans mon apps de stockage privé avec des applications telles que Gmail et Google drive sans aucun problème.

13
répondu Luke Sleeman 2013-09-03 04:01:10

autant que je peux dire que cela ne fonctionnera que sur les nouvelles versions D'Android, donc vous aurez probablement à trouver une autre façon de le faire. Cette solution fonctionne pour moi sur 4.4, mais pas sur 4.0 ou 2.3.3, donc ce ne sera pas une façon utile de partager du contenu pour une application qui est destiné à fonctionner sur n'importe quel appareil Android.

dans le manifeste.xml:

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

prenez bonne note de la façon dont vous spécifiez les autorités. Vous devez préciser l'activité à partir de laquelle vous allez créer L'URI et lancer l'intention de partager, dans ce cas, l'activité s'appelle SharingActivity. Cette exigence n'est pas évidente dans les documents de Google!

file_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="just_a_name" path=""/>
</paths>

faites attention à la façon dont vous spécifiez le chemin. Les valeurs ci-dessus sont par défaut à la racine de votre stockage interne privé.

In SharingActivity.java:

Uri contentUri = FileProvider.getUriForFile(getActivity(),
"com.mydomain.myapp.SharingActivity", myFile);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share with"));

dans cet exemple, nous partageons une image JPEG.

Enfin, il est probablement une bonne idée de vous assurer que vous avez enregistré le fichier correctement et que vous pouvez y accéder avec quelque chose comme ceci:

File myFile = getActivity().getFileStreamPath("mySavedImage.jpeg");
if(myFile != null){
    Log.d(TAG, "File found, file description: "+myFile.toString());
}else{
    Log.w(TAG, "File not found!");
}
13
répondu Rasmusob 2015-05-20 13:05:24

dans mon application FileProvider fonctionne très bien, et je suis en mesure d'attacher des fichiers internes stockés dans le répertoire de fichiers pour envoyer des clients comme Gmail,Yahoo, etc.

dans mon manifeste comme mentionné dans la documentation Android que j'ai placé:

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

et comme mes fichiers étaient stockés dans le répertoire des fichiers racine, les chemins de fichiers.

 <paths>
<files-path path="." name="name" />

maintenant dans le code:

 File file=new File(context.getFilesDir(),"test.txt");

 Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE);

 shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
                                     "Test");

 shareIntent.setType("text/plain");

 shareIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
                                 new String[] {"email-address you want to send the file to"});

   Uri uri = FileProvider.getUriForFile(context,"com.package.name.fileprovider",
                                                   file);

                ArrayList<Uri> uris = new ArrayList<Uri>();
                uris.add(uri);

                shareIntent .putParcelableArrayListExtra(Intent.EXTRA_STREAM,
                                                        uris);


                try {
                   context.startActivity(Intent.createChooser(shareIntent , "Email:").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));                                                      


                }
                catch(ActivityNotFoundException e) {
                    Toast.makeText(context,
                                   "Sorry No email Application was found",
                                   Toast.LENGTH_SHORT).show();
                }
            }

ça a marché pour moi.Espérons que cela aide :)

6
répondu Adarsh Chithran 2014-10-28 13:27:27

juste pour améliorer la réponse donnée ci-dessus: si vous recevez NullPointerEx:

vous pouvez également utiliser getApplicationContext () sans contexte

                List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
                for (ResolveInfo resolveInfo : resInfoList) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
1
répondu Deepak Singh 2017-01-01 09:23:51

si vous obtenez une image de la caméra aucune de ces solutions ne fonctionne pour Android 4.4. Dans ce cas, il est préférable de vérifier les versions.

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);
}
1
répondu CoolMind 2018-03-01 16:10:37

je veux partager quelque chose qui nous a bloqué pendant quelques jours: le code de fileprovider doit être inséré entre les étiquettes d'application, pas après. C'est peut-être trivial, mais ça n'est jamais spécifié, et j'ai pensé que j'aurais pu aider quelqu'un! (merci encore à piolo94)

0
répondu Eric Draven 2018-08-23 11:52:22