com.aitorvs.android.allowme.AllowMe.java Source code

Java tutorial

Introduction

Here is the source code for com.aitorvs.android.allowme.AllowMe.java

Source

package com.aitorvs.android.allowme;

/*
 * Copyright (C) 02/12/15 aitorvs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.preference.PreferenceManager;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;

import java.lang.reflect.Method;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class AllowMe {

    private static final String TAG = AllowMe.class.getSimpleName();
    private static AllowMe instance;

    private Activity mActivity;
    private final Map<String, ArrayList<AllowMeCallback>> mRequestList;

    private AllowMe() {
        mRequestList = new HashMap<>();
    }

    // singleton class
    private static AllowMe getInstance() {
        if (instance == null) {
            instance = new AllowMe();
        }

        return instance;
    }

    public static void registerActivity(@NonNull Activity activity) {
        getInstance().mActivity = activity;

    }

    public static void unregisterActivity(@NonNull Activity activity) {
        //noinspection ConstantConditions
        if (getInstance().mActivity == null || activity == null) {
            Log.w(TAG, "Trying to unregister null activity");
        } else if (activity.getClass().getName().equals(getInstance().mActivity.getClass().getName())) {
            getInstance().mActivity = null;
        } else {
            Log.w(TAG, "unregisterActivity: Old activity is trying to unregister");
        }

    }

    private static Activity safeActivity() {
        Activity activity = getInstance().mActivity;
        if (activity == null) {
            throw new IllegalStateException("Ooops!! Activity not registered (?)");
        }

        return activity;
    }

    /**
     * Checks whether a particular permissions is already granted
     *
     * @param permission permission
     * @return <code>boolean</code> value, <code>true</code> when permissions is already granted,
     * <code>false</code> when permissions not granted.
     */
    public static boolean isPermissionGranted(@NonNull String permission) {
        return ContextCompat.checkSelfPermission(safeActivity(), permission) == PackageManager.PERMISSION_GRANTED;
    }

    /**
     * Checks whether user should show the permissions request rationale or not
     *
     * @param permission permission
     * @return Returns <code>boolean</code> with value <code>true</code> when rationale should be shown
     * or <code>false</code> otherwise
     */
    public static boolean shouldShowRationale(@NonNull String permission) {
        return ActivityCompat.shouldShowRequestPermissionRationale(safeActivity(), permission);
    }

    /**
     * Dispatches the permission request to the user handler
     *
     * @param requestCode  permission request code
     * @param permissions  permissions
     * @param grantResults request results
     * @return boolean Return <code>false</code> to allow normal menu processing to
     * proceed, <code>true</code> to consume it here.
     */
    public static boolean dispatchResult(int requestCode, String[] permissions, int[] grantResults) {
        synchronized (getRequestQueue()) {
            // get all the permission keys
            final String cacheKey = getRequestKey(requestCode, permissions);
            // get all registered callbacks for the key
            final ArrayList<AllowMeCallback> callbacks = getRequestQueue().get(cacheKey);

            // no callbacks return
            if (callbacks != null) {
                final PermissionResultSet resultSet = PermissionResultSet.create(permissions, grantResults);

                for (AllowMeCallback callback : callbacks) {
                    callback.onPermissionResult(requestCode, resultSet);
                }

                // now remove the request from the queue
                getRequestQueue().remove(cacheKey);

                // consume the event here
                return true;
            }

            // no callback called, event not consumed here.
            return false;
        }
    }

    /**
     * Requests the permission, showing the given rationale when necessary and calls the registered
     * callback when permission request operation is performed
     *
     * @param callback        {@link AllowMeCallback} callback method
     * @param requestCode     request code identifier
     * @param rationale        string rationale to be shown when appropriate
     * @param rationaleThemeId rationale alert dialog theme id
     * @param permission      permission under request
     */
    private static void requestPermissionWithRationale(final AllowMeCallback callback, final int requestCode,
            String rationale, int rationaleThemeId, final String permission) {

        // healthy check
        if (isPermissionGranted(permission)) {
            // permission is already granted...why you ask?
            return;
        }

        if (ActivityCompat.shouldShowRequestPermissionRationale(safeActivity(), permission)) {
            AlertDialog.Builder builder = new AlertDialog.Builder(safeActivity(), rationaleThemeId).setTitle("")
                    .setMessage(rationale).setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            requestPermissions(callback, requestCode, permission);
                        }
                    }).setNegativeButton("Not Now", null);

            builder.show();
        } else {
            requestPermissions(callback, requestCode, permission);
        }
    }

    /**
     * Requests the permission, and calls the registered callback when permission request operation
     * is performed
     *
     * @param callback    callback method
     * @param requestCode request code identifier
     * @param permission  permission to request
     */
    private static void requestPermission(final AllowMeCallback callback, int requestCode,
            final String permission) {

        // healthy check
        if (!isPermissionGranted(permission)) {
            requestPermissions(callback, requestCode, permission);
        }
    }

    private static void requestPermissions(AllowMeCallback callback, int requestCode, String... permissions) {

        synchronized (getRequestQueue()) {
            final String requestKey = getRequestKey(requestCode, permissions);
            ArrayList<AllowMeCallback> callbackList = getRequestQueue().get(requestKey);
            if (callbackList != null) {
                callbackList.add(callback);
            } else {
                callbackList = new ArrayList<>();
                callbackList.add(callback);
                getRequestQueue().put(requestKey, callbackList);

                ActivityCompat.requestPermissions(safeActivity(), permissions, requestCode);
            }
        }
    }

    /**
     * Generates a key given the permissions requested and its request code
     *
     * @param requestCode request code identifier
     * @param permissions permissions
     * @return generated request key
     */
    private static String getRequestKey(int requestCode, String[] permissions) {
        StringBuilder result = new StringBuilder();
        result.append(requestCode);
        for (String perm : permissions) {
            result.append(perm);
            result.append("\0");
        }
        return result.toString();
    }

    private static Map<String, ArrayList<AllowMeCallback>> getRequestQueue() {
        return getInstance().mRequestList;
    }

    /**
     * Builder class
     */
    public static class Builder {
        private String rationale;
        private int rationaleThemeId = 0;
        private String permission;
        private AllowMeCallback callback;
        private String primingMessage;

        /**
         * Set the permission rationale message
         *
         * @param rationale {@link String} rationale
         * @return {@link Builder}
         */
        public Builder setRationale(@NonNull String rationale) {
            this.rationale = rationale;
            return this;
        }

        /**
         * Sset the permission rationale
         *
         * @param res Rationale string resource ID
         * @return {@link Builder}
         */
        public Builder setRationale(@StringRes int res) {
            this.rationale = safeActivity().getString(res);
            return this;
        }

        /**
         * Set the rationale dialog theme
         *
         * @param themeId identifier
         * @return {@link Builder}
         */
        public Builder setRationaleThemeId(@IntRange(from = 0, to = Integer.MAX_VALUE) int themeId) {
            this.rationaleThemeId = themeId;
            return this;
        }

        /**
         * Set the permission to request
         *
         * @param permissions permissions
         * @return {@link Builder}
         */
        public Builder setPermissions(@NonNull String permissions) {
            this.permission = permissions;
            return this;
        }

        /**
         * Set the callback to be call once the permissions are granted
         *
         * @param callback {@link AllowMeCallback} callback
         * @return {@link Builder}
         */
        public Builder setCallback(@NonNull AllowMeCallback callback) {
            this.callback = callback;
            return this;
        }

        /**
         * Set the permission priming message that will be shown before the permission is request
         * (Optional)
         *
         * @param primingMessage priming message {@link String}
         * @return {@link Builder}
         */
        public Builder setPrimingMessage(String primingMessage) {
            this.primingMessage = primingMessage;
            return this;
        }

        /**
         * Request the permissions set using the Builder
         *
         * @param requestCode positive <code>int</code> value to identify the permission request
         */
        public void request(@IntRange(from = 1, to = Integer.MAX_VALUE) final int requestCode) {
            // some checks
            throwIfNoPermissions();
            throwIfNoCallback();

            // permission priming ?
            if (this.primingMessage != null) {
                // show the priming message
                AlertDialog.Builder builder = new AlertDialog.Builder(safeActivity(), rationaleThemeId).setTitle("")
                        .setMessage(this.primingMessage)
                        .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestPermission(Builder.this.callback, requestCode);
                            }
                        }).setNegativeButton("Not now", null);

                builder.show();
            } else if (this.primingMessage == null) {
                // request permission directly
                requestPermission(Builder.this.callback, requestCode);
            }
        }

        public void requestPermissionForResult(final @NonNull Object handlerClass,
                @IntRange(from = 1, to = Integer.MAX_VALUE) int requestCode) {
            // throw when user forgot the permissions
            throwIfNoPermissions();
            // we don't need the callbacks here because should be using annotated callback
            final Method annotatedCallback = getAnnotatedMethod(handlerClass, OnPermissionResult.class);
            annotatedCallback.setAccessible(true);

            final int fRequestCode = requestCode;

            // do the magic
            // permission priming ?
            if (this.primingMessage != null) {
                // show the priming message
                AlertDialog.Builder builder = new AlertDialog.Builder(safeActivity(), rationaleThemeId).setTitle("")
                        .setMessage(this.primingMessage)
                        .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestPermission(new AllowMeCallback() {
                                    @Override
                                    public void onPermissionResult(int requestCode, PermissionResultSet results) {
                                        try {
                                            annotatedCallback.invoke(handlerClass, requestCode, results);
                                        } catch (Exception e) {
                                            throw new IllegalStateException(
                                                    String.format("Error invoking %s", annotatedCallback.getName()),
                                                    e);
                                        }
                                    }
                                }, fRequestCode);
                            }
                        }).setNegativeButton("Not now", null);

                builder.show();
            } else if (this.primingMessage == null) {
                // request permission directly
                requestPermission(new AllowMeCallback() {
                    @Override
                    public void onPermissionResult(int requestCode, PermissionResultSet results) {
                        try {
                            annotatedCallback.invoke(handlerClass, requestCode, results);
                        } catch (Exception e) {
                            throw new IllegalStateException(
                                    String.format("Error invoking %s", annotatedCallback.getName()), e);
                        }
                    }
                }, fRequestCode);
            }

        }

        private Method getAnnotatedMethod(Object target, Class<OnPermissionResult> targetAnnotation) {
            final Method[] targetMethods = target.getClass().getDeclaredMethods();
            Method match = null;
            for (Method method : targetMethods) {
                OnPermissionResult annotation = method.getAnnotation(targetAnnotation);
                // FIXME: 02/01/16 permissions should eventually be an array
                if (annotation != null
                        && equalPermissions(new String[] { this.permission }, annotation.requestedPermissions())) {
                    match = method;
                    break;
                }
            }

            // check the correctness of the annotated method
            if (match == null) {
                throw new IllegalStateException(String.format(
                        "No OnPermissionResult annotated "
                                + "methods found in %s or with different permission set parameters.",
                        target.getClass().getName()));
            } else if (match.getParameterTypes().length != 2
                    || !match.getParameterTypes()[0].toString().equalsIgnoreCase("int")
                    || match.getParameterTypes()[1] != PermissionResultSet.class) {
                throw new IllegalStateException(String.format(
                        "Method %s shall have two " + "parameters of type 'int' and 'PermissionResultSet'",
                        match.getName()));
            }

            return match;
        }

        /**
         * Compare two array of permissions, independent of the order inside the array
         *
         * @param foo array of permissions
         * @param bar array of permissions
         * @return <code>true</code> when match, <code>false</code> otherwise
         */
        private boolean equalPermissions(String[] foo, String[] bar) {
            if (foo == null || bar == null) {
                return (foo == null) && (bar == null);
            } else if (foo.length != bar.length) {
                return false;
            }

            // sort the arrays
            Arrays.sort(foo);
            Arrays.sort(bar);

            // do the actual compare
            for (int i = 0; i < foo.length; i++) {
                if (!foo[i].equals(bar[i]))
                    return false;
            }
            return true;
        }

        /**
         * Request the permissions using the {@link Builder} fields and the given params
         *
         * @param callback    permission request callback
         * @param requestCode permission request code identifier
         */
        private void requestPermission(AllowMeCallback callback, int requestCode) {
            if (rationale == null) {
                AllowMe.requestPermission(callback, requestCode, this.permission);
            } else {
                AllowMe.requestPermissionWithRationale(callback, requestCode, rationale, rationaleThemeId,
                        this.permission);
            }
        }

        private void throwIfNoPermissions() {
            if (this.permission == null) {
                throw new InvalidParameterException("Permissions must be set");
            }
        }

        private void throwIfNoCallback() {
            if (this.callback == null) {
                throw new InvalidParameterException("Callback must be set");
            }
        }
    }

}