Échec de l’ouverture d’Android 10: EACCES (Autorisation refusée)

Récemment, pour l’une des applications sur lesquelles je travaillais, nous avons changé le SDK cible en niveau API 29 (Android 10). Depuis que le changement a été apporté, l’un de nos flux de travail s’est cassé. Le workflow est simple, nous demandons à l’utilisateur s’il souhaite télécharger un logo sur son profil. Nous laissons l’utilisateur choisir un fichier dans sa galerie multimédia et télécharger cette image sur le serveur.

L’exécution de ce flux de travail sur un appareil Android 10 n’a pas réussi à télécharger les images. L’erreur que nous avons vue était open failed: EACCES (Permission denied)

Alors, qu’est-ce qui a changé?

Selon la documentation Android.

Les applications ciblant Android 10 (niveau API 29) et supérieur bénéficient d’un accès limité à un périphérique de stockage externe, ou à un stockage limité, par défaut. Ces applications ne peuvent voir que leur répertoire spécifique à l’application

Également dans la documentation

Pour accéder à tout autre fichier créé par une autre application, y compris les fichiers dans un répertoire « téléchargements », votre application doit utiliser le Framework d’accès au stockage, qui permet à l’utilisateur de sélectionner un fichier spécifique.

Revenons à notre flux de travail, lorsqu’un utilisateur sélectionne un fichier dans la galerie, il n’y a aucune garantie que le fichier sélectionné a été ajouté ou modifié par une autre application. Donc, si l’utilisateur choisit un fichier qui appartient, disons, à une autre application, nous rencontrerions des problèmes d’autorisation. Alors, comment pouvons-nous résoudre cela?

Il y a deux façons de le faire, dans cet article, nous examinerons deux façons.

Si votre application n’est pas prête pour les modifications à venir pour Android 10, vous pouvez vous désinscrire en définissant l’indicateur requestLegacyExternalStorage sur true dans votre manifeste.

<manifest ... > <application android:requestLegacyExternalStorage="true" ... >
...
</application></manifest>

J’ai parlé à l’un des développeurs de l’équipe de confidentialité lors du récent Sommet de développement Android et leur recommandation était d’utiliser cette solution comme une solution temporaire

En utilisant openFileDescriptor

L’idée est de faire une copie du fichier que l’utilisateur choisit dans la galerie multimédia et de l’ajouter à votre répertoire de cache d’application.

Donc, la première étape consiste à ouvrir le fichier en utilisant openFileDescriptor

val parcelFileDescriptor = context.contentResolver.openFileDescriptor(fileUri, "r", null)

Créons un flux d’entrée à partir de celui-ci.

val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)

L’étape suivante consiste à obtenir le nom du fichier à partir de l’URI, nous pouvons écrire une fonction d’extension sur la classe ContentResolver pour cela. Veuillez noter que la logique ci-dessous est inspirée de l’exemple de code des documents Android.

fun ContentResolver.getFileName(fileUri: Uri): String {
var name = ""
val returnCursor = this.query(fileUri, null, null, null, null)
if (returnCursor != null) {
val nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
returnCursor.moveToFirst()
name = returnCursor.getString(nameIndex)
returnCursor.close()
}
return name
}

Alternativement, vous pouvez toujours donner votre propre nom de fichier généré.

Maintenant que nous savons comment obtenir le nom du fichier, créons un fichier dans le répertoire de cache de notre application.

val file = File(context.cacheDir, getFileName(context.contentResolver, fileUri))

Maintenant, faisons le flux de sortie de ce fichier

val outputStream = FileOutputStream(file)

La dernière étape consiste à copier le contenu du fichier d’origine dans notre fichier nouvellement créé dans notre répertoire de cache.

Vous pouvez effectuer cette étape de plusieurs manières, un moyen simple consiste à utiliser la fonction de copie de la classe IOUtils (org.apache.commons.io.IOUtils).

IOUtils.copy(inputStream, outputStream)

En rassemblant tout cela, le code final serait quelque chose comme ça.

val parcelFileDescriptor = context.contentResolver.openFileDescriptor(fileUri, "r", null)
parcelFileDescriptor?.let {
val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)
val file = File(context.cacheDir, context.contentResolver.getFileName(fileUri))
val outputStream = FileOutputStream(file)
IOUtils.copy(inputStream, outputStream)
}

Maintenant que votre application dispose d’une copie du fichier à télécharger, vous pouvez désormais le télécharger en toute sécurité sur votre serveur principal.

presenter.uploadLogo(file, ....)

Cela devrait résoudre les problèmes d’autorisation auxquels vous pouvez être confronté.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.