↩ home



Yet another way to deal with Android Permissions

18 Jul 2016

Android M introduced permissions ask at runtime, which I personnally think is a good thing for users. But it also means every developers need to be aware of how to implement it in their app. Thanksfully Android community react quickly and produced lot of article, code and libraries to help to the transition.

Permission popup

It was time for me to update my application and I wanted to do it in a simple way. I first looked into existing libraries - https://android-arsenal.com/tag/235 - but I find them too complex or not enought interesting to add yet-another gradle line.

So I decided to create my little class helper, doing the job, simple and generic.


start with the basics

When we use a method which needs a permission, Android Studio will tell us that we need to handle permissions..

So we check if we have the permission, and this is only relevant for Android M and above :

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
   int perm = activity.checkSelfPermission(Manifest.permission.USE_FINGERPRINT);
   if (perm != PackageManager.PERMISSION_GRANTED) {
      // Permission not granted (need to ask for it).
   }
   else {
      // Permission granted (user already accepted).
   }
}
else {
   // Permission granted (because no runtime permission).
}

The first time we necessarily need to ask for the permission. And it can fails if user disallows it.

private static final int REQUEST_CODE = 1337;
activity.requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, REQUEST_CODE);

....

// Callback from Activity
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    switch (requestCode) {
        case REQUEST_CODE: {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission granted
            }
            else {
                // Permission denied
            }
        }
    }
}

That's it. We have everything we need to manage permissions. But you certainly do not want to copy this part of code everytime you need a permission.

In order to create a good generic process, we need to take into account :

  • There are several cases in the code where the permission is granted.
  • We need to pass through an Activity's (or Fragment) callback.
  • We did not explain why we ask for permission (and why the app does not work if he refuses the permission).
  • There is another case not handled : user can check "Never ask me again" on a permission popup.

parameters

For the fun and to use a good pattern (and because you may need to add new features), let's create a Builder and a callback listener :

public class PermissionHelper {
    /**
     * Two possible callback: granted or not.
     */
    public interface PermissionListener {
        void onPermissionGranted();

        void onPermissionDenied();
    }

    private static final int REQUEST_CODE = 1337;

    public static class Builder {
        private Activity activity;
        private String permission;
        private PermissionListener listener;
        private @StringRes int message_explain;
        private @StringRes int messsage_fail;

        private Builder() {

        }

        /**
         * Manage permissions.
         *
         * @param activity        Calling activity.
         * @param permission      Permission to ask.
         * @param listener        Callback..
         * @param message_explain Message to show before to ask permission.
         * @param messsage_fail   Message to show if user refused to grant permission.
         */
        public static Builder goWithPermission(@NonNull final Activity activity,
                                               @NonNull final String permission,
                                               @Nullable final PermissionListener listener,
                                               @StringRes final int message_explain,
                                               @StringRes final int messsage_fail) {
            Builder builder = new Builder();
            builder.activity = activity;
            builder.permission = permission;
            builder.listener = listener;
            builder.message_explain = message_explain;
            builder.messsage_fail = messsage_fail;
            return builder;
        }

        public PermissionHelper start() {
            return new PermissionHelper(this);
        }
    }

    private PermissionHelper(Builder builder) {
         // We can do the work here.
    }
}

Easy to call :

// Called from a Fragment for example
PermissionHelper.Builder.goWithPermission(getActivity(),
    Manifest.permission.USE_FINGERPRINT,
    R.string.ask,  // "My app need permission to enable fingerprint lock feature"
    R.string.fail, // "Fingerprint lock feature cannot be used without permission"
    new PermissionHelper.PermissionListener() {
        @Override
        public void onPermissionGranted() {
            // Hooray, let's start fingerprint scan.
        }

        @Override
        public void onPermissionDenied() {

        }
    })
    .start();

final code

public class PermissionHelper {
    /**
     * Two possible callback: granted or not.
     */
    public interface PermissionListener {
        void onPermissionGranted();

        void onPermissionDenied();
    }

    private static final int REQUEST_CODE = 1337;

    // Keep instance of last listener & message fail.
    private static PermissionListener last_listener;
    private static @StringRes int last_messsage_fail;

    public static class Builder {
        private Activity activity;
        private String permission;
        private PermissionListener listener;
        private @StringRes int message_explain;
        private @StringRes int messsage_fail;

        private Builder() {

        }

        /**
         * Manage permissions.
         *
         * @param activity        Calling activity.
         * @param permission      Permission to ask.
         * @param listener        Callback..
         * @param message_explain Message to show before to ask permission.
         * @param messsage_fail   Message to show if user refused to grant permission.
         */
        public static Builder goWithPermission(@NonNull final Activity activity,
                                               @NonNull final String permission,
                                               @Nullable final PermissionListener listener,
                                               @StringRes final int message_explain,
                                               @StringRes final int messsage_fail) {
            Builder builder = new Builder();
            builder.activity = activity;
            builder.permission = permission;
            builder.listener = listener;
            builder.message_explain = message_explain;
            builder.messsage_fail = messsage_fail;
            return builder;
        }

        public PermissionHelper start() {
            return new PermissionHelper(this);
        }
    }

    private PermissionHelper(final Builder builder) {
        last_listener = builder.listener;
        last_messsage_fail = builder.messsage_fail;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            int perm = builder.activity.checkSelfPermission(builder.permission);
            if (perm != PackageManager.PERMISSION_GRANTED) {

                // If user do not want to be bother again (cliked on "Never ask again").
                if (!builder.activity.shouldShowRequestPermissionRationale(builder.permission)) {
                    showFailMessage(builder.activity);
                    return;
                }

                // Inform user why we ask permission
                final AlertDialog.Builder dialog = new AlertDialog.Builder(builder.activity);
                dialog.setTitle("Permission required");
                dialog.setMessage(builder.message_explain);
                dialog.setPositiveButton(android.R.string.ok, null);
                dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            builder.activity.requestPermissions(new String[]{builder.permission}, REQUEST_CODE);
                        }
                });
                dialog.show();
            } else if (last_listener != null) {
                last_listener.onPermissionGranted();
            }
        } else if (last_listener != null) {
            last_listener.onPermissionGranted();
        }
    }

    /**
     * Must be called from Activity's onRequestPermissionsResult()
     */
    public static void onRequestPermissionsResult(@NonNull Activity activity,
                                                  int requestCode,
                                                  @NonNull String[] permissions,
                                                  @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE: {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    if (last_listener != null) {
                        last_listener.onPermissionGranted();
                    }
                } else if (last_messsage_fail != 0) {
                    showFailMessage(activity);
                } else if (last_listener != null) {
                    last_listener.onPermissionDenied();
                }
            }
        }
    }

    /**
     * Show final fail message.
     */
    private static void showFailMessage(@NonNull Activity activity) {
        final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        builder.setTitle(R.string.AndroidPermission_storageLimited_title3);
        builder.setMessage(last_messsage_fail);
        builder.setPositiveButton(android.R.string.ok, null);
        builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                if (last_listener != null) {
                    last_listener.onPermissionDenied();
                }
            }
        });
        builder.show();
    }

}

Last step is to plug class with calling Activity :

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        PermissionHelper.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
    }


Top