package com.jdroid.android.permission;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.ContextCompat;
import com.jdroid.android.R;
import com.jdroid.android.application.AbstractApplication;
import com.jdroid.android.dialog.AppInfoDialogFragment;
/**
* Helper class for permissions.
*
* When using an PermissionHelper instance, it is required to set the listener to receive callbacks using the method
* {@link #setOnRequestPermissionsResultListener(OnRequestPermissionsResultListener)}, and to call the methods
* {@link #onResume()} and {@link #onRequestPermissionsResult(int, String[], int[])} from the respective activity/fragment methods.
*
* If you prefer to use the static methods to chek permissions like {@link #checkPermission(Fragment, int, int, String, int)},
* Your activity/Fragment has to implement the "OnRequestPermissionsResultCallback" method (
* {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])} or
* {@link android.support.v4.app.Fragment#onRequestPermissionsResult(int, String[], int[])})
*
*/
public class PermissionHelper {
public static final String LOCATION_PERMISSION = Manifest.permission.ACCESS_COARSE_LOCATION;
public static final String CAMERA_PERMISSION = Manifest.permission.CAMERA;
public static final String ACCOUNTS_PERMISSION = Manifest.permission.GET_ACCOUNTS;
public static final String WRITE_EXTERNAL_STORAGE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public static final int LOCATION_PERMISSION_REQUEST_CODE = 91;
public static final int CAMERA_PERMISSION_REQUEST_CODE = 92;
public static final int ACCOUNTS_PERMISSION_REQUEST_CODE = 93;
public static final int WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 94;
private PermissionDelegate permissionDelegate;
private int permissionRequestCode;
private String permission;
private int permissionRationaleMessageResId;
private int appInfoDialogMessageResId;
private int permissionRationaleTitleResId;
private int appInfoDialogTitleResId;
private OnRequestPermissionsResultListener onRequestPermissionsResultListener;
private boolean showAppInfoDialogEnabled = true;
private boolean requestPermissionOnStart = false;
private boolean pendingAppInfoDialog;
private Boolean previouslyShouldShowRequestPermissionRationale;
private boolean firstTime;
public static PermissionHelper createLocationPermissionHelper(Fragment fragment) {
return new PermissionHelper(fragment, LOCATION_PERMISSION, LOCATION_PERMISSION_REQUEST_CODE);
}
public static PermissionHelper createCameraPermissionHelper(Fragment fragment) {
return new PermissionHelper(fragment, CAMERA_PERMISSION, CAMERA_PERMISSION_REQUEST_CODE);
}
public static PermissionHelper createAccountsPermissionHelper(Fragment fragment) {
return new PermissionHelper(fragment, ACCOUNTS_PERMISSION, ACCOUNTS_PERMISSION_REQUEST_CODE);
}
public static PermissionHelper createWriteExternalStoragePermissionHelper(Fragment fragment) {
return new PermissionHelper(fragment, WRITE_EXTERNAL_STORAGE_PERMISSION, WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
}
public static Boolean checkPermission(FragmentActivity fragmentActivity, @StringRes int titleResId, @StringRes int messageResId, String permission, int permissionRequestCode) {
return checkPermission(createPermissionDelegate(fragmentActivity), titleResId, messageResId, permission, permissionRequestCode);
}
public static Boolean checkPermission(FragmentActivity fragmentActivity, String title, CharSequence message, String permission, int permissionRequestCode) {
return checkPermission(createPermissionDelegate(fragmentActivity), title, message, permission, permissionRequestCode);
}
public static Boolean checkPermission(FragmentActivity fragmentActivity, String permission, int permissionRequestCode) {
return checkPermission(createPermissionDelegate(fragmentActivity), permission, permissionRequestCode);
}
public static Boolean shouldShowRequestPermissionRationale(FragmentActivity fragmentActivity, String permission) {
return shouldShowRequestPermissionRationale(createPermissionDelegate(fragmentActivity), permission);
}
public static Boolean checkPermission(Fragment fragment, @StringRes int titleResId, @StringRes int messageResId, String permission, int permissionRequestCode) {
return checkPermission(createPermissionDelegate(fragment), titleResId, messageResId, permission, permissionRequestCode);
}
public static Boolean checkPermission(Fragment fragment, String title, CharSequence message, String permission, int permissionRequestCode) {
return checkPermission(createPermissionDelegate(fragment), title, message, permission, permissionRequestCode);
}
public static Boolean checkPermission(Fragment fragment, String permission, int permissionRequestCode) {
return checkPermission(createPermissionDelegate(fragment), permission, permissionRequestCode);
}
public static Boolean shouldShowRequestPermissionRationale(Fragment fragment, String permission) {
return shouldShowRequestPermissionRationale(createPermissionDelegate(fragment), permission);
}
private static boolean checkPermission(PermissionDelegate permissionDelegate, @StringRes int titleResId, @StringRes int messageResId, String permission, int permissionRequestCode) {
return checkPermission(permissionDelegate, permissionDelegate.getActivity().getString(titleResId), permissionDelegate.getActivity().getText(messageResId), permission, permissionRequestCode);
}
private static boolean checkPermission(PermissionDelegate permissionDelegate, String title, CharSequence message, String permission, int permissionRequestCode) {
// Try catch added for exception occurred when checking permissions on some Lenovo 7" tablets
boolean hasPermission;
try {
if (permissionDelegate.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
if (permissionDelegate.shouldShowRequestPermissionRationale(permission)) {
permissionDelegate.showPermissionDialogFragment(title, message, permission, permissionRequestCode);
} else {
permissionDelegate.requestPermissions(new String[]{permission}, permissionRequestCode);
}
hasPermission = false;
} else {
hasPermission = true;
}
} catch (Exception e) {
hasPermission = false;
AbstractApplication.get().getExceptionHandler().logWarningException("Exception checking permission", e);
}
return hasPermission;
}
private static boolean checkPermission(PermissionDelegate permissionDelegate, String permission, int permissionRequestCode) {
// Try catch added for exception occurred when checking permissions on some Lenovo 7" tablets
boolean hasPermission;
try {
if (permissionDelegate.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
permissionDelegate.requestPermissions(new String[] {permission}, permissionRequestCode);
hasPermission = false;
} else {
hasPermission = true;
}
} catch (Exception e) {
hasPermission = false;
AbstractApplication.get().getExceptionHandler().logWarningException("Exception checking permission", e);
}
return hasPermission;
}
private static boolean shouldShowRequestPermissionRationale(PermissionDelegate permissionDelegate, String permission) {
return permissionDelegate.shouldShowRequestPermissionRationale(permission);
}
public static Boolean verifyPermission(Context context, String permission) {
// Try catch added for exception occurred when checking permissions on some Lenovo 7" tablets
Boolean hasPermission;
try {
hasPermission = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
} catch (Exception e) {
hasPermission = false;
AbstractApplication.get().getExceptionHandler().logWarningException("Exception checking permission", e);
}
return hasPermission;
}
protected static PermissionDelegate createPermissionDelegate(@NonNull FragmentActivity fragmentActivity) {
return new ActivityPermissionDelegate(fragmentActivity);
}
protected static PermissionDelegate createPermissionDelegate(@NonNull Fragment fragment) {
return new FragmentPermissionDelegate(fragment);
}
public PermissionHelper(@NonNull FragmentActivity fragmentActivity, @NonNull String permission, int permissionRequestCode) {
this(new ActivityPermissionDelegate(fragmentActivity), permission, permissionRequestCode);
}
public PermissionHelper(@NonNull Fragment fragment, @NonNull String permission, int permissionRequestCode) {
this(new FragmentPermissionDelegate(fragment), permission, permissionRequestCode);
}
private PermissionHelper(PermissionDelegate permissionDelegate, String permission, int permissionRequestCode) {
this.permissionDelegate = permissionDelegate;
this.permission = permission;
this.permissionRequestCode = permissionRequestCode;
}
/**
* Sets the message to be displayed in the permission rationale dialog, from a resource.
* If not set, the permission rationale dialog will not be shown.
*
* @param permissionRationaleMessageResId the resource id for the message
*/
public void setPermissionRationaleMessageResId(@StringRes int permissionRationaleMessageResId) {
this.permissionRationaleMessageResId = permissionRationaleMessageResId;
}
/**
* Sets the title to be displayed in the permission rationale dialog, from a resource.
* If not set, the default title is used {@link R.string#jdroid_requiredPermission} is used.
*
* @param permissionRationaleTitleResId the resource id for the title
*/
public void setPermissionRationaleTitleResId(int permissionRationaleTitleResId) {
this.permissionRationaleTitleResId = permissionRationaleTitleResId;
}
/**
* Sets the message to be displayed in the App Info dialog, from a resource.
* If not set and {@link #isShowAppInfoDialogEnabled()} is true, the message specified in {@link #setPermissionRationaleMessageResId(int)} is used.
*
* @param appInfoDialogMessageResId the resource id for the message
*/
public void setAppInfoDialogMessageResId(@StringRes int appInfoDialogMessageResId) {
this.appInfoDialogMessageResId = appInfoDialogMessageResId;
}
/**
* Sets the title to be displayed in the App Info dialog, from a resource.
* If not set and {@link #isShowAppInfoDialogEnabled()} is true, the title specified in {@link #setPermissionRationaleTitleResId(int)} or the default title is used.
*
* @param appInfoDialogTitleResId the resource id for the title
*/
public void setAppInfoDialogTitleResId(int appInfoDialogTitleResId) {
this.appInfoDialogTitleResId = appInfoDialogTitleResId;
}
/**
* Sets the listener to be called for the result from requesting permissions.
*
* @see OnRequestPermissionsResultListener
*/
public void setOnRequestPermissionsResultListener(OnRequestPermissionsResultListener onRequestPermissionsResultListener) {
this.onRequestPermissionsResultListener = onRequestPermissionsResultListener;
}
public boolean isShowAppInfoDialogEnabled() {
return showAppInfoDialogEnabled;
}
/**
* Enables or disables the App Info dialog. When it is enabled, the App Info dialog will be shown
* if {@link #checkPermission()} is called and the user previously was denied the permission with
* the option "Never ask again".
* The default value for showAppInfoDialogEnabled is true.
*
* @param showAppInfoDialogEnabled true to enable the App Info dialog, false otherwise.
*/
public void setShowAppInfoDialogEnabled(boolean showAppInfoDialogEnabled) {
this.showAppInfoDialogEnabled = showAppInfoDialogEnabled;
}
public boolean isRequestPermissionOnStart() {
return requestPermissionOnStart;
}
/**
* Sets whether the permission to the user will be requested when the activity/fragment starting.
* The default value is false.
*
* @param requestPermissionOnStart true if the permission will be requested when starting, false otherwise
*/
public void setRequestPermissionOnStart(boolean requestPermissionOnStart) {
this.requestPermissionOnStart = requestPermissionOnStart;
}
/**
* This method must be called from the respective method of the fragment/activity when requestPermissionOnStart is true.
*
* @see #setRequestPermissionOnStart(boolean)
*/
public void onCreate(Bundle savedInstanceState) {
firstTime = savedInstanceState==null;
}
/**
* This method must be called from the respective method of the fragment/activity.
*
*/
public void onResume() {
if(requestPermissionOnStart && firstTime) {
checkPermission(false);
firstTime = false;
} else if(pendingAppInfoDialog) {
pendingAppInfoDialog = false;
showAppInfoDialog();
}
}
/**
* This method must be called from the respective method of the fragment/activity.
*/
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == permissionRequestCode) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (onRequestPermissionsResultListener != null) {
onRequestPermissionsResultListener.onRequestPermissionsGranted();
}
} else {
if (onRequestPermissionsResultListener != null) {
onRequestPermissionsResultListener.onRequestPermissionsDenied();
}
if (previouslyShouldShowRequestPermissionRationale != null && !previouslyShouldShowRequestPermissionRationale && !permissionDelegate.shouldShowRequestPermissionRationale(permission)) {
//Note: onRequestPermissionsResult(...) is called immediately before onResume() (like onActivityResult(...)),
// if the dialog is show immediately in this case an exception occurs ("java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState").
pendingAppInfoDialog = true;
}
}
}
}
/**
* Check wheter the app has the permission. If the app does not have the permission, the permission will be request
* to the user using the default Android UI for accepting it. If the user previously was denied this permission
* a dialog with the "Permission Rationale Message" will be showed. By last, if the user previously was denied
* this permission with the option "Never ask again" the AppInfoDialog could be showed.
* After the user has accepted or rejected the requested permission the OnRequestPermissionsResultListener
* will receive a callback.
*
* @return true if the app already has the permission, false otherwise.
*
* @see #setPermissionRationaleMessageResId(int)
* @see #setPermissionRationaleTitleResId(int)
* @see #setAppInfoDialogMessageResId(int)
* @see #setAppInfoDialogTitleResId(int)
* @see #setShowAppInfoDialogEnabled(boolean)
*
* @see #setOnRequestPermissionsResultListener(OnRequestPermissionsResultListener)
*
*/
public boolean checkPermission() {
return checkPermission(showAppInfoDialogEnabled);
}
/**
* Similar to {@link #checkPermission()}, but it allows the AppInfoDialog will be enabled or disabled for this call.
*
* @param showAppInfoDialogEnabled true if the AppInfoDialog is enabled, false to disabled it.
*/
public boolean checkPermission(boolean showAppInfoDialogEnabled) {
boolean hasPermission;
if(permissionRationaleMessageResId != 0) {
hasPermission = checkPermission(permissionDelegate, getPermissionRationaleTitleResId(), permissionRationaleMessageResId, permission, permissionRequestCode);
} else {
hasPermission = checkPermission(permissionDelegate, permission, permissionRequestCode);
}
if(!hasPermission && showAppInfoDialogEnabled) {
previouslyShouldShowRequestPermissionRationale = permissionDelegate.shouldShowRequestPermissionRationale(permission);
} else {
previouslyShouldShowRequestPermissionRationale = null;
}
return hasPermission;
}
/**
* @return true whether you should show UI with rationale for requesting a permission., false otherwise
*/
public boolean shouldShowRequestPermissionRationale() {
return permissionDelegate.shouldShowRequestPermissionRationale(permission);
}
/**
* @return true if the app has the permission, false otherwise
*/
public boolean verifyPermission() {
return verifyPermission(permissionDelegate.getActivity(), permission);
}
private int getAppInfoDialogMessageResId() {
if(appInfoDialogMessageResId != 0) {
return appInfoDialogMessageResId;
}
return permissionRationaleMessageResId;
}
private int getAppInfoDialogTitleResId() {
if(appInfoDialogTitleResId != 0) {
return appInfoDialogTitleResId;
}
return getPermissionRationaleTitleResId();
}
private int getPermissionRationaleTitleResId() {
if(permissionRationaleTitleResId != 0) {
return permissionRationaleTitleResId;
}
return R.string.jdroid_requiredPermission;
}
private void showAppInfoDialog() {
int appInfoDialogMessageResId = getAppInfoDialogMessageResId();
if(appInfoDialogMessageResId != 0) {
AppInfoDialogFragment.show(permissionDelegate.getActivity(), getAppInfoDialogTitleResId(), appInfoDialogMessageResId, permission);
}
}
// Nested classes -------------------------
/**
* Listener for the result from requesting permissions.
* The listener will be called when the fragment/activity onRequestPermissionsResult(...) is called and this can occur immediately before onResume() (like onActivityResult(...)).
*/
public interface OnRequestPermissionsResultListener {
/**
* Called when the permission is granted.
*/
void onRequestPermissionsGranted();
/**
* Called when the permission is denied.
*/
void onRequestPermissionsDenied();
}
protected static abstract class PermissionDelegate {
public abstract FragmentActivity getActivity();
public abstract void requestPermissions(@NonNull String[] permissions, int requestCode);
public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permission);
public abstract void showPermissionDialogFragment(String title, CharSequence message, String permission, int permissionRequestCode);
public int checkSelfPermission(@NonNull String permission) {
return ContextCompat.checkSelfPermission(getActivity(), permission);
}
}
protected static class ActivityPermissionDelegate extends PermissionDelegate {
private FragmentActivity fragmentActivity;
public ActivityPermissionDelegate(FragmentActivity fragmentActivity) {
this.fragmentActivity = fragmentActivity;
}
@Override
public FragmentActivity getActivity() {
return fragmentActivity;
}
@Override
public void requestPermissions(@NonNull String[] permissions, int requestCode) {
ActivityCompat.requestPermissions(fragmentActivity, permissions,requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
return ActivityCompat.shouldShowRequestPermissionRationale(fragmentActivity, permission);
}
@Override
public void showPermissionDialogFragment(String title, CharSequence message, String permission, int permissionRequestCode) {
PermissionDialogFragment.show(fragmentActivity, title, message, permission, permissionRequestCode);
}
}
protected static class FragmentPermissionDelegate extends PermissionDelegate {
private Fragment fragment;
public FragmentPermissionDelegate(Fragment fragment) {
this.fragment = fragment;
}
@Override
public FragmentActivity getActivity() {
return fragment.getActivity();
}
@Override
public void requestPermissions(@NonNull String[] permissions, int requestCode) {
fragment.requestPermissions(permissions, requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
return fragment.shouldShowRequestPermissionRationale(permission);
}
@Override
public void showPermissionDialogFragment(String title, CharSequence message, String permission, int permissionRequestCode) {
PermissionDialogFragment.show(fragment, title, message, permission, permissionRequestCode);
}
}
}