¿Cómo distinguimos los nunca solicitados de Stop-Asking en los permisos de tiempo de ejecución de Android M?

Cuando se trata de los permisos de tiempo de ejecución M Developer Preview, según Google :

  1. Si nunca antes has pedido un permiso determinado, solo pídelo

  2. Si lo hizo antes, y el usuario dijo “no”, y el usuario intenta hacer algo que necesita el permiso rechazado, debe solicitar al usuario que explique por qué necesita el permiso, antes de solicitar nuevamente el permiso.

  3. Si lo ha preguntado un par de veces antes, y el usuario ha dicho “no, y deje de preguntar” (a través de la casilla de diálogo de permiso de ejecución), debe dejar de molestar (por ejemplo, deshabilitar la IU que requiere el permiso)

Sin embargo, solo tenemos un método, shouldShowRequestPermissionRationale() , que devuelve un boolean , y tenemos tres estados. Necesitamos una manera de distinguir el estado nunca solicitado del estado de petición de parada, ya que obtenemos false de shouldShowRequestPermissionRationale() para ambos.

Para los permisos que se solicitan en la primera ejecución de la aplicación, este no es un gran problema. Hay muchas recetas para determinar que esta es probablemente la primera ejecución de su aplicación (por ejemplo, valor boolean en SharedPreferences ), por lo que supone que si es la primera ejecución de su aplicación, se encontrará en el estado nunca solicitado.

Sin embargo, parte de la visión de los permisos de tiempo de ejecución es que puede que no los solicite por adelantado. Permisos vinculados a funciones adicionales que quizás solo solicite más adelante, cuando el usuario toque algo que requiera ese permiso. Aquí, la aplicación puede haberse ejecutado muchas veces, durante meses, antes de que de repente tengamos que solicitar otro permiso.

En esos casos, ¿se supone que debemos rastrear si solicitamos o no el permiso nosotros mismos? ¿O hay algo en la API de Android M que me falta que nos dice si lo preguntamos antes o no?

Según el ejemplo actual: https://github.com/googlesamples/android-RuntimePermissions/blob/master/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java#L195

 @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == REQUEST_CAMERA) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { doThing(); //STORE FALSE IN SHAREDPREFERENCES } else { //STORE TRUE IN SHAREDPREFERENCES } } 

Almacene un booleano en SharedPreferences con la clave como su código de permiso y valor como se indicó anteriormente, para indicar si esa preferencia se ha denegado antes.

Tristemente, es probable que no pueda comparar con una preferencia que ha sido aceptada y luego negada mientras su aplicación se está ejecutando. La especificación final no está disponible, pero existe la posibilidad de que su aplicación se reinicie o adquiera valores simulados hasta el próximo lanzamiento.

Sé que estoy publicando muy tarde, pero un ejemplo detallado puede ser útil para alguien.

Lo que he notado es que si comprobamos el indicador shouldShowRequestPermissionRationale () en el método de callback onRequestPermissionsResult (), solo se muestran dos estados.

Estado 1: -Return true: – Siempre que el usuario haga clic en Denegar permisos (incluida la primera vez).

Estado 2: -Returna falso: – si el usuario selecciona s “nunca vuelve a preguntar”.

Aquí hay un ejemplo con solicitud de permiso múltiple:

La aplicación necesita 2 permisos al inicio. SEND_SMS y ACCESS_FINE_LOCATION (ambos se mencionan en manifest.xml).

Tan pronto como la aplicación se inicia, solicita múltiples permisos juntos. Si se otorgan ambos permisos, se aplica el flujo normal.

enter image description here

 public static final int REQUEST_ID_MULTIPLE_PERMISSIONS = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(checkAndRequestPermissions()) { // carry on the normal flow, as the case of permissions granted. } } private boolean checkAndRequestPermissions() { int permissionSendMessage = ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS); int locationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION); List listPermissionsNeeded = new ArrayList<>(); if (locationPermission != PackageManager.PERMISSION_GRANTED) { listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION); } if (permissionSendMessage != PackageManager.PERMISSION_GRANTED) { listPermissionsNeeded.add(Manifest.permission.SEND_SMS); } if (!listPermissionsNeeded.isEmpty()) { ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]),REQUEST_ID_MULTIPLE_PERMISSIONS); return false; } return true; } 

En caso de que uno o más permisos no sean otorgados, activityCompat.requestPermissions () solicitará permisos y el control irá al método de callback onRequestPermissionsResult ().

Debería verificar el valor del indicador shouldShowRequestPermissionRationale () en el método de callback onRequestPermissionsResult ().

Solo hay dos casos:

Caso 1: “ Cada vez que un usuario hace clic en Denegar permisos (incluida la primera vez), se devolverá verdadero. Entonces, cuando el usuario niega, podemos mostrarle más explicaciones y seguir preguntando.

Caso 2: -Solo si el usuario selecciona “nunca vuelve a preguntar”, devolverá falso. En este caso, podemos continuar con una funcionalidad limitada y guiar al usuario para activar los permisos de configuración para obtener más funcionalidades, o podemos finalizar la configuración, si los permisos son triviales para la aplicación.

CASO 1

Caso 1

CASO- 2

Caso - 2

 @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { Log.d(TAG, "Permission callback called-------"); switch (requestCode) { case REQUEST_ID_MULTIPLE_PERMISSIONS: { Map perms = new HashMap<>(); // Initialize the map with both permissions perms.put(Manifest.permission.SEND_SMS, PackageManager.PERMISSION_GRANTED); perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED); // Fill with actual results from user if (grantResults.length > 0) { for (int i = 0; i < permissions.length; i++) perms.put(permissions[i], grantResults[i]); // Check for both permissions if (perms.get(Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED && perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "sms & location services permission granted"); // process the normal flow //else any one or both the permissions are not granted } else { Log.d(TAG, "Some permissions are not granted ask again "); //permission is denied (this is the first time, when "never ask again" is not checked) so ask again explaining the usage of permission // // shouldShowRequestPermissionRationale will return true //show the dialog or snackbar saying its necessary and try again otherwise proceed with setup. if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.SEND_SMS) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) { showDialogOK("SMS and Location Services Permission 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. break; } } }); } //permission is denied (and never ask again is checked) //shouldShowRequestPermissionRationale will return false else { Toast.makeText(this, "Go to settings and enable permissions", Toast.LENGTH_LONG) .show(); // //proceed with logic by disabling the related features or quit the app. } } } } } } private void showDialogOK(String message, DialogInterface.OnClickListener okListener) { new AlertDialog.Builder(this) .setMessage(message) .setPositiveButton("OK", okListener) .setNegativeButton("Cancel", okListener) .create() .show(); } 

No, no necesita rastrear si solicitó o no el permiso, y no necesita distinguir Never-Asked From Stop-Asking.

El estado 1 y 3 son los mismos para el desarrollador de la aplicación: usted necesita el permiso y ActivityCompat.checkSelfPermission != PackageManager.PERMISSION_GRANTED , luego solo solicita el permiso a través de ActivityCompat.requestPermissions() , siempre que el usuario toque la función que requiere el permiso, no importa cuántas veces haya solicitado. El usuario eventualmente lo “otorgará” o lo “negará” con “nunca preguntar de nuevo” marcado. El diseño NO lo desanima de abrir el cuadro de diálogo de solicitud de permisos varias veces.

Sin embargo, el diseño lo alienta a explicar el propósito del permiso en algún momento: su estado 2. shouldShowRequestPermissionRationale() NO se usa para determinar si debe solicitar permiso, se usa para determinar si debe mostrar explicaciones, ANTES de solicitarlo con permiso.

Un par de explicaciones más sobre el estado 3:

  1. Sí, deberíamos dejar de molestar al usuario al dejar de mostrar la explicación, no detener la solicitud. Es por eso que proporcionaron el shouldShowRequestPermissionRationale() .
  2. No se molesta en mantener la solicitud de permiso. Después de que el usuario eligió “nunca preguntar de nuevo”, ActivityCompat.requestPermissions() ya no aparecerá en el cuadro de diálogo emergente.
  3. Es mejor desactivar la IU relevante cada vez que descubramos que no tenemos el permiso, durante la sesión de usuario único. En lugar de deshabilitar la IU después de que shouldShowRequestPermissionRationale() devuelva falso.

Tengo un enfoque para la solución a su problema, parece funcionar bastante bien para mí.

Distinguir Nunca preguntado de Stop-Asking usando SharedPreferences, le daré un ejemplo de cómo uso eso.

 private void requestAccountPermission() { SharedPreferences mPreferences = getSharedPreferences("configuration", MODE_PRIVATE); boolean firstTimeAccount = mPreferences.getBoolean("firstTimeAccount", true); if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.GET_ACCOUNTS)) { // 2. Asked before, and the user said "no" ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS}, REQUEST_CODE_ACCOUNTS); }else { if(firstTimeAccount) { // 1. first time, never asked SharedPreferences.Editor editor = mPreferences.edit(); editor.putBoolean("firstTimeAccount", false); editor.commit(); // Account permission has not been granted, request it directly. ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS},REQUEST_CODE_ACCOUNTS); }else{ // 3. If you asked a couple of times before, and the user has said "no, and stop asking" // Your code } } } 

Este es un método para rastrear cuando se mostró el cuadro de diálogo de permiso por primera vez, cuando el usuario marcó nunca volver a preguntar y cuando se deniega el permiso después de que el usuario lo revise, nunca vuelva a preguntar por esto. da como resultado onRequestPermissionsResult. Llame al método checkPermission () cuando sea necesario.

 public boolean mPermissionRationaleDialogShown = false; public void checkPermission() { if (ContextCompat.checkSelfPermission(this, "PermissionName") != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")) { showPermissionRequiredDialog(); } else { askPermission(); } } else { // Permission Granted } } public void askPermission() { ActivityCompat.requestPermissions(this, new String[]{"PermissionName"}, permissionRequestCode); } public void showPermissionRequiredDialog() { mPermissionRationaleDialogShown = true; // Dialog to show why permission is required } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission Granted } else { if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName") && !mPermissionRationaleDialogShown) { // Permission dialog was shown for first time } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName") && mPermissionRationaleDialogShown){ // User deny permission without Never ask again checked } else if (!ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_READ_EXTERNAL) && mPermissionRationaleDialogShown) { // User has checked Never ask again during this permission request } else { // No permission dialog shown to user has user has previously checked Never ask again. Here we can show dialog to open setting screen to change permission } } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } 

Después de probar toda la respuesta aquí y alguna otra publicación a través de internet. Llegué a saber que tengo que usar una SharedPreference isLocationPermissionDialogShown (valor predeterminado falso) y todo funciona según lo esperado.

  1. Si la primera vez pidió permiso. En este caso, shouldShowRequestPermissionRationale devuelve false y isLocationPermissionDialogShown también false
  2. Segunda vez shouldShowRequestPermissionRationale return true y, al mostrar el diálogo, establecemos que isLocationPermissionDialogShown es true . y cuando verificamos la condición, ambos serán true
  3. Cada vez hasta que nunca shouldShowRequestPermissionRationale preguntar shouldShowRequestPermissionRationale return true y isLocationPermissionDialogShown devuelve true
  4. If Never Ask Again shouldShowRequestPermissionRationale return false y isLocationPermissionDialogShown devuelve true . Que es lo que necesitamos

Por favor, compruebe el ejemplo de trabajo.

 public class MainActivity extends AppCompatActivity { SharedPreferences sharedPreferences; String locationPermission; String prefLocationPermissionKey = "isLocationPermissionDialogShown"; private final int PERMISSION_REQUEST_CODE_LOCATION = 1001; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); locationPermission = Manifest.permission.ACCESS_FINE_LOCATION; sharedPreferences = getSharedPreferences("configuration", MODE_PRIVATE); //check for android version if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //Check for permission if (checkSelfPermission(locationPermission) != PackageManager.PERMISSION_GRANTED) { //check if clarification dialog should be shown. if (shouldShowRequestPermissionRationale(locationPermission)) { showClarificationDialog(locationPermission, PERMISSION_REQUEST_CODE_LOCATION); } else { requestPermissions(new String[] { locationPermission}, PERMISSION_REQUEST_CODE_LOCATION); } } else { Log.d("nets-debug", "permission already grranted"); } } } @Override @TargetApi(Build.VERSION_CODES.M) public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) { //for location permission if (requestCode == PERMISSION_REQUEST_CODE_LOCATION) { boolean isLocationPermissionDialogShown = sharedPreferences.getBoolean(prefLocationPermissionKey, false); if (!shouldShowRequestPermissionRationale(locationPermission) && isLocationPermissionDialogShown) { // user selected Never Ask Again. do something Log.d("nets-debug", "never ask again"); } else { // all other conditions like first time asked, previously denied etc are captured here and can be extended if required. Log.d("nets-debug", "all other cases"); } } } } @TargetApi(Build.VERSION_CODES.M) public void showClarificationDialog(final String permission, final int requestCode) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Permission Required"); builder.setMessage("Please grant Location permission to use all features of this app"); builder.setPositiveButton("Grant", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putBoolean(prefLocationPermissionKey, true); editor.apply(); requestPermissions(new String[] {permission}, requestCode); } }); builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(getApplicationContext(), "This permission required", Toast.LENGTH_LONG).show(); } }); builder.create().show(); } } 

Espero que esto ayude

Puede mirar aquí : hay un diagtwig de flujo que explica el proceso bastante bien. También explica cuándo debe llamar a shouldShowRequestPermissionRationale() y cuándo devuelve verdadero.

Básicamente según la documentación de Android, siempre debes pedir permiso si no lo tienes (Android devolverá automáticamente DENEGADO en la callback si el usuario dice que nunca volverá a preguntar) y deberías mostrar un mensaje corto si el usuario ya ha rechazado una vez en el pasado pero no ha marcado la opción nunca preguntar de nuevo.

No es necesario crear un estado persistente paralelo para el estado de permiso; puede usar este método que devuelve el estado de permiso actual en cualquier momento:

 @Retention(RetentionPolicy.SOURCE) @IntDef({GRANTED, DENIED, BLOCKED}) public @interface PermissionStatus {} public static final int GRANTED = 0; public static final int DENIED = 1; public static final int BLOCKED = 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; } return DENIED; } return GRANTED; } 

Advertencia: devuelve BLOQUEADO el primer inicio de la aplicación, antes de que el usuario acepte / deniegue el permiso a través del aviso del usuario (en dispositivos SDK 23+)

También usé esto respondido aquí.