Android 10 aperto non riuscito: EACCES (Permesso negato)

Recentemente per una delle app su cui stavo lavorando, abbiamo cambiato l’SDK di destinazione in API level 29 (Android 10). Da quando è stata apportata la modifica, uno dei nostri flussi di lavoro si è rotto. Il flusso di lavoro è semplice, chiediamo all’utente se vuole caricare un logo sul proprio profilo. Lasciamo che l’utente scelga un file dalla propria galleria multimediale e carichi questa immagine sul server.

L’esecuzione di questo flusso di lavoro su un dispositivo Android 10 non è riuscita a caricare le immagini. L’errore che abbiamo visto era open failed: EACCES (Permission denied)

Quindi, cosa è cambiato?

Secondo la documentazione di Android.

Le app destinate ad Android 10 (livello API 29) e versioni successive ricevono l’accesso con ambito in un dispositivo di archiviazione esterno o storage con ambito, per impostazione predefinita. Tali app possono vedere solo la directory specifica dell’app

Anche nella documentazione

Per accedere a qualsiasi altro file creato da un’altra app, inclusi i file in una directory “download”, l’app deve utilizzare Storage Access Framework, che consente all’utente di selezionare un file specifico.

Torna al nostro flusso di lavoro, quando un utente sceglie un file dalla galleria, non vi è alcuna garanzia che il file che è stato scelto è stato aggiunto o modificato da qualche altra applicazione. Quindi, se l’utente sceglie un file che diciamo appartiene a un’altra app, ci imbatteremo nei problemi di autorizzazione. Quindi, come risolviamo questo?

Ci sono un paio di modi per farlo, in questo articolo vedremo in due modi.

Se la tua app non è pronta per le modifiche in arrivo per Android 10, puoi “opt-out” impostando il flag requestLegacyExternalStorage su true nel manifest.

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

ho parlato con uno degli sviluppatori di Privacy dalla squadra durante il recente Android dev Summit e la loro raccomandazione è di utilizzare questa soluzione come un temporaneo

Utilizzando openFileDescriptor

L’idea è di fare una copia del file che l’utente sceglie nella galleria multimediale e aggiungerlo alla tua applicazione, la directory della cache.

Quindi, il primo passo è aprire il file usando openFileDescriptor

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

Creiamo un flusso di input da esso.

val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)

Il passo successivo è ottenere il nome del file dall’URI, possiamo scrivere una funzione di estensione sulla classe ContentResolver per questo. Si prega di notare che la logica di seguito è ispirata al codice di esempio da Android docs.

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
}

In alternativa, puoi sempre dare il tuo nome file generato.

Ora che sappiamo come ottenere il nome del file, creiamo un file nella directory cache della nostra applicazione.

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

Ora facciamo OutputStream da questo file

val outputStream = FileOutputStream(file)

Il passo finale è copiare il contenuto del file originale nel nostro file appena creato nella nostra directory cache.

Puoi fare questo passaggio in diversi modi, un modo semplice è usare la funzione di copia dalla classe IOUtils(org.apache.commons.io.IOUtils).

IOUtils.copy(inputStream, outputStream)

Mettendo tutto insieme, il codice finale sarebbe qualcosa del genere.

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

Ora che la tua app ha una copia del file che deve essere caricato, puoi caricarlo in modo sicuro sul tuo server backend.

presenter.uploadLogo(file, ....)

Questo dovrebbe risolvere i problemi di autorizzazione che potresti incontrare.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.