Permisos de Android M: Confundido sobre el uso de la función shouldShowRequestPermissionRationale ()

148

Estaba revisando el documento oficial sobre el nuevo modelo de permisos en Android M. Habla sobre la shouldShowRequestPermissionRationale()función que regresa truesi la aplicación ha solicitado este permiso anteriormente y el usuario rechazó la solicitud. Si el usuario rechazó la solicitud de permiso en el pasado y eligió la opción No volver a preguntar, este método regresa false.

Pero, ¿cómo podemos diferenciar entre los siguientes dos casos?

Caso 1 : la aplicación no tiene permiso y no se le ha pedido permiso al usuario antes. En este caso, shouldShowRequestPermissionRationale () devolverá false porque es la primera vez que le preguntamos al usuario.

Caso 2 : el usuario ha denegado el permiso y ha seleccionado "No volver a preguntar", en este caso también shouldShowRequestPermissionRationale () devolverá false.

Me gustaría enviar al usuario a la página de configuración de la aplicación en el Caso 2. ¿Cómo hago para diferenciar estos dos casos?

akshayt23
fuente
1
La respuesta aceptada es buena. Como alternativa, también puede usar un pref compartido para saber si la aplicación ha solicitado el permiso anteriormente. Solo tirar eso por si es más aplicable a la situación de otra persona.
Rockin4Life33
44
También hay un caso 3: al usuario se le ha pedido y se le ha otorgado / denegado el permiso, pero ha utilizado la configuración de permisos para volver a "preguntar siempre". Las pruebas muestran que las shouldShowRequestPermissionRationale()devoluciones son falsas en este caso, lo que perjudicará a cualquier código que se base en un indicador de "he preguntado antes".
Recogida de Logan
Aquí hay una muestra de Google que muestra las mejores prácticas en permissionsAndroid. github.com/android/permissions-samples
itabdullah
@itabdullah El código de muestra de Google es inútil ya que ni siquiera consideraron el muy probable caso de "¿el usuario le negó el permiso la última vez?". : - / típico
Alguien en algún lugar

Respuestas:

172

Después de M Vista previa 1, si el cuadro de diálogo se muestra por primera vez , no hay una casilla de verificación Nunca preguntar de nuevo .

Si el usuario rechaza la solicitud de permiso, habrá una casilla de verificación Nunca preguntar de nuevo en el cuadro de diálogo de permisos la segunda vez que se solicite el permiso.

Entonces la lógica debería ser así:

  1. Pedir permiso:

    if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
    } else {
        //Do the stuff that requires permission...
    }
  2. Verifique si el permiso fue denegado o concedido onRequestPermissionsResult.

    Si el permiso fue denegado anteriormente, esta vez habrá una casilla de verificación Nunca preguntar nuevamente en el cuadro de diálogo de permisos.

    Llame shouldShowRequestPermissionRationalepara ver si el usuario marcó No preguntar nunca más . shouldShowRequestPermissionRationaleEl método devuelve falso solo si el usuario seleccionó Nunca preguntar de nuevo o la política del dispositivo prohíbe que la aplicación tenga ese permiso:

    if (grantResults.length > 0){
        if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //Do the stuff that requires permission...
        }else if (grantResults[0] == PackageManager.PERMISSION_DENIED){
            // Should we show an explanation?
            if (ActivityCompat.shouldShowRequestPermissionRationale(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                //Show permission explanation dialog...
            }else{
                //Never ask again selected, or device policy prohibits the app from having that permission.
                //So, disable that feature, or fall back to another situation...
            }
        }
    }

Por lo tanto, no tendrá que rastrear si un usuario marcó Nunca preguntar de nuevo o no.

CanC
fuente
49
Un punto de aclaración, shouldShowRequestPermissionRationale () también devolverá falso si nunca se le ha pedido permiso al usuario (es decir, la primera vez que se ejecuta la aplicación). No se encontrará con ese caso si sigue la lógica del ejemplo proporcionado. Pero la redacción, debajo de 2 es un poco engañosa.
Ben
15
No estoy seguro, esto parece defectuoso. ¿Cómo se supone que debemos saber si es la primera vez que se le pregunta al usuario? Tengo que rastrear si se le preguntó al usuario, y si lo hizo, entonces tengo que revertir la lógica. No tiene ningún sentido para mí.
Daniel F
44
Creo que vale la pena señalar que, cuando está de paso contexten ActivityCompat.shouldShowRequestPermissionRationale(...)el parámetro es en realidad de tipo Activity. Puede que no los afecte a todos, pero en mi caso sí.
aProperFox
77
¡Esta lógica de Android es tan estúpida! ¡Me obliga a llamar al shoulden la devolución de llamada Y guardar su contravalor en NVM solo para saber si necesito volver a solicitar la solicitud la próxima vez que se abra la aplicación! ... wow (facepalm) ... ¿fue demasiado difícil hacer que solo una llamada devolviera una enumeración de estado?
Shockwaver
2
Creo que este es un gran fracaso de Google. La documentación oficial establece que debe llamarse shouldShowRequestPermissionRationale () antes de verificar los permisos (consulte developer.android.com/training/permissions/requesting#explain ), pero todas las respuestas en StackOverflow lo llaman en onRequestPermissionResult () para distinguir si el usuario hizo clic en "Nunca preguntar de nuevo" o no.
Miloš Černilovský
22

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

Caso 1: La aplicación no tiene permiso y no se le ha pedido permiso al usuario antes. En este caso, shouldShowRequestPermissionRationale () devolverá false porque es la primera vez que le preguntamos al usuario.

Caso 2: el usuario ha denegado el permiso y ha seleccionado "No volver a preguntar", en este caso también shouldShowRequestPermissionRationale () devolverá false.

Me gustaría enviar al usuario a la página de configuración de la aplicación en el Caso 2. ¿Cómo hago para diferenciar estos dos casos?

Recibirá devolución de llamada en onPermissionAsk para el caso 1 y onPermissionDisabled para el caso 2.

Feliz codificación :)

muthuraj
fuente
Excelente explicación hermano. Seguí exactamente el mismo procedimiento. :)
Sumit Jha
¿Qué debo completar para esta actividad? public void onPermissionAsk() { ActivityCompat.requestPermissions( thisActivity, ... .
Mardymar
@Mardymar thisActivityno es más que YourActivity.this.
muthuraj
1
cómo manejar múltiples permisos y cómo integrar este código dentro del fragmento.
Taimur
¿Qué tipo de contextestás usando? shouldShowRequestPermissionRationale(permission)no existe en android.content.Context. está en ActivityCompat
Hilikus
9

ACTUALIZAR

Creo que la respuesta de CanC a continuación es la correcta que debe seguirse. La única forma de saberlo con certeza es verificar esto en la devolución de llamada onRequestPermissionResult usando shouldShowPermissionRationale.

==

Mi respuesta original:

La única manera que he encontrado es hacer un seguimiento por su cuenta de si es la primera vez o no (por ejemplo, usando preferencias compartidas). Si no es la primera vez, entonces use shouldShowRequestPermissionRationale()para diferenciar.

Ver también: Android M: verifique el permiso de tiempo de ejecución: ¿cómo determinar si el usuario marcó "Nunca preguntar de nuevo"?

Alex Florescu
fuente
1
Sí, incluso estoy de acuerdo en que el método de CanC es el que debe seguirse. Voy a marcarlo como la respuesta aceptada.
akshayt23
6

Según tengo entendido, shouldShowRequestPermissionRationale () ejecuta una serie de casos de uso bajo el capó, y notifica a la aplicación si mostrar o no una explicación sobre los permisos solicitados.

La idea detrás de los permisos de Tiempo de ejecución es que la mayoría de las veces, el usuario dirá Sí a la solicitud de permiso. De esta forma, el usuario tendrá que hacer solo un clic. Por supuesto, la solicitud debe usarse en el contexto correcto, es decir, solicitar el permiso de la Cámara cuando se presiona el botón "Cámara".

Si el usuario rechaza la solicitud, pero después de un tiempo aparece y presiona el botón "Cámara" nuevamente, shouldShowRequestPermissionRationale () volverá verdadero, por lo que la aplicación puede mostrar una explicación significativa de por qué se solicita el permiso y por qué no funciona correctamente sin ella. Normalmente mostraría en esa ventana de diálogo un botón para negar nuevamente / decidir más tarde, y un botón para otorgar los permisos. El botón de otorgar permisos en el cuadro de diálogo de justificación, debe iniciar la solicitud de permiso nuevamente. Esta vez, el usuario también tendrá una casilla de verificación "Nunca volver a mostrar". Si decide seleccionarlo y volver a denegar el permiso, notificará al sistema Android que el usuario y la aplicación no están en la misma página. Esa acción tendría dos consecuencias: shouldShowRequestPermissionRationale () siempre devolverá false,

Pero también hay otro escenario posible en el que onRequestPermissionsResult podría usarse. Por ejemplo, algunos dispositivos pueden tener una política de dispositivos que deshabilita la cámara (funciona para CIA, DARPA, etc.). En estos dispositivos, onRequestPermissionsResult siempre devolverá falso, y el método requestPermissions () denegará silenciosamente la solicitud.

Eso es lo que obtuve al escuchar el podcast con Ben Poiesz, un gerente de producto en el marco de Android.
http://androidbackstage.blogspot.jp/2015/08/episode-33-permission-mission.html

Shumoapp
fuente
6

Simplemente publique otra opción, si alguien puede sentirlo. Puede usar EasyPermissions, que fue proporcionado por el propio Google, para, como se dijo, "Simplificar los permisos del sistema Android M".

Entonces no tienes que manejar shouldShowRequestPermissionRationaledirectamente.

Wei WANG
fuente
por qué no vi este proyecto anteriormente :)
Vlad
El problema con EasyPermissions sigue siendo casi el mismo. Preguntar permissionPermanentlyDeniedinternamente solo llamadas shouldShowPermissionsRationaley devoluciones trueen el caso en que nunca se solicitó al usuario que otorgue permisos.
hgoebl
4

Si alguien está interesado en una solución de Kotlin, refactoré la respuesta de @muthuraj para estar en Kotlin. También se modernizó un poco para tener un bloque de finalización en lugar de oyentes.

Permiso hasta

object PermissionUtil {
    private val PREFS_FILE_NAME = "preference"

    fun firstTimeAskingPermission(context: Context, permission: String, isFirstTime: Boolean) {
        val sharedPreference = context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE)
        sharedPreference.preferences.edit().putBoolean(permission,
                isFirstTime).apply()
    }

    fun isFirstTimeAskingPermission(context: Context, permission: String): Boolean {
        val sharedPreference = context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE)
        return sharedPreference.preferences.getBoolean(permission,
                true)
    }
}

Permiso Manejador

enum class CheckPermissionResult {
    PermissionAsk,
    PermissionPreviouslyDenied,
    PermissionDisabled,
    PermissionGranted
}

typealias PermissionCheckCompletion = (CheckPermissionResult) -> Unit


object PermissionHandler {

    private fun shouldAskPermission(context: Context, permission: String): Boolean {
        return ContextCompat.checkSelfPermission(context,
                permission) != PackageManager.PERMISSION_GRANTED
    }

    fun checkPermission(context: Context, permission: String, completion: PermissionCheckCompletion) {
        // If permission is not granted
        if (shouldAskPermission(context, permission)) {
            //If permission denied previously
            if ((context as Activity).shouldShowRequestPermissionRationale(permission)) {
                completion(CheckPermissionResult.PermissionPreviouslyDenied)
            } else {
                // Permission denied or first time requested
                if (PermissionUtil.isFirstTimeAskingPermission(context,
                                permission)) {
                    PermissionUtil.firstTimeAskingPermission(context,
                            permission,
                            false)
                    completion(CheckPermissionResult.PermissionAsk)
                } else {
                    // Handle the feature without permission or ask user to manually allow permission
                    completion(CheckPermissionResult.PermissionDisabled)
                }
            }
        } else {
            completion(CheckPermissionResult.PermissionGranted)
        }
    }
}

Implementación

PermissionHandler.checkPermission(activity,
                    Manifest.permission.CAMERA) { result ->
                when (result) {
                    CheckPermissionResult.PermissionGranted -> {
                        // openCamera()
                    }
                    CheckPermissionResult.PermissionDisabled -> {
                        // displayAlert(noPermissionAlert)
                    }
                    CheckPermissionResult.PermissionAsk -> {
                        // requestCameraPermissions()
                    }
                    CheckPermissionResult.PermissionPreviouslyDenied -> {
                        // displayAlert(permissionRequestAlert)
                    }
                }
            }
bmjohns
fuente
3

Verifica esta implementación. Funciona bastante bien para mí. básicamente, verifica los permisos en el método checkPermissions () pasando una lista de permisos. Comprueba el resultado de la solicitud de permiso en onRequestPermissionsResult (). La implementación le permite abordar ambos casos cuando el usuario selecciona "nunca preguntar de nuevo" o no. En esta implementación, en caso de que seleccione "nunca preguntar de nuevo", el cuadro de diálogo tiene una opción para llevarlo a la Actividad de configuración de la aplicación.

Todo este código está dentro de mi fragmento. Estaba pensando que sería mejor crear una clase especializada para hacer esto, como un PermissionManager, pero no estoy seguro de eso.

/**
     * responsible for checking if permissions are granted. In case permissions are not granted, the user will be requested and the method returns false. In case we have all permissions, the method return true.
     * The response of the request for the permissions is going to be handled in the onRequestPermissionsResult() method
     * @param permissions list of permissions to be checked if are granted onRequestPermissionsResult().
     * @param requestCode request code to identify this request in
     * @return true case we already have all permissions. false in case we had to prompt the user for it.
     */
    private boolean checkPermissions(List<String> permissions, int requestCode) {
        List<String> permissionsNotGranted = new ArrayList<>();
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(getActivity(), permission) != PackageManager.PERMISSION_GRANTED)
                permissionsNotGranted.add(permission);
        }

        //If there is any permission we don't have (it's going to be in permissionsNotGranted List) , we need to request.
        if (!permissionsNotGranted.isEmpty()) {
            requestPermissions(permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]), requestCode);
            return false;
        }
        return true;
    }

    /**
     * called after permissions are requested to the user. This is called always, either
     * has granted or not the permissions.
     * @param requestCode  int code used to identify the request made. Was passed as parameter in the
     *                     requestPermissions() call.
     * @param permissions  Array containing the permissions asked to the user.
     * @param grantResults Array containing the results of the permissions requested to the user.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case YOUR_REQUEST_CODE: {
                boolean anyPermissionDenied = false;
                boolean neverAskAgainSelected = false;
                // Check if any permission asked has been denied
                for (int i = 0; i < grantResults.length; i++) {
                    if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                        anyPermissionDenied = true;
                        //check if user select "never ask again" when denying any permission
                        if (!shouldShowRequestPermissionRationale(permissions[i])) {
                            neverAskAgainSelected = true;
                        }
                    }
                }
                if (!anyPermissionDenied) {
                    // All Permissions asked were granted! Yey!
                    // DO YOUR STUFF
                } else {
                    // the user has just denied one or all of the permissions
                    // use this message to explain why he needs to grant these permissions in order to proceed
                    String message = "";
                    DialogInterface.OnClickListener listener = null;
                    if (neverAskAgainSelected) {
                        //This message is displayed after the user has checked never ask again checkbox.
                        message = getString(R.string.permission_denied_never_ask_again_dialog_message);
                        listener = new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                //this will be executed if User clicks OK button. This is gonna take the user to the App Settings
                                startAppSettingsConfigActivity();
                            }
                        };
                    } else {
                        //This message is displayed while the user hasn't checked never ask again checkbox.
                        message = getString(R.string.permission_denied_dialog_message);
                    }
                    new AlertDialog.Builder(getActivity(), R.style.AlertDialogTheme)
                            .setMessage(message)
                            .setPositiveButton(getString(R.string.label_Ok), listener)
                            .setNegativeButton(getString(R.string.label_cancel), null)
                            .create()
                            .show();
                }
            }
            break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    /**
     * start the App Settings Activity so that the user can change
     * settings related to the application such as permissions.
     */
    private void startAppSettingsConfigActivity() {
        final Intent i = new Intent();
        i.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        i.addCategory(Intent.CATEGORY_DEFAULT);
        i.setData(Uri.parse("package:" + getActivity().getPackageName()));
        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
        i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
        getActivity().startActivity(i);
    }
Thiago Saraiva
fuente
2

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 (), solo muestra 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 para un ejemplo de trabajo detallado .

Mellas
fuente
66
devuelve falso por primera vez. no es cierto
JoM
Sí, eso es lo que mencioné, si marca el indicador en el método de devolución de llamada onRequestPermissionsResult (), tendrá solo dos estados, específicamente en esta devolución de llamada.
Nicks
2
Desafortunadamente, shouldShowRequestPermissionRationale siempre devuelve falso, independientemente de si el usuario alguna vez negó el permiso o no.
IgorGanapolsky
1

¿Podemos hacerlo de esta manera?

@Retention(RetentionPolicy.SOURCE)
@IntDef({GRANTED, DENIED, NEVER})
public @interface PermissionStatus {
}

public static final int GRANTED = 0;
public static final int DENIED = 1;
public static final int NEVER = 2;

@PermissionStatus
public static int getPermissionStatus(Activity activity, String permission) {
    if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
        return DENIED;
    } else {
        if (ActivityCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED) {
            return GRANTED;
        } else {
            return NEVER;
        }
    }
}
Dr. aNdRO
fuente
Desafortunadamente, este código no distingue entre una situación en la que nunca antes se solicitó el permiso y donde se verificó "nunca volver a solicitar".
Ben
debe usar la combinación de esto + la clase auxiliar de permisos para verificar si el permiso se otorga o no.
Dr. aNdRO
0

shouldShowRequestPermissionRationale para permiso ESPECIAL siempre devuelve VERDADERO SOLO después de que el usuario lo haya denegado sin casilla de verificación

Estamos interesados ​​en el valor FALSO

Entonces hay 3 casos perdidos con valor falso :

1. no hubo tal acción anteriormente y ahora el usuario decide aceptar o negar.

Simplemente defina una preferencia ASKED_PERMISSION_*que no existe ahora y que sería cierta en onRequestPermissionsResultsu inicio en cualquier caso de acuerdo o denegación

Entonces, aunque esta preferencia no existe, no hay razón para verificarshouldShowRequestPermissionRationale

2. el usuario hizo clic en aceptar.

Simplemente haz:

checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED

Lo cual devolverá verdadero y no hay razón para verificarshouldShowRequestPermissionRationale

3. el usuario hizo clic en denegar con casilla de verificación (segundo o más tiempo solicitado)

Es EL MOMENTO de trabajar con el shouldShowRequestPermissionRationaleque volverá FALSO

(existe preferencia y no tenemos permiso)

Vlad
fuente
0

Este código le pide al usuario que pida permiso durante el tiempo de ejecución, si el usuario lo permite, ejecuta el método de resultado, si el usuario lo niega, vuelve a preguntar con la descripción con el usuario denegar (pregunta nuevamente con las instrucciones), pero si el usuario elige no volver a preguntar nunca más. maneja nunca preguntar de nuevo, muestra la opción de configuración abierta con instrucciones.

public String storagePermissions = Manifest.permission.READ_EXTERNAL_STORAGE;   
private static final int REQUEST_ACCESS =101;  

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      if(checkSelfPermission(storagePermissions)== PackageManager.PERMISSION_GRANTED){
          result();    // result  is your block of code 
      }else {
          requestPermissions(new String[]{storagePermissions},REQUEST_ACCESS);
      }

    }
    else{
        result();    //so if user is lower than api verison M, no permission is requested
    } 

}

 private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
    new AlertDialog.Builder(MainActivity.this)
            .setMessage(message)
            .setTitle("Hi User..")
            .setPositiveButton("Ok", okListener)
            .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {        //idea calling showMessage funtion again
                    Snackbar mySnackbar = Snackbar.make( findViewById(R.id.coordinatorlayout),"You Press Cancel.. ", Snackbar.LENGTH_INDEFINITE);
                    mySnackbar.setAction("Exit", new cancelButton());
                    mySnackbar.show();

                }
            })
            .create()
            .show();
}


private void result(){
          //your code
}

    @RequiresApi(api = Build.VERSION_CODES.M)
public class NeverAskAgain implements View.OnClickListener{
    @Override
    public void onClick(View view)
    {
        goToSettings();
    }
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void goToSettings() {
    Intent myAppSettings = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName()));
    finish();
    myAppSettings.addCategory(Intent.CATEGORY_DEFAULT);
    myAppSettings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivityForResult(myAppSettings, REQUEST_APP_SETTINGS);
}
public class cancelButton implements View.OnClickListener{
    @Override
    public void onClick(View view){
        Toast.makeText(MainActivity.this,"To use this app , you must grant storage permission",Toast.LENGTH_SHORT);
        finish();
    }
    }


 @Override
@RequiresApi(api = Build.VERSION_CODES.M)
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode,permissions,grantResults);

    switch(requestCode) {
        case REQUEST_ACCESS:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // permission is granted
                    result();
                    break;
                }
                else if (!shouldShowRequestPermissionRationale(permissions[0])){
                    showMessageOKCancel("You choose Never Ask Again,option",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Snackbar mySnackbar = Snackbar.make(findViewById(R.id.coordinatorlayout), "Permission=>Storage=>On", Snackbar.LENGTH_INDEFINITE);
                        mySnackbar.setAction("Settings", new NeverAskAgain());
                        mySnackbar.show();
                    }
                     });
                    break;
                }
                else {
                    showMessageOKCancel("You Denid permission Request..",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            requestPermissions(new String[]{storagePermissions}, REQUEST_ACCESS);
                        }
                    });
                    break;
                }
        }
}
Abhishek Garg
fuente