studio - Android M: verifique el permiso de tiempo de ejecución: ¿cómo determinar si el usuario marcó "Nunca preguntar de nuevo"?
request permission android (24)
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 ha otorgado. Entonces se mostrará el siguiente cuadro de diálogo:
En caso de que el usuario rechace un permiso importante, una aplicación debería mostrar una explicación de por qué se necesita el permiso y qué impacto tiene la disminución. Ese diálogo tiene dos opciones:
- vuelva a intentarlo nuevamente (se solicita permiso nuevamente)
- negar (la aplicación funcionará sin ese permiso).
Sin embargo, si el usuario marca
Never ask again
, el segundo cuadro de diálogo con la explicación no debería mostrarse, especialmente si el usuario ya lo rechazó una vez.
Ahora la pregunta es: ¿cómo sabe mi aplicación si el usuario ha verificado
Never ask again
?
OMI el
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 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.
Ampliando la respuesta anterior de mVck , la siguiente lógica determina si "Nunca preguntar de nuevo" se ha verificado para una solicitud de permiso dada:
bool bStorage = grantResults[0] == Permission.Granted;
bool bNeverAskForStorage =
!bStorage && (
_bStorageRationaleBefore == true && _bStorageRationaleAfter == false ||
_bStorageRationaleBefore == false && _bStorageRationaleAfter == false
);
que se extrae de abajo (para ver el ejemplo completo, vea esta answer )
private bool _bStorageRationaleBefore;
private bool _bStorageRationaleAfter;
private const int ANDROID_PERMISSION_REQUEST_CODE__SDCARD = 2;
//private const int ANDROID_PERMISSION_REQUEST_CODE__CAMERA = 1;
private const int ANDROID_PERMISSION_REQUEST_CODE__NONE = 0;
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode)
{
case ANDROID_PERMISSION_REQUEST_CODE__SDCARD:
_bStorageRationaleAfter = ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage);
bool bStorage = grantResults[0] == Permission.Granted;
bool bNeverAskForStorage =
!bStorage && (
_bStorageRationaleBefore == true && _bStorageRationaleAfter == false ||
_bStorageRationaleBefore == false && _bStorageRationaleAfter == false
);
break;
}
}
private List<string> GetRequiredPermissions(out int requestCode)
{
// Android v6 requires explicit permission granting from user at runtime for security reasons
requestCode = ANDROID_PERMISSION_REQUEST_CODE__NONE; // 0
List<string> requiredPermissions = new List<string>();
_bStorageRationaleBefore = ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage);
Permission writeExternalStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);
//if(extStoragePerm == Permission.Denied)
if (writeExternalStoragePerm != Permission.Granted)
{
requestCode |= ANDROID_PERMISSION_REQUEST_CODE__SDCARD;
requiredPermissions.Add(Android.Manifest.Permission.WriteExternalStorage);
}
return requiredPermissions;
}
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Android v6 requires explicit permission granting from user at runtime for security reasons
int requestCode;
List<string> requiredPermissions = GetRequiredPermissions(out requestCode);
if (requiredPermissions != null && requiredPermissions.Count > 0)
{
if (requestCode >= ANDROID_PERMISSION_REQUEST_CODE__SDCARD)
{
_savedInstanceState = savedInstanceState;
RequestPermissions(requiredPermissions.ToArray(), requestCode);
return;
}
}
}
OnCreate2(savedInstanceState);
}
En su lugar, recibirá una devolución de llamada
onRequestPermissionsResult()
como PERMISSION_DENIED cuando solicite permiso nuevamente mientras cae en condición falsa de
shouldShowRequestPermissionRationale()
Desde el documento de Android:
Cuando el sistema le pide al usuario que otorgue un permiso, el usuario tiene la opción de decirle al sistema que no solicite ese permiso nuevamente.
En ese caso, cada vez que una aplicación utiliza
requestPermissions()
para solicitar ese permiso nuevamente, el sistema inmediatamente rechaza la solicitud.
El sistema llama a su
onRequestPermissionsResult()
método de devolución de llamada y pasa
PERMISSION_DENIED
, de la misma manera que lo haría si el usuario hubiera rechazado explícitamente su solicitud nuevamente.
Esto significa que cuando llama
requestPermissions()
, no puede suponer que ha tenido lugar una interacción directa con el usuario.
Para responder la pregunta con precisión, ¿qué sucede cuando el usuario presiona "Nunca preguntar de nuevo"?
El método / función anulada
onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)
La matriz grantResult resulta estar vacía, ¿entonces puede hacer algo allí? Pero no es la mejor práctica.
¿Cómo manejar "Nunca preguntar de nuevo"?
Estoy trabajando con Fragment, que requería el permiso READ_EXTERNAL_STORAGE.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
when {
isReadPermissionsGranted() -> {
/**
* Permissions has been Granted
*/
getDirectories()
}
isPermissionDeniedBefore() -> {
/**
* User has denied before, explain why we need the permission and ask again
*/
updateUIForDeniedPermissions()
checkIfPermissionIsGrantedNow()
}
else -> {
/**
* Need to ask For Permissions, First Time
*/
checkIfPermissionIsGrantedNow()
/**
* If user selects, "Dont Ask Again" it will never ask again! so just update the UI for Denied Permissions
*/
updateUIForDeniedPermissions()
}
}
}
Las otras funciones son triviales.
// Is Read Write Permissions Granted
fun isReadWritePermissionGranted(context: Context): Boolean {
return (ContextCompat.checkSelfPermission(
context as Activity,
Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED) and
(ContextCompat.checkSelfPermission(
context,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED)
}
fun isReadPermissionDenied(context: Context) : Boolean {
return ActivityCompat.shouldShowRequestPermissionRationale(
context as Activity,
PermissionsUtils.READ_EXTERNAL_STORAGE_PERMISSIONS)
}
Puede usar el
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)
método para detectar si nunca preguntar está marcado o no.
Para más referencia: verifique esto
Para verificar los permisos múltiples, use:
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)
|| ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|| ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)
|| ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
showDialogOK("Service Permissions are required for this app",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
checkAndRequestPermissions();
break;
case DialogInterface.BUTTON_NEGATIVE:
// proceed with logic by disabling the related features or quit the app.
finish();
break;
}
}
});
}
//permission is denied (and never ask again is checked)
//shouldShowRequestPermissionRationale will return false
else {
explain("You need to give some mandatory permissions to continue. Do you want to go to app settings?");
// //proceed with logic by disabling the related features or quit the app.
}
método de explicar ()
private void explain(String msg){
final android.support.v7.app.AlertDialog.Builder dialog = new android.support.v7.app.AlertDialog.Builder(this);
dialog.setMessage(msg)
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface paramDialogInterface, int paramInt) {
// permissionsclass.requestPermission(type,code);
startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:com.exampledemo.parsaniahardik.marshmallowpermission")));
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface paramDialogInterface, int paramInt) {
finish();
}
});
dialog.show();
}
El código anterior también mostrará un cuadro de diálogo, que redirigirá al usuario a la pantalla de configuración de la aplicación desde donde puede otorgar permiso si ha marcado el botón Nunca preguntar de nuevo.
Puedes escuchar bonita.
Oyente
interface PermissionListener {
fun onNeedPermission()
fun onPermissionPreviouslyDenied(numberDenyPermission: Int)
fun onPermissionDisabledPermanently(numberDenyPermission: Int)
fun onPermissionGranted()
}
MainClass para permiso
class PermissionUtil {
private val PREFS_FILENAME = "permission"
private val TAG = "PermissionUtil"
private fun shouldAskPermission(context: Context, permission: String): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val permissionResult = ActivityCompat.checkSelfPermission(context, permission)
if (permissionResult != PackageManager.PERMISSION_GRANTED) {
return true
}
}
return false
}
fun checkPermission(context: Context, permission: String, listener: PermissionListener) {
Log.i(TAG, "CheckPermission for $permission")
if (shouldAskPermission(context, permission)) {
// Load history permission
val sharedPreference = context.getSharedPreferences(PREFS_FILENAME, 0)
val numberShowPermissionDialog = sharedPreference.getInt(permission, 0)
if (numberShowPermissionDialog == 0) {
(context as? Activity)?.let {
if (ActivityCompat.shouldShowRequestPermissionRationale(it, permission)) {
Log.e(TAG, "User has denied permission but not permanently")
listener.onPermissionPreviouslyDenied(numberShowPermissionDialog)
} else {
Log.e(TAG, "Permission denied permanently.")
listener.onPermissionDisabledPermanently(numberShowPermissionDialog)
}
} ?: kotlin.run {
listener.onNeedPermission()
}
} else {
// Is FirstTime
listener.onNeedPermission()
}
// Save history permission
sharedPreference.edit().putInt(permission, numberShowPermissionDialog + 1).apply()
} else {
listener.onPermissionGranted()
}
}
}
Usado de esta manera
PermissionUtil().checkPermission(this, Manifest.permission.ACCESS_FINE_LOCATION,
object : PermissionListener {
override fun onNeedPermission() {
log("---------------------->onNeedPermission")
// ActivityCompat.requestPermissions(this@SplashActivity,
// Array(1) { Manifest.permission.ACCESS_FINE_LOCATION },
// 118)
}
override fun onPermissionPreviouslyDenied(numberDenyPermission: Int) {
log("---------------------->onPermissionPreviouslyDenied")
}
override fun onPermissionDisabledPermanently(numberDenyPermission: Int) {
log("---------------------->onPermissionDisabled")
}
override fun onPermissionGranted() {
log("---------------------->onPermissionGranted")
}
})
anular onRequestPermissionsResult en actividad o fragmnet
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == 118) {
if (permissions[0] == Manifest.permission.ACCESS_FINE_LOCATION && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getLastLocationInMap()
}
}
}
Puedes usar
shouldShowRequestPermissionRationale()
dentro
onRequestPermissionsResult()
Vea el siguiente ejemplo:
Compruebe si tiene permiso cuando el usuario hace clic en el botón:
@Override
public void onClick(View v) {
if (v.getId() == R.id.appCompatBtn_changeProfileCoverPhoto) {
if (Build.VERSION.SDK_INT < 23) { // API < 23 don''t need to ask permission
navigateTo(MainActivity.class); // Navigate to activity to change photos
} else {
if (ContextCompat.checkSelfPermission(SettingsActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted yet. Ask for permission...
requestWriteExternalPermission();
} else {
// Permission is already granted, good to go :)
navigateTo(MainActivity.class);
}
}
}
}
Cuando el usuario responda al cuadro de diálogo de permiso, iremos a onRequestPermissionResult:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == WRITE_EXTERNAL_PERMISSION_REQUEST_CODE) {
// Case 1. Permission is granted.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (ContextCompat.checkSelfPermission(SettingsActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
// Before navigating, I still check one more time the permission for good practice.
navigateTo(MainActivity.class);
}
} else { // Case 2. Permission was refused
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Case 2.1. shouldShowRequest... returns true because the
// permission was denied before. If it is the first time the app is running we will
// end up in this part of the code. Because he need to deny at least once to get
// to onRequestPermissionsResult.
Snackbar snackbar = Snackbar.make(findViewById(R.id.relLayout_container), R.string.you_must_verify_permissions_to_send_media, Snackbar.LENGTH_LONG);
snackbar.setAction("VERIFY", new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCompat.requestPermissions(SettingsActivity.this
, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}
, WRITE_EXTERNAL_PERMISSION_REQUEST_CODE);
}
});
snackbar.show();
} else {
// Case 2.2. Permission was already denied and the user checked "Never ask again".
// Navigate user to settings if he choose to allow this time.
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.instructions_to_turn_on_storage_permission)
.setPositiveButton(getString(R.string.settings), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent settingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
settingsIntent.setData(uri);
startActivityForResult(settingsIntent, 7);
}
})
.setNegativeButton(getString(R.string.not_now), null);
Dialog dialog = builder.create();
dialog.show();
}
}
}
}
También me gustaría obtener la información de si el usuario ha seleccionado "nunca preguntar de nuevo". He logrado una ''casi solución'' con una bandera de aspecto feo, pero antes de decirte cómo, te contaré sobre mi motivación:
Me gustaría ofrecer la funcionalidad de referencia de permisos inicialmente. Si el usuario lo usa y no tiene derechos, obtiene el primer diálogo desde arriba o el segundo y el tercero. Cuando el usuario ha elegido ''Nunca preguntar de nuevo'', me gustaría deshabilitar la funcionalidad y mostrarla de manera diferente. - Mi acción se desencadena por una entrada de texto giratoria, también me gustaría agregar ''(Permiso revocado)'' al texto de la etiqueta que se muestra. Esto le muestra al usuario: ''Hay funcionalidad pero no puedo usarla, debido a mi configuración de permisos''. Sin embargo, esto no parece ser posible, ya que no puedo verificar si se ha elegido ''Nunca preguntar de nuevo''.
Llegué a una solución con la que puedo vivir teniendo mi funcionalidad siempre habilitada con una verificación de permiso activa. Estoy mostrando un mensaje Toast en onRequestPermissionsResult () en caso de una respuesta negativa, pero solo si no he mostrado mi ventana emergente de justificación personalizada. Entonces, si el usuario ha elegido ''Nunca preguntar de nuevo'', solo recibe un mensaje de brindis. Si el usuario es reacio a elegir "nunca preguntar de nuevo", solo obtiene la justificación personalizada y la ventana emergente de solicitud de permiso por parte del sistema operativo, pero no brinda, ya que tres notificaciones seguidas serían demasiado dolorosas.
Tengo que implementar permisos dinámicos para la cámara. Donde ocurren 3 casos posibles: 1. Permitir, 2. Denegado, 3. No volver a preguntar.
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
for (String permission : permissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), permission)) {
//denied
Log.e("denied", permission);
} else {
if (ActivityCompat.checkSelfPermission(getActivity(), permission) == PackageManager.PERMISSION_GRANTED) {
//allowed
Log.e("allowed", permission);
} else {
//set to never ask again
Log.e("set to never ask again", permission);
//do something here.
}
}
}
if (requestCode != MaterialBarcodeScanner.RC_HANDLE_CAMERA_PERM) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
return;
}
if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mScannerView.setResultHandler(this);
mScannerView.startCamera(mCameraId);
mScannerView.setFlash(mFlash);
mScannerView.setAutoFocus(mAutoFocus);
return;
} else {
//set to never ask again
Log.e("set to never ask again", permissions[0]);
}
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Error")
.setMessage(R.string.no_camera_permission)
.setPositiveButton(android.R.string.ok, listener)
.show();
}
private void insertDummyContactWrapper() {
int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.CAMERA);
if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.CAMERA},
REQUEST_CODE_ASK_PERMISSIONS);
return;
}
mScannerView.setResultHandler(this);
mScannerView.startCamera(mCameraId);
mScannerView.setFlash(mFlash);
mScannerView.setAutoFocus(mAutoFocus);
}
private int checkSelfPermission(String camera) {
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
return REQUEST_CODE_ASK_PERMISSIONS;
} else {
return REQUEST_NOT_CODE_ASK_PERMISSIONS;
}
}
puedes leer el documento oficial de Android Solicitar permisos de aplicación
o puede encontrar muchas bibliotecas populares de permisos de Android en Github
Aquí hay un método agradable y fácil para verificar el estado actual de los permisos:
@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 haya aceptado / denegado el permiso a través de la solicitud 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()
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;
Developer Preview 2 trae algunos cambios en la forma en que la aplicación solicita los permisos (consulte también developer.android.com/preview/support.html#preview2-notes ).
El primer diálogo ahora se ve así:
No hay una casilla de verificación "Nunca volver a mostrar" (a diferencia de la vista previa del desarrollador 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 la razón por la cual la aplicación solicita ese permiso, por ejemplo:
Si el usuario rechaza nuevamente, la aplicación debería cerrarse si necesita absolutamente ese permiso o seguir ejecutándose con una funcionalidad limitada. Si el usuario reconsidera (y selecciona volver a intentarlo), se solicita nuevamente el permiso. Esta vez el aviso se ve así:
La segunda vez se muestra la casilla de verificación "Nunca preguntar de nuevo". Si el usuario niega nuevamente y la casilla de verificación está marcada, no debería suceder nada más. Se puede determinar si la casilla de verificación está marcada o no mediante 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 una explicación adicional, el sistema proporciona el método Activity.shouldShowRequestPermissionRationale (String). Este método devuelve verdadero si la aplicación ha solicitado este permiso anteriormente y el usuario rechazó la solicitud. Eso indica que probablemente debería explicarle al usuario por qué necesita 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 preguntar de nuevo", 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.
El método shouldShowRequestPermissionRationale() puede ser un usuario para verificar si el usuario seleccionó la opción ''nunca más preguntó'' y denegó el permiso. Hay muchos ejemplos de código, por lo que preferiría explicar cómo usarlo para tal propósito, porque creo que su nombre y su implementación hacen que esto sea más complicado de lo que realmente es.
Como se explica en https://developer.android.com/training/permissions/requesting.html , ese método devuelve verdadero si la opción ''nunca preguntar de nuevo'' es visible, falso de lo contrario; por lo tanto, devuelve falso la primera vez que se muestra un cuadro de diálogo, luego, a partir de la segunda vez, devuelve verdadero, y solo si el usuario niega el permiso para seleccionar la opción, en ese momento vuelve a ser falso.
Para detectar este caso, puede detectar la secuencia falso-verdadero-falso, o (más simple) puede tener un indicador que haga un seguimiento de la hora inicial en que se muestra el diálogo. Después de eso, ese método devuelve verdadero o falso, donde el falso le permitirá detectar cuándo se selecciona la opción.
Escribí una taquigrafía para solicitar permiso en Android M. Este código también maneja la compatibilidad con versiones anteriores de Android.
Todo el código feo se extrae en un Fragmento que se adjunta y se separa de la Actividad que solicita los permisos. Puede usar
PermissionRequestManager
siguiente manera:
new PermissionRequestManager()
// We need a AppCompatActivity here, if you are not using support libraries you will have to slightly change
// the PermissionReuqestManager class
.withActivity(this)
// List all permissions you need
.withPermissions(android.Manifest.permission.CALL_PHONE, android.Manifest.permission.READ_CALENDAR)
// This Runnable is called whenever the request was successfull
.withSuccessHandler(new Runnable() {
@Override
public void run() {
// Do something with your permissions!
// This is called after the user has granted all
// permissions, we are one a older platform where
// the user does not need to grant permissions
// manually, or all permissions are already granted
}
})
// Optional, called when the user did not grant all permissions
.withFailureHandler(new Runnable() {
@Override
public void run() {
// This is called if the user has rejected one or all of the requested permissions
L.e(this.getClass().getSimpleName(), "Unable to request permission");
}
})
// After calling this, the user is prompted to grant the rights
.request();
Echa un vistazo: https://gist.github.com/crysxd/385b57d74045a8bd67c4110c34ab74aa
Por favor no me arrojes piedras por esta solución.
Esto funciona pero es un poco "hacky".
Cuando llame a
requestPermissions
, registre la hora actual.
mAskedPermissionTime = System.currentTimeMillis();
Luego en
onRequestPermissionsResult
Si no se otorga el resultado, verifique la hora nuevamente.
if (System.currentTimeMillis() - mAskedPermissionTime < 100)
Como el usuario no puede hacer clic tan rápido en el botón denegar, sabemos que seleccionó "nunca preguntar de nuevo" porque la devolución de llamada es instantánea.
Úselo bajo su propio riesgo.
Prueba esta sencilla biblioteca de permisos. Manejará todas las operaciones relacionadas con el permiso en 3 sencillos pasos. Me salvó el tiempo. Puede finalizar todo el trabajo relacionado con los permisos en 15 minutos .
Puede manejar Denegar, Puede manejar Nunca preguntar de nuevo, Puede llamar a la configuración de la aplicación para obtener permiso, Puede dar un mensaje Racional, Puede dar un mensaje de Denegación, Puede dar una lista de permisos aceptados, Puede dar una lista de denegados permisos y etc.
https://github.com/ParkSangGwon/TedPermission
Paso 1: agrega tu dependencia
dependencies {
compile ''gun0912.ted:tedpermission:2.1.1''
//check the above link for latest libraries
}
Paso 2: pide permisos
TedPermission.with(this)
.setPermissionListener(permissionlistener)
.setDeniedMessage("If you reject permission,you can not use this service/n/nPlease turn on permissions at [Setting] > [Permission]")
.setPermissions(Manifest.permission.READ_CONTACTS, Manifest.permission.ACCESS_FINE_LOCATION)
.check();
Paso 3: manejar la respuesta de permiso
PermissionListener permissionlistener = new PermissionListener() {
@Override
public void onPermissionGranted() {
Toast.makeText(MainActivity.this, "Permission Granted", Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionDenied(ArrayList<String> deniedPermissions) {
Toast.makeText(MainActivity.this, "Permission Denied/n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
}
};
Puede
determinarlo
comprobando si la
justificación del
permiso se mostrará dentro del método de devolución de llamada
onRequestPermissionsResult()
.
Y si encuentra algún permiso configurado para
no volver a preguntar 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 para solicitudes de permisos simples o múltiples . Use lo siguiente o use 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).
}
}
}
Puede ser útil para alguien: -
Lo que he notado es que si marcamos el indicador shouldShowRequestPermissionRationale () en el método de devolución de llamada onRequestPermissionsResult (), muestra solo dos estados .
Estado 1: -Return true: - Cada vez que el usuario hace clic en Denegar permisos (incluida la primera vez).
Estado 2: - Devuelve falso: - si el usuario selecciona "nunca pregunta de nuevo".
Enlace del ejemplo de trabajo detallado
Puede verificar
shouldShowRequestPermissionRationale()
en su
onRequestPermissionsResult()
.
https://youtu.be/C8lUdPVSzDk?t=2m23s
Compruebe si se otorgó permiso o no en
onRequestPermissionsResult()
.
De lo
contrario
, marque
shouldShowRequestPermissionRationale()
.
-
Si este método devuelve
true
, muestre una explicación de por qué se necesita este permiso en particular. Luego, dependiendo de la elección del usuario nuevamenterequestPermissions()
. -
Si devuelve
false
, muestre un mensaje de error que indique que no se otorgó el permiso y que la aplicación no puede continuar o que una función en particular está desactivada.
A continuación se muestra el 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.
Si desea detectar todos los "estados" (primera vez denegado, solo denegado, solo denegado con "Nunca preguntar de nuevo" o denegado permanentemente), puede hacer lo siguiente:
Crea 2 booleanos
private boolean beforeClickPermissionRat;
private boolean afterClickPermissionRat;
Establezca el primero antes de pedir permiso:
beforeClickPermissionRat = shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE);
Establezca el segundo dentro de su método onRequestPermissionsResult:
afterClickPermissionRat = shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE);
Use la siguiente "tabla" para hacer lo que necesite en onRequestPermissionsResult () (después de verificar que todavía no tiene el permiso):
// before after
// FALSE FALSE = Was denied permanently, still denied permanently --> App Settings
// FALSE TRUE = First time deny, not denied permanently yet --> Nothing
// TRUE FALSE = Just been permanently denied --> Changing my caption to "Go to app settings to edit permissions"
// TRUE TRUE = Wasn''t denied permanently, still not denied permanently --> Nothing
Tuve el mismo problema y lo resolví. Para simplificar la vida, escribí una clase util para manejar los permisos de tiempo de ejecución.
public class PermissionUtil {
/*
* Check if version is marshmallow and above.
* Used in deciding to ask runtime permission
* */
public static boolean shouldAskPermission() {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
}
private static boolean shouldAskPermission(Context context, String permission){
if (shouldAskPermission()) {
int permissionResult = ActivityCompat.checkSelfPermission(context, permission);
if (permissionResult != PackageManager.PERMISSION_GRANTED) {
return true;
}
}
return false;
}
public static void checkPermission(Context context, String permission, PermissionAskListener listener){
/*
* If permission is not granted
* */
if (shouldAskPermission(context, permission)){
/*
* If permission denied previously
* */
if (((Activity)context).shouldShowRequestPermissionRationale(permission)) {
listener.onPermissionPreviouslyDenied();
} else {
/*
* Permission denied or first time requested
* */
if (PreferencesUtil.isFirstTimeAskingPermission(context, permission)) {
PreferencesUtil.firstTimeAskingPermission(context, permission, false);
listener.onPermissionAsk();
} else {
/*
* Handle the feature without permission or ask user to manually allow permission
* */
listener.onPermissionDisabled();
}
}
} else {
listener.onPermissionGranted();
}
}
/*
* Callback on various cases on checking permission
*
* 1. Below M, runtime permission not needed. In that case onPermissionGranted() would be called.
* If permission is already granted, onPermissionGranted() would be called.
*
* 2. Above M, if the permission is being asked first time onPermissionAsk() would be called.
*
* 3. Above M, if the permission is previously asked but not granted, onPermissionPreviouslyDenied()
* would be called.
*
* 4. Above M, if the permission is disabled by device policy or the user checked "Never ask again"
* check box on previous request permission, onPermissionDisabled() would be called.
* */
public interface PermissionAskListener {
/*
* Callback to ask permission
* */
void onPermissionAsk();
/*
* Callback on permission denied
* */
void onPermissionPreviouslyDenied();
/*
* Callback on permission "Never show again" checked and denied
* */
void onPermissionDisabled();
/*
* Callback on permission granted
* */
void onPermissionGranted();
}
}
Y los métodos PreferenceUtil son los siguientes.
public static void firstTimeAskingPermission(Context context, String permission, boolean isFirstTime){
SharedPreferences sharedPreference = context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE;
sharedPreference.edit().putBoolean(permission, isFirstTime).apply();
}
public static boolean isFirstTimeAskingPermission(Context context, String permission){
return context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE).getBoolean(permission, true);
}
Ahora, todo lo que necesita es usar el método * checkPermission * con los argumentos adecuados.
Aquí hay un ejemplo,
PermissionUtil.checkPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE,
new PermissionUtil.PermissionAskListener() {
@Override
public void onPermissionAsk() {
ActivityCompat.requestPermissions(
thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_EXTERNAL_STORAGE
);
}
@Override
public void onPermissionPreviouslyDenied() {
//show a dialog explaining permission and then request permission
}
@Override
public void onPermissionDisabled() {
Toast.makeText(context, "Permission Disabled.", Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionGranted() {
readContacts();
}
});
¿Cómo sabe mi aplicación si el usuario ha marcado "Nunca preguntar de nuevo"?
Si el usuario marcó No preguntar nunca más , recibirá una devolución de llamada en onPermissionDisabled .
Feliz codificación :)
Una función útil para determinar si se ha bloqueado la solicitud de un permiso arbitrario (en Kotlin):
private fun isPermissionBlockedFromAsking(activity: Activity, permission: String): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED
&& !activity.shouldShowRequestPermissionRationale(permission)
&& PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(permission, false)
}
return false
}
El uso de esto requiere establecer un booleano de preferencia compartida con el nombre de su permiso deseado (por ejemplo
android.Manifest.permission.READ_PHONE_STATE
)
true
cuando solicite un permiso por primera vez.
Explicación:
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
ya que parte del código solo se puede ejecutar en el nivel de API 23+.
ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED
para verificar que ya no tenemos el permiso.
!activity.shouldShowRequestPermissionRationale(permission)
para verificar si el usuario ha denegado la aplicación preguntando nuevamente.
Debido a las
peculiaridades de esta función
, también se requiere la siguiente línea.
PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(permission, false)
esto se usa (junto con establecer el valor en verdadero en la primera solicitud de permiso) para distinguir entre los estados "Nunca preguntado" y "Nunca preguntar de nuevo", ya que la línea anterior no devuelve esta información.
Una vez que el usuario ha marcado "No volver a preguntar", la pregunta no se puede volver a mostrar. Pero se le puede explicar al usuario que previamente ha denegado el permiso y debe otorgarlo en la configuración. Y haga referencia a él 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;
}
Explicación completa de cada caso de permiso.
/**
* Case 1: User doesn''t have permission
* Case 2: User has permission
*
* Case 3: User has never seen the permission Dialog
* Case 4: User has denied permission once but he din''t clicked on "Never Show again" check box
* Case 5: User denied the permission and also clicked on the "Never Show again" check box.
* Case 6: User has allowed the permission
*
*/
public void handlePermission() {
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// This is Case 1. Now we need to check further if permission was shown before or not
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// This is Case 4.
} else {
// This is Case 3. Request for permission here
}
} else {
// This is Case 2. You have permission now you can do anything related to it
}
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// This is Case 2 (Permission is now granted)
} else {
// This is Case 1 again as Permission is not granted by user
//Now further we check if used denied permanently or not
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// case 4 User has denied permission but not permanently
} else {
// case 5. Permission denied permanently.
// You can open Permission setting''s page from here now.
}
}
}
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
switch (requestCode) {
case PERMISSIONS_REQUEST_EXTERNAL_STORAGE: {
if (grantResults.length > 0) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Denied
} else {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
// To what you want
} else {
// Bob never checked click
}
}
}
}
}
}