Un'applicazione Android vive in all'interno di una sandbox, ovvero un ambiente chiuso in cui l'applicazione opera in maniera sostanzialmente isolata dal resto del sistema. Talvolta, però, può essere necessario che essa debba "uscire" da questa gabbia per accedere a informazioni, funzionalità o apparati hardware del dispositivo. Per far sì che l'app acceda a tali servizi, essa deve possedere alcuni "permessi" speciali - le cosiddette permission - che è necessario dichiarare espressamente nel file AndroidManifest.xml. Ogni permission viene dichiarata tramite il tag <uses-permission>
, da collocare al di fuori del nodo <application>
:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="...." >
<uses-permission android:name="android.permission.INTERNET" />
<application>
...
...
</application>
...
</manifest>
Fatto ciò, il sistema operativo saprà quali attività particolari l'app dovrà svolgere, e potrà concederle il permesso di effettuarle. Prima dell'installazione dell'app, infatti, l'utente viene sempre avvisato delle permission richieste dall'app: se accetta di installarla, accetta che il suo sistema operativo conceda tali permessi "speciali" all'app.
Tipi di permission
Le permission Android vengono suddivise in tre famiglie in base al livello di protezione:
- permission normal, che non mettono cioè a rischio la privacy dell'utente, tra le quali troviamo:
- accesso alla Rete (
android.permission.INTERNET
, verifica dello stato della rete (android.permission.ACCESS_NETWORK_STATE
) e accesso alle informazioni sulle reti Wi-Fi (android.permission.ACCESS_WIFI_STATE
); - accesso a particolari tecnologie per la connettività:
android.permission.BLUETOOTH
,android.permission.BLUETOOTH_ADMIN
oandroid.permission.NFC
; - invio di un Intent per l'impostazione di un allarme:
com.android.alarm.permission.SET_ALARM
; - gestione della vibrazione (
android.permission.VIBRATE
) o del WaveLock per il controllo dello stand-by (android.permission.WAVE_LOCK
); - molte altre: una lista completa è disponibile sulla documentazione ufficiale.
- accesso alla Rete (
- permission dangerous, potenzialmente più lesive della riservatezza degli utenti, come quelle riguardanti:
- la localizzazione:
android.permission.ACCESS_FINE_LOCATION
eandroid.permission.ACCESS_COARSE_LOCATION
; - il calendario:
android.permission.READ_CALENDAR
eandroid.permission.WRITE_CALENDAR
; - i contatti:
android.permission.READ_CONTACTS
eandroid.permission.WRITE_CONTACTS
; - attività telefoniche: ad esempio,
android.permission.READ_PHONE_STATE
eandroid.permission.SEND_SMS
; - accesso ai file:
android.permission.READ_EXTERNAL_STORAGE
eandroid.permission.WRITE_EXTERNAL_STORAGE
.
- la localizzazione:
- permission signature, gestite a livello di installazione ma utilizzabili
solo se l'app che le richiede è firmata con lo stesso certificato di quella che
ha definito la permission.
Gestione delle permission
Le permission dangerous vanno accettate a runtime dall'utente al momento dell'utilizzo delle
funzionalità che le richiedono mentre per quelle normal è sufficiente dichiararne l'impiego
al momento dell'installazione dell'app. Tale distinzione è stata introdotta in Android 6 (API 23) ed
è ormai prassi cui l'utente è totalmente abituato. Inoltre, una permission dangerous già concessa
potrà essere revocata in qualsiasi istante.
Tutto ciò ha determinato un regime di gestione delle attività critiche più flessibile e più protettivo nei confronti dell'utente, ma che richiede agli sviluppatori una gestione più accurata delle eccezioni.
Ciò che cambia sul piano della progettazione è che l'app, in prospettiva, potrebbe avere più comportamenti: uno in cui tutte le permission dangerous sono state concesse dall'utente, altri in cui una o più di esse sono state negate o revocate. L'app, dal canto suo, dovrebbe essere in grado di lavorare correttamente in tutti i casi, semmai disattivando le funzionalità che necessitano delle permission negate. Per sviluppare app moderne, quindi, è necessario gestire le permission a runtime, verificando se esse siano revocate o meno, nonchè richiedendo all'utente di abilitarle.
La classe ContextCompat, inclusa nella libreria di supporto, mette a disposizione un metodo per effettuare questo tipo di controllo:
int statoPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
dove this
rappresenta la nostra Activity (che fungerà da Context). Il valore della variabile intera statoPermission
sarà confrontato con le costanti PackageManager.PERMISSION_GRANTED
oppure PackageManager.PERMISSION_DENIED
che equivalgono, rispettivamente, alla concessione del permesso o al suo diniego.
Per fare in modo che le nostre app funzionino su versioni Android con API 23 o superiori dovremo verificare la disponibilità di una permission prima di utilizzare il metodo che ne ha bisogno, e qualora essa non fosse disponibile dovremo chiedere all'utente di acconsentire lanciando un apposito Intent. Per far comparire una finestra di dialogo che richieda l'accettazione della permission, si può usare il seguente codice Java:
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, ID_RICHIESTA_PERMISSION);
Abbiamo così passato un array di stringhe contenente le permission che dovranno essere accettate, ed una costante intera che noi abbiamo chiamato ID_RICHIESTA_PERMISSION
, che permetterà di identificare la specifica richiesta che abbiamo inoltrato. Apparirà così una finestra di dialogo di questo tipo:
Il risultato sarà inviato inviato ad un metodo che noi predisporremo:
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case ID_RICHIESTA_PERMISSION: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission concessa: eseguiamo il codice
} else {
// permission negata: provvediamo in qualche maniera
}
return;
}
}
}
Il metodo riceverà tre elementi: il codice della richiesta (utile per riconoscere a cosa sta rispondendo), l'array delle permission e l'array dei risultati (che conterrà in ogni posizione se la permission è stata accettata o meno). In base al controllo di quest'ultimo capiremo il da farsi: possiamo eseguire il codice o dobbiamo ovviare facendo qualcos'altro.
Riepilogando, quella che segue è la struttura di controllo da usare per far sì che l'utente acconsenta a svolgere attività soggette ad una permission dangerous. Si noti che viene controllato anche il metodo shouldShowRequestPermissionRationale
, che verifica se sia il caso di fornire all'utente ulteriori informazioni rispetto alla permission richiesta e all'uso che l'app deve farne:
// controlliamo se la permission è concessa
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// se arriviamo qui è perchè la permission non è stata ancora concessa
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
// mostriamo ulteriori informazioni all'utente riguardante l'uso della permission nell'app ed eventualmente richiediamo la permission
} else {
// se siamo qui è perchè non si è mostrata alcuna spiegazione all'utente, richiesta di permission
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, ID_RICHIESTA_PERMISSION);
}
}
L'utente sarà sempre padrone di revocare le permission dangerous concesse, semplicemente agendo sul controllo che apparirà tra le impostazioni dell'app: