Android M: verifique el permiso de tiempo de ejecución: ¿cómo determinar si el usuario marcó "No volver a preguntar"?
De acuerdo con esto: http://developer.android.com/preview/features/runtime-permissions.html#coding una aplicación puede verificar los permisos de tiempo de ejecución y solicitar permisos si aún no se han otorgado. Entonces se mostrará el siguiente cuadro de diálogo:
En caso de que el usuario rechace un permiso importante, en mi opinión, una aplicación debería mostrar una explicación de por qué se necesita el permiso y qué impacto tiene rechazarlo. Ese diálogo tiene dos opciones:
- Vuelva a intentarlo (se solicita permiso nuevamente)
- negar (la aplicación funcionará sin ese permiso).
Sin embargo, si el usuario marca Never ask again
, no debería mostrarse el segundo cuadro de diálogo con la explicación, especialmente si el usuario ya lo rechazó una vez antes. Ahora la pregunta es: ¿cómo sabe mi aplicación si el usuario ha verificado Never ask again
? En mi opinión, onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
no me da esa información.
Una segunda pregunta sería: ¿Google tiene planes de incorporar un mensaje personalizado en el cuadro de diálogo de permiso que explique por qué la aplicación necesita el permiso? De esa manera, nunca habría un segundo diálogo que sin duda mejoraría la experiencia de usuario.
Developer Preview 2 trae algunos cambios en la forma en que la aplicación solicita los permisos (consulte también http://developer.android.com/preview/support.html#preview2-notes ).
El primer cuadro de diálogo ahora se ve así:
No hay una casilla de verificación "No volver a mostrar" (a diferencia de la vista previa para desarrolladores 1). Si el usuario niega el permiso y si el permiso es esencial para la aplicación, podría presentar otro cuadro de diálogo para explicar el motivo por el que la aplicación solicita ese permiso, por ejemplo, así:
Si el usuario vuelve a rechazarlo, la aplicación debería cerrarse si es absolutamente necesario ese permiso o seguir ejecutándose con una funcionalidad limitada. Si el usuario lo reconsidera (y selecciona volver a intentarlo), se solicita nuevamente el permiso. Esta vez el mensaje se ve así:
La segunda vez se muestra la casilla de verificación "No volver a preguntar". Si el usuario vuelve a negar y la casilla de verificación está marcada, no debería pasar nada más. Se puede determinar si la casilla de verificación está marcada o no usando Activity.shouldShowRequestPermissionRationale(String), por ejemplo, así:
if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {...
Eso es lo que dice la documentación de Android ( https://developer.android.com/training/permissions/requesting.html ):
Para ayudar a encontrar las situaciones en las que necesita proporcionar explicaciones adicionales, el sistema proporciona el método Activity.shouldShowRequestPermissionRationale(String). Este método devuelve verdadero si la aplicación solicitó este permiso anteriormente y el usuario rechazó la solicitud. Eso indica que probablemente deberías explicarle al usuario por qué necesitas el permiso.
Si el usuario rechazó la solicitud de permiso en el pasado y eligió la opción No volver a preguntar en el cuadro de diálogo del sistema de solicitud de permiso, este método devuelve falso. El método también devuelve falso si la política del dispositivo prohíbe que la aplicación tenga ese permiso.
Para saber si el usuario negó con "nunca volver a preguntar", puede verificar nuevamente el método shouldShowRequestPermissionRationale en su onRequestPermissionsResult cuando el usuario no otorgó el permiso.
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_PERMISSION) {
// for each permission check if the user granted/denied them
// you may want to group the rationale in a single dialog,
// this is just an example
for (int i = 0, len = permissions.length; i < len; i++) {
String permission = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
// user rejected the permission
boolean showRationale = shouldShowRequestPermissionRationale( permission );
if (! showRationale) {
// user also CHECKED "never ask again"
// you can either enable some fall back,
// disable features of your app
// or open another dialog explaining
// again the permission and directing to
// the app setting
} else if (Manifest.permission.WRITE_CONTACTS.equals(permission)) {
showRationale(permission, R.string.permission_denied_contacts);
// user did NOT check "never ask again"
// this is a good place to explain the user
// why you need the permission and ask if he wants
// to accept it (the rationale)
} else if ( /* possibly check more permissions...*/ ) {
}
}
}
}
}
Puede abrir la configuración de su aplicación con este código:
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, REQUEST_PERMISSION_SETTING);
No hay forma de enviar al usuario directamente a la página de Autorización.
Puedes consultar shouldShowRequestPermissionRationale()
en tu onRequestPermissionsResult()
.
https://youtu.be/C8lUdPVSzDk?t=2m23s
Compruebe si se concedió el permiso o no en onRequestPermissionsResult()
. Si no es así, compruébalo shouldShowRequestPermissionRationale()
.
- Si este método regresa
true
, muestre una explicación de por qué se necesita este permiso en particular. Luego, dependiendo de la elección del usuario nuevamenterequestPermissions()
. - Si regresa
false
, muestra un mensaje de error que indica que no se otorgó el permiso y que la aplicación no puede continuar o que una función en particular está deshabilitada.
A continuación se muestra un código de muestra.
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case STORAGE_PERMISSION_REQUEST:
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted :)
downloadFile();
} else {
// permission was not granted
if (getActivity() == null) {
return;
}
if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
showStoragePermissionRationale();
} else {
Snackbar snackbar = Snackbar.make(getView(), getResources().getString(R.string.message_no_storage_permission_snackbar), Snackbar.LENGTH_LONG);
snackbar.setAction(getResources().getString(R.string.settings), new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getActivity() == null) {
return;
}
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getActivity().getPackageName(), null);
intent.setData(uri);
OrderDetailFragment.this.startActivity(intent);
}
});
snackbar.show();
}
}
break;
}
}
Aparentemente, Google Maps hace exactamente esto para obtener permiso de ubicación.
Aquí hay un método fácil y agradable para verificar el estado actual del permiso:
@Retention(RetentionPolicy.SOURCE)
@IntDef({GRANTED, DENIED, BLOCKED_OR_NEVER_ASKED })
public @interface PermissionStatus {}
public static final int GRANTED = 0;
public static final int DENIED = 1;
public static final int BLOCKED_OR_NEVER_ASKED = 2;
@PermissionStatus
public static int getPermissionStatus(Activity activity, String androidPermissionName) {
if(ContextCompat.checkSelfPermission(activity, androidPermissionName) != PackageManager.PERMISSION_GRANTED) {
if(!ActivityCompat.shouldShowRequestPermissionRationale(activity, androidPermissionName)){
return BLOCKED_OR_NEVER_ASKED;
}
return DENIED;
}
return GRANTED;
}
Advertencia: devuelve BLOCKED_OR_NEVER_ASKED el primer inicio de la aplicación, antes de que el usuario aceptara/negara el permiso a través del mensaje del usuario (en dispositivos SDK 23+)
Actualizar:
La biblioteca de soporte de Android ahora también parece tener una clase muy similar android.support.v4.content.PermissionChecker
que contiene un checkSelfPermission()
archivo que devuelve:
public static final int PERMISSION_GRANTED = 0;
public static final int PERMISSION_DENIED = -1;
public static final int PERMISSION_DENIED_APP_OP = -2;
Una vez que el usuario haya marcado "No volver a preguntar", la pregunta no podrá volver a mostrarse. Pero se le puede explicar al usuario que previamente ha negado el permiso y debe otorgarlo en la configuración. Y referenciarlo a la configuración, con el siguiente código:
@Override
public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// now, you have permission go ahead
// TODO: something
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.READ_CALL_LOG)) {
// now, user has denied permission (but not permanently!)
} else {
// now, user has denied permission permanently!
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), "You have previously declined this permission.\n" +
"You must approve this permission in \"Permissions\" in the app settings on your device.", Snackbar.LENGTH_LONG).setAction("Settings", new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + BuildConfig.APPLICATION_ID)));
}
});
View snackbarView = snackbar.getView();
TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
textView.setMaxLines(5); //Or as much as you need
snackbar.show();
}
}
return;
}
Puede determinarlo verificando si la justificación del permiso se mostrará dentro del onRequestPermissionsResult()
método de devolución de llamada. Y si encuentra algún permiso configurado para no volver a solicitarlo nunca más , puede solicitar a los usuarios que otorguen permisos desde la configuración.
Mi implementación completa sería como a continuación. Funciona tanto para solicitudes de permisos únicos como múltiples . Utilice lo siguiente o utilice directamente mi biblioteca.
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if(permissions.length == 0){
return;
}
boolean allPermissionsGranted = true;
if(grantResults.length>0){
for(int grantResult: grantResults){
if(grantResult != PackageManager.PERMISSION_GRANTED){
allPermissionsGranted = false;
break;
}
}
}
if(!allPermissionsGranted){
boolean somePermissionsForeverDenied = false;
for(String permission: permissions){
if(ActivityCompat.shouldShowRequestPermissionRationale(this, permission)){
//denied
Log.e("denied", permission);
}else{
if(ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED){
//allowed
Log.e("allowed", permission);
} else{
//set to never ask again
Log.e("set to never ask again", permission);
somePermissionsForeverDenied = true;
}
}
}
if(somePermissionsForeverDenied){
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setTitle("Permissions Required")
.setMessage("You have forcefully denied some of the required permissions " +
"for this action. Please open settings, go to permissions and allow them.")
.setPositiveButton("Settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", getPackageName(), null));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setCancelable(false)
.create()
.show();
}
} else {
switch (requestCode) {
//act according to the request code used while requesting the permission(s).
}
}
}