web-dev-qa-db-fra.com

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

Je cherche un moyen de partager correctement (et non pas OPEN) un fichier interne avec une application externe en utilisant Android Bibliothèque de support (FileProvider ).

En suivant l'exemple de la documentation,

<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 utiliser 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, car FLAG_GRANT_READ_URI_PERMISSION n'accorde l'autorisation que pour l'URI spécifié sur le data de l'intention, et non sur la valeur du EXTRA_STREAM extra (comme défini par setStream).

J'ai essayé de compromettre la sécurité en définissant Android:exported sur true pour le fournisseur, mais FileProvider vérifie en interne si elle-même est exportée. Dans ce cas, une exception est levée.

123

En utilisant FileProvider depuis la bibliothèque de support, vous devez accorder et révoquer manuellement les autorisations (au moment de l'exécution) pour que d'autres applications puissent lire des Uri spécifiques. Utilisez les méthodes Context.grantUriPermission et Context.revokeUriPermission .

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 package, vous pouvez accorder l'autorisation à toutes les applications pouvant gérer une intention spécifique:

//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 le documentation :

  • Placez l'URI du contenu dans une intention en appelant setData ().
  • Ensuite, appelez la méthode Intent.setFlags () avec FLAG_GRANT_READ_URI_PERMISSION ou FLAG_GRANT_WRITE_URI_PERMISSION, ou les deux.
  • Enfin, envoyez l’intention à une autre application. Le plus souvent, vous le faites en appelant setResult ().

    Les autorisations accordées dans une intention restent en vigueur tant que la pile de l'activité réceptrice est active. Lorsque la pile se termine, le
    Les autorisations sont automatiquement supprimées. Autorisations accordées à un
    L’activité dans une application client est automatiquement étendue à d’autres
    composants de cette application

Btw. si nécessaire, vous pouvez copier source de FileProvider et changer la méthode attachInfo pour empêcher le fournisseur de vérifier s'il est exporté.

141
Leszek

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

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>

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

L'autorisation est révoquée avec revokeFileReadPermission () dans les méthodes onResume et onDestroy () du fragment ou de l'activité.

23
Oliver Kranz

Échantillon de code totalement opérationnel pour partager un fichier à partir du dossier de l'application interne. 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);
23
Divers

Comme, comme le dit Phil dans son commentaire sur la question initiale, ceci est unique et qu'il n'y a aucune autre information sur SO dans google, j'ai pensé que je devrais également partager mes résultats:

Dans mon application, FileProvider fonctionnait immédiatement pour partager des fichiers en utilisant l'intention de partage. Aucune configuration spéciale ou code particulier n'était nécessaire, à part cela pour configurer FileProvider. Dans mon fichier manifest.xml, 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 peux partager sans problème mon magasin de fichiers dans le stockage privé de mes applications avec des applications telles que Gmail et Google Drive.

15
Luke Sleeman

Autant que je sache, cela ne fonctionnera que sur les versions les plus récentes d'Android. Vous devrez donc probablement trouver un moyen différent de le faire. Cette solution fonctionne pour moi sur la version 4.4, mais pas sur la version 4.0 ou 2.3.3. Ce ne sera donc pas un moyen utile de partager du contenu pour une application destinée à être exécutée sur un périphérique Android.

Dans manifest.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 note de la façon dont vous spécifiez les autorités. Vous devez spécifier l'activité à partir de laquelle vous allez créer l'URI et lancer l'intention de partage. Dans ce cas, l'activité s'appelle SharingActivity. Cette exigence n'est pas évidente dans la documentation de Google!

chemin_fichiers.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 manière dont vous spécifiez le chemin. La valeur ci-dessus est la racine de votre stockage interne privé.

Dans 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, c’est probablement une bonne idée de vous assurer que vous avez bien enregistré le fichier 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!");
}
14
Rasmusob

Dans mon application, FileProvider fonctionne parfaitement et je peux joindre des fichiers internes stockés dans un répertoire de fichiers à des clients de messagerie tels que Gmail, Yahoo, etc.

Comme indiqué dans la documentation Android, 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 de fichiers racine, le fichier pathpaths.xml était le suivant:

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

Cela a fonctionné pour moi. J'espère que cela aide :)

7
Adarsh Chithran

Si vous obtenez une image de l'appareil photo, aucune de ces solutions ne fonctionne pour Android 4.4. Dans ce cas, il vaut mieux 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);
}
2
CoolMind

Je souhaite partager quelque chose qui nous a bloqués pendant quelques jours: le code du fournisseur de fichier DOIT être inséré entre les balises de l'application, pas après. C'est peut-être trivial, mais ce n'est jamais précisé, et j'ai pensé que j'aurais pu aider quelqu'un! (merci encore à piolo94)

1
Eric Draven

juste pour améliorer la réponse donnée ci-dessus: si vous obtenez 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
Deepak Singh