Comment éviter l'erreur android.os.FileUriExposedException: file:///storage/emulated/0/ exposed beyond app through Intent.getData()

Depuis la version 7 d'Android (Nougat), la gestion de l'accès aux fichiers dans Android a été modifiée. Les chemins commençant par le protocole "file://" ne sont plus autorisés. Seul le préfixe "content://" est accepté, et il ne permet d'accéder qu'aux fichiers de l'application. Si vous souhaitez accéder aux fichiers en dehors de votre application de cette manière, vous obtenez une erreur de type "FileUriExposedException". Pour accéder à des fichiers situés en dehors de l'application, vous devez faire appel à la classe "FileProvider".

La classe "FileProvider" est disponible depuis Android 5 (la version 22 du SDK). Vous devez donc faire des contrôles de la version du SDK si vous voulez que votre application soit compatible avec une version plus ancienne. Dans votre application, vous devez implémenter votre propre classe qui héritera de la classe "FileProvider". Ce sera cette classe qui sera directement utilisée pour accéder aux fichiers.

public class MonFileProvider extends FileProvider {}

La modification suivante a lieu dans le fichier "AndroidManifest.xml". Dans la balise "Application". Créer une balise "Provider". Vous devez bien faire attention à spécifier une autorité unique dans l'attribut "android:authorities" afin d'éviter un conflit.

<provider
    android:name=".MonFileProvider"
    android:authorities="${applicationId}.nom.de.mon.package.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>

Vous devez ensuite créer le fichier "provider_paths" que vous avez mentionné dans le fichier "AndroidManifest.xml". Il doit se situer dans le dossier "res/xml" de votre application (créer le dossier s'il n'existe pas déjà). La balise "<file-path>" spécifiera un répertoire auquel l'application a accès. On peut en ajouter plusieurs. Si on indique le répertoire ".", cela correspond à la racine du système.

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

Il ne reste qu'à utiliser la classe dans votre application. On utilise pour cela la méthode "getUtiForFile()".

File dossierImage = new File(Context.getFilesDir(), "images");
File nouvelleImage = new File(dossierImage, "mon_image.jpg");
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + "mon.nom.de.package.provider", nouvelleImage);

Si vous utilisez la classe "Intent" pour faire en sorte que ce soit le système qui ouvre le fichier, vous devez ajouter une directive supplémentaire pour autoriser l'objet de la classe "Intent" à lire les URI.

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

Android