referral click fixed,

update_flutter_3.16.0
Sultan khan 2 months ago
parent c13d7a08a0
commit b8bdee99a6

@ -0,0 +1,52 @@
apply plugin: 'com.android.library'
// See: https://github.com/vanniktech/gradle-maven-publish-plugin/issues/206
ext {
RELEASE_REPOSITORY_URL = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
SNAPSHOT_REPOSITORY_URL = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
}
apply plugin: 'com.vanniktech.maven.publish'
android {
compileSdkVersion 30
testOptions.unitTests.includeAndroidResources = true
defaultConfig {
minSdkVersion 14
targetSdkVersion 30
versionCode 1
versionName "3.0.0"
}
buildTypes {
debug {
testCoverageEnabled true
}
release {
minifyEnabled false
consumerProguardFiles 'proguard-rules.pro'
}
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
api "androidx.appcompat:appcompat:1.1.0"
api "androidx.annotation:annotation:1.1.0"
api "androidx.core:core:1.3.0"
api "androidx.fragment:fragment:1.2.5"
testImplementation 'junit:junit:4.13'
testImplementation 'com.google.truth:truth:0.42'
testImplementation 'org.robolectric:robolectric:4.1'
testImplementation 'androidx.test:core:1.3.0-rc01'
testImplementation 'androidx.fragment:fragment-testing:1.2.5'
testImplementation 'org.mockito:mockito-core:2.23.4'
}

@ -0,0 +1,18 @@
GROUP=pub.devrel
POM_ARTIFACT_ID=easypermissions
VERSION_NAME=3.0.0
POM_NAME=EasyPermissions
POM_PACKAGING=aar
POM_DESCRIPTION=A wrapper library for basic Android M system permissions logic
POM_URL=https://github.com/googlesamples/easypermissions
POM_SCM_URL=https://github.com/googlesamples/easypermissions
POM_SCM_CONNECTION=https://github.com/googlesamples/easypermissions.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_NAME=Google

@ -0,0 +1,3 @@
-keepclassmembers class * {
@pub.devrel.easypermissions.AfterPermissionGranted <methods>;
}

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="pub.devrel.easypermissions">
<application>
<activity
android:name="pub.devrel.easypermissions.AppSettingsDialogHolderActivity"
android:exported="false"
android:label=""
android:theme="@style/EasyPermissions.Transparent"/>
</application>
</manifest>

@ -0,0 +1,29 @@
/*
* Copyright Google Inc. All Rights Reserved.
*
* 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.
*/
package pub.devrel.easypermissions;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterPermissionGranted {
int value();
}

@ -0,0 +1,356 @@
package pub.devrel.easypermissions;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
/**
* Dialog to prompt the user to go to the app's settings screen and enable permissions. If the user
* clicks 'OK' on the dialog, they are sent to the settings screen. The result is returned to the
* Activity via {@see Activity#onActivityResult(int, int, Intent)}.
* <p>
* Use the {@link Builder} to create and display a dialog.
*/
public class AppSettingsDialog implements Parcelable {
private static final String TAG = "EasyPermissions";
public static final int DEFAULT_SETTINGS_REQ_CODE = 16061;
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final Parcelable.Creator<AppSettingsDialog> CREATOR = new Parcelable.Creator<AppSettingsDialog>() {
@Override
public AppSettingsDialog createFromParcel(Parcel in) {
return new AppSettingsDialog(in);
}
@Override
public AppSettingsDialog[] newArray(int size) {
return new AppSettingsDialog[size];
}
};
static final String EXTRA_APP_SETTINGS = "extra_app_settings";
@StyleRes
private final int mThemeResId;
private final String mRationale;
private final String mTitle;
private final String mPositiveButtonText;
private final String mNegativeButtonText;
private final int mRequestCode;
private final int mIntentFlags;
private Object mActivityOrFragment;
private Context mContext;
private AppSettingsDialog(Parcel in) {
mThemeResId = in.readInt();
mRationale = in.readString();
mTitle = in.readString();
mPositiveButtonText = in.readString();
mNegativeButtonText = in.readString();
mRequestCode = in.readInt();
mIntentFlags = in.readInt();
}
private AppSettingsDialog(@NonNull final Object activityOrFragment,
@StyleRes int themeResId,
@Nullable String rationale,
@Nullable String title,
@Nullable String positiveButtonText,
@Nullable String negativeButtonText,
int requestCode,
int intentFlags) {
setActivityOrFragment(activityOrFragment);
mThemeResId = themeResId;
mRationale = rationale;
mTitle = title;
mPositiveButtonText = positiveButtonText;
mNegativeButtonText = negativeButtonText;
mRequestCode = requestCode;
mIntentFlags = intentFlags;
}
static AppSettingsDialog fromIntent(Intent intent, Activity activity) {
AppSettingsDialog dialog = intent.getParcelableExtra(AppSettingsDialog.EXTRA_APP_SETTINGS);
// It's not clear how this could happen, but in the case that it does we should try
// to avoid a runtime crash and just use the default dialog.
// https://github.com/googlesamples/easypermissions/issues/278
if (dialog == null) {
Log.e(TAG, "Intent contains null value for EXTRA_APP_SETTINGS: "
+ "intent=" + intent
+ ", "
+ "extras=" + intent.getExtras());
dialog = new AppSettingsDialog.Builder(activity).build();
}
dialog.setActivityOrFragment(activity);
return dialog;
}
private void setActivityOrFragment(Object activityOrFragment) {
mActivityOrFragment = activityOrFragment;
if (activityOrFragment instanceof Activity) {
mContext = (Activity) activityOrFragment;
} else if (activityOrFragment instanceof Fragment) {
mContext = ((Fragment) activityOrFragment).getContext();
} else {
throw new IllegalStateException("Unknown object: " + activityOrFragment);
}
}
private void startForResult(Intent intent) {
if (mActivityOrFragment instanceof Activity) {
((Activity) mActivityOrFragment).startActivityForResult(intent, mRequestCode);
} else if (mActivityOrFragment instanceof Fragment) {
((Fragment) mActivityOrFragment).startActivityForResult(intent, mRequestCode);
}
}
/**
* Display the built dialog.
*/
public void show() {
startForResult(AppSettingsDialogHolderActivity.createShowDialogIntent(mContext, this));
}
/**
* Show the dialog. {@link #show()} is a wrapper to ensure backwards compatibility
*/
AlertDialog showDialog(DialogInterface.OnClickListener positiveListener,
DialogInterface.OnClickListener negativeListener) {
AlertDialog.Builder builder;
if (mThemeResId != -1) {
builder = new AlertDialog.Builder(mContext, mThemeResId);
} else {
builder = new AlertDialog.Builder(mContext);
}
return builder
.setCancelable(false)
.setTitle(mTitle)
.setMessage(mRationale)
.setPositiveButton(mPositiveButtonText, positiveListener)
.setNegativeButton(mNegativeButtonText, negativeListener)
.show();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mThemeResId);
dest.writeString(mRationale);
dest.writeString(mTitle);
dest.writeString(mPositiveButtonText);
dest.writeString(mNegativeButtonText);
dest.writeInt(mRequestCode);
dest.writeInt(mIntentFlags);
}
int getIntentFlags() {
return mIntentFlags;
}
/**
* Builder for an {@link AppSettingsDialog}.
*/
public static class Builder {
private final Object mActivityOrFragment;
private final Context mContext;
@StyleRes
private int mThemeResId = -1;
private String mRationale;
private String mTitle;
private String mPositiveButtonText;
private String mNegativeButtonText;
private int mRequestCode = -1;
private boolean mOpenInNewTask = false;
/**
* Create a new Builder for an {@link AppSettingsDialog}.
*
* @param activity the {@link Activity} in which to display the dialog.
*/
public Builder(@NonNull Activity activity) {
mActivityOrFragment = activity;
mContext = activity;
}
/**
* Create a new Builder for an {@link AppSettingsDialog}.
*
* @param fragment the {@link Fragment} in which to display the dialog.
*/
public Builder(@NonNull Fragment fragment) {
mActivityOrFragment = fragment;
mContext = fragment.getContext();
}
/**
* Set the dialog theme.
*/
@NonNull
public Builder setThemeResId(@StyleRes int themeResId) {
mThemeResId = themeResId;
return this;
}
/**
* Set the title dialog. Default is "Permissions Required".
*/
@NonNull
public Builder setTitle(@Nullable String title) {
mTitle = title;
return this;
}
/**
* Set the title dialog. Default is "Permissions Required".
*/
@NonNull
public Builder setTitle(@StringRes int title) {
mTitle = mContext.getString(title);
return this;
}
/**
* Set the rationale dialog. Default is
* "This app may not work correctly without the requested permissions.
* Open the app settings screen to modify app permissions."
*/
@NonNull
public Builder setRationale(@Nullable String rationale) {
mRationale = rationale;
return this;
}
/**
* Set the rationale dialog. Default is
* "This app may not work correctly without the requested permissions.
* Open the app settings screen to modify app permissions."
*/
@NonNull
public Builder setRationale(@StringRes int rationale) {
mRationale = mContext.getString(rationale);
return this;
}
/**
* Set the positive button text, default is {@link android.R.string#ok}.
*/
@NonNull
public Builder setPositiveButton(@Nullable String text) {
mPositiveButtonText = text;
return this;
}
/**
* Set the positive button text, default is {@link android.R.string#ok}.
*/
@NonNull
public Builder setPositiveButton(@StringRes int textId) {
mPositiveButtonText = mContext.getString(textId);
return this;
}
/**
* Set the negative button text, default is {@link android.R.string#cancel}.
* <p>
* To know if a user cancelled the request, check if your permissions were given with {@link
* EasyPermissions#hasPermissions(Context, String...)} in {@see
* Activity#onActivityResult(int, int, Intent)}. If you still don't have the right
* permissions, then the request was cancelled.
*/
@NonNull
public Builder setNegativeButton(@Nullable String text) {
mNegativeButtonText = text;
return this;
}
/**
* Set the negative button text, default is {@link android.R.string#cancel}.
*/
@NonNull
public Builder setNegativeButton(@StringRes int textId) {
mNegativeButtonText = mContext.getString(textId);
return this;
}
/**
* Set the request code use when launching the Settings screen for result, can be retrieved
* in the calling Activity's {@see Activity#onActivityResult(int, int, Intent)} method.
* Default is {@link #DEFAULT_SETTINGS_REQ_CODE}.
*/
@NonNull
public Builder setRequestCode(int requestCode) {
mRequestCode = requestCode;
return this;
}
/**
* Set whether the settings screen should be opened in a separate task. This is achieved by
* setting {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK#FLAG_ACTIVITY_NEW_TASK} on
* the Intent used to open the settings screen.
*/
@NonNull
public Builder setOpenInNewTask(boolean openInNewTask) {
mOpenInNewTask = openInNewTask;
return this;
}
/**
* Build the {@link AppSettingsDialog} from the specified options. Generally followed by a
* call to {@link AppSettingsDialog#show()}.
*/
@NonNull
public AppSettingsDialog build() {
mRationale = TextUtils.isEmpty(mRationale) ?
mContext.getString(R.string.rationale_ask_again) : mRationale;
mTitle = TextUtils.isEmpty(mTitle) ?
mContext.getString(R.string.title_settings_dialog) : mTitle;
mPositiveButtonText = TextUtils.isEmpty(mPositiveButtonText) ?
mContext.getString(android.R.string.ok) : mPositiveButtonText;
mNegativeButtonText = TextUtils.isEmpty(mNegativeButtonText) ?
mContext.getString(android.R.string.cancel) : mNegativeButtonText;
mRequestCode = mRequestCode > 0 ? mRequestCode : DEFAULT_SETTINGS_REQ_CODE;
int intentFlags = 0;
if (mOpenInNewTask) {
intentFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
return new AppSettingsDialog(
mActivityOrFragment,
mThemeResId,
mRationale,
mTitle,
mPositiveButtonText,
mNegativeButtonText,
mRequestCode,
intentFlags);
}
}
}

@ -0,0 +1,65 @@
package pub.devrel.easypermissions;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import androidx.annotation.RestrictTo;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class AppSettingsDialogHolderActivity extends AppCompatActivity implements DialogInterface.OnClickListener {
private static final int APP_SETTINGS_RC = 7534;
private AlertDialog mDialog;
private int mIntentFlags;
public static Intent createShowDialogIntent(Context context, AppSettingsDialog dialog) {
Intent intent = new Intent(context, AppSettingsDialogHolderActivity.class);
intent.putExtra(AppSettingsDialog.EXTRA_APP_SETTINGS, dialog);
return intent;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AppSettingsDialog appSettingsDialog = AppSettingsDialog.fromIntent(getIntent(), this);
mIntentFlags = appSettingsDialog.getIntentFlags();
mDialog = appSettingsDialog.showDialog(this, this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == Dialog.BUTTON_POSITIVE) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", getPackageName(), null));
intent.addFlags(mIntentFlags);
startActivityForResult(intent, APP_SETTINGS_RC);
} else if (which == Dialog.BUTTON_NEGATIVE) {
setResult(Activity.RESULT_CANCELED);
finish();
} else {
throw new IllegalStateException("Unknown button type: " + which);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
setResult(resultCode, data);
finish();
}
}

@ -0,0 +1,358 @@
/*
* Copyright Google Inc. All Rights Reserved.
*
* 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.
*/
package pub.devrel.easypermissions;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Size;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.core.content.ContextCompat;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import pub.devrel.easypermissions.helper.PermissionHelper;
/**
* Utility to request and check System permissions for apps targeting Android M (API &gt;= 23).
*/
public class EasyPermissions {
/**
* Callback interface to receive the results of {@code EasyPermissions.requestPermissions()}
* calls.
*/
public interface PermissionCallbacks extends ActivityCompat.OnRequestPermissionsResultCallback {
void onPermissionsGranted(int requestCode, @NonNull List<String> perms);
void onPermissionsDenied(int requestCode, @NonNull List<String> perms);
}
/**
* Callback interface to receive button clicked events of the rationale dialog
*/
public interface RationaleCallbacks {
void onRationaleAccepted(int requestCode);
void onRationaleDenied(int requestCode);
}
private static final String TAG = "EasyPermissions";
/**
* Check if the calling context has a set of permissions.
*
* @param context the calling context.
* @param perms one ore more permissions, such as {@link Manifest.permission#CAMERA}.
* @return true if all permissions are already granted, false if at least one permission is not
* yet granted.
* @see Manifest.permission
*/
public static boolean hasPermissions(@NonNull Context context,
@Size(min = 1) @NonNull String... perms) {
// Always return true for SDK < M, let the system deal with the permissions
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Log.w(TAG, "hasPermissions: API version < M, returning true by default");
// DANGER ZONE!!! Changing this will break the library.
return true;
}
// Null context may be passed if we have detected Low API (less than M) so getting
// to this point with a null context should not be possible.
if (context == null) {
throw new IllegalArgumentException("Can't check permissions for null context");
}
for (String perm : perms) {
if (ContextCompat.checkSelfPermission(context, perm)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* Request a set of permissions, showing a rationale if the system requests it.
*
* @param host requesting context.
* @param rationale a message explaining why the application needs this set of permissions;
* will be displayed if the user rejects the request the first time.
* @param requestCode request code to track this request, must be &lt; 256.
* @param perms a set of permissions to be requested.
* @see Manifest.permission
*/
public static void requestPermissions(
@NonNull Activity host, @NonNull String rationale,
@IntRange(from = 0, to = 255) int requestCode, @Size(min = 1) @NonNull String... perms) {
requestPermissions(
new PermissionRequest.Builder(host, requestCode, perms)
.setRationale(rationale)
.build());
}
/**
* Request permissions from a Support Fragment with standard OK/Cancel buttons.
*
* @see #requestPermissions(Activity, String, int, String...)
*/
public static void requestPermissions(
@NonNull Fragment host, @NonNull String rationale,
@IntRange(from = 0, to = 255) int requestCode, @Size(min = 1) @NonNull String... perms) {
requestPermissions(
new PermissionRequest.Builder(host, requestCode, perms)
.setRationale(rationale)
.build());
}
/**
* Request a set of permissions.
*
* @param request the permission request
* @see PermissionRequest
*/
public static void requestPermissions(PermissionRequest request) {
// Check for permissions before dispatching the request
if (hasPermissions(request.getHelper().getContext(), request.getPerms())) {
notifyAlreadyHasPermissions(
request.getHelper().getHost(), request.getRequestCode(), request.getPerms());
return;
}
// Request permissions
request.getHelper().requestPermissions(
request.getRationale(),
request.getPositiveButtonText(),
request.getNegativeButtonText(),
request.getTheme(),
request.getRequestCode(),
request.getPerms());
}
/**
* Handle the result of a permission request, should be called from the calling {@link
* Activity}'s {@link ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int,
* String[], int[])} method.
* <p>
* If any permissions were granted or denied, the {@code object} will receive the appropriate
* callbacks through {@link PermissionCallbacks} and methods annotated with {@link
* AfterPermissionGranted} will be run if appropriate.
*
* @param requestCode requestCode argument to permission result callback.
* @param permissions permissions argument to permission result callback.
* @param grantResults grantResults argument to permission result callback.
* @param receivers an array of objects that have a method annotated with {@link
* AfterPermissionGranted} or implement {@link PermissionCallbacks}.
*/
public static void onRequestPermissionsResult(@IntRange(from = 0, to = 255) int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults,
@NonNull Object... receivers) {
// Make a collection of granted and denied permissions from the request.
List<String> granted = new ArrayList<>();
List<String> denied = new ArrayList<>();
for (int i = 0; i < permissions.length; i++) {
String perm = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
granted.add(perm);
} else {
denied.add(perm);
}
}
// iterate through all receivers
for (Object object : receivers) {
// Report granted permissions, if any.
if (!granted.isEmpty()) {
if (object instanceof PermissionCallbacks) {
((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted);
}
}
// Report denied permissions, if any.
if (!denied.isEmpty()) {
if (object instanceof PermissionCallbacks) {
((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied);
}
}
// If 100% successful, call annotated methods
if (!granted.isEmpty() && denied.isEmpty()) {
runAnnotatedMethods(object, requestCode);
}
}
}
/**
* Check if at least one permission in the list of denied permissions has been permanently
* denied (user clicked "Never ask again").
*
* <b>Note</b>: Due to a limitation in the information provided by the Android
* framework permissions API, this method only works after the permission
* has been denied and your app has received the onPermissionsDenied callback.
* Otherwise the library cannot distinguish permanent denial from the
* "not yet denied" case.
*
* @param host context requesting permissions.
* @param deniedPermissions list of denied permissions, usually from {@link
* PermissionCallbacks#onPermissionsDenied(int, List)}
* @return {@code true} if at least one permission in the list was permanently denied.
*/
public static boolean somePermissionPermanentlyDenied(@NonNull Activity host,
@NonNull List<String> deniedPermissions) {
return PermissionHelper.newInstance(host)
.somePermissionPermanentlyDenied(deniedPermissions);
}
/**
* @see #somePermissionPermanentlyDenied(Activity, List)
*/
public static boolean somePermissionPermanentlyDenied(@NonNull Fragment host,
@NonNull List<String> deniedPermissions) {
return PermissionHelper.newInstance(host)
.somePermissionPermanentlyDenied(deniedPermissions);
}
/**
* Check if a permission has been permanently denied (user clicked "Never ask again").
*
* @param host context requesting permissions.
* @param deniedPermission denied permission.
* @return {@code true} if the permissions has been permanently denied.
*/
public static boolean permissionPermanentlyDenied(@NonNull Activity host,
@NonNull String deniedPermission) {
return PermissionHelper.newInstance(host).permissionPermanentlyDenied(deniedPermission);
}
/**
* @see #permissionPermanentlyDenied(Activity, String)
*/
public static boolean permissionPermanentlyDenied(@NonNull Fragment host,
@NonNull String deniedPermission) {
return PermissionHelper.newInstance(host).permissionPermanentlyDenied(deniedPermission);
}
/**
* See if some denied permission has been permanently denied.
*
* @param host requesting context.
* @param perms array of permissions.
* @return true if the user has previously denied any of the {@code perms} and we should show a
* rationale, false otherwise.
*/
public static boolean somePermissionDenied(@NonNull Activity host,
@NonNull String... perms) {
return PermissionHelper.newInstance(host).somePermissionDenied(perms);
}
/**
* @see #somePermissionDenied(Activity, String...)
*/
public static boolean somePermissionDenied(@NonNull Fragment host,
@NonNull String... perms) {
return PermissionHelper.newInstance(host).somePermissionDenied(perms);
}
/**
* Run permission callbacks on an object that requested permissions but already has them by
* simulating {@link PackageManager#PERMISSION_GRANTED}.
*
* @param object the object requesting permissions.
* @param requestCode the permission request code.
* @param perms a list of permissions requested.
*/
private static void notifyAlreadyHasPermissions(@NonNull Object object,
int requestCode,
@NonNull String[] perms) {
int[] grantResults = new int[perms.length];
for (int i = 0; i < perms.length; i++) {
grantResults[i] = PackageManager.PERMISSION_GRANTED;
}
onRequestPermissionsResult(requestCode, perms, grantResults, object);
}
/**
* Find all methods annotated with {@link AfterPermissionGranted} on a given object with the
* correct requestCode argument.
*
* @param object the object with annotated methods.
* @param requestCode the requestCode passed to the annotation.
*/
private static void runAnnotatedMethods(@NonNull Object object, int requestCode) {
Class clazz = object.getClass();
if (isUsingAndroidAnnotations(object)) {
clazz = clazz.getSuperclass();
}
while (clazz != null) {
for (Method method : clazz.getDeclaredMethods()) {
AfterPermissionGranted ann = method.getAnnotation(AfterPermissionGranted.class);
if (ann != null) {
// Check for annotated methods with matching request code.
if (ann.value() == requestCode) {
// Method must be void so that we can invoke it
if (method.getParameterTypes().length > 0) {
throw new RuntimeException(
"Cannot execute method " + method.getName() + " because it is non-void method and/or has input parameters.");
}
try {
// Make method accessible if private
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(object);
} catch (IllegalAccessException e) {
Log.e(TAG, "runDefaultMethod:IllegalAccessException", e);
} catch (InvocationTargetException e) {
Log.e(TAG, "runDefaultMethod:InvocationTargetException", e);
}
}
}
}
clazz = clazz.getSuperclass();
}
}
/**
* Determine if the project is using the AndroidAnnotations library.
*/
private static boolean isUsingAndroidAnnotations(@NonNull Object object) {
if (!object.getClass().getSimpleName().endsWith("_")) {
return false;
}
try {
Class clazz = Class.forName("org.androidannotations.api.view.HasViews");
return clazz.isInstance(object);
} catch (ClassNotFoundException e) {
return false;
}
}
}

@ -0,0 +1,260 @@
package pub.devrel.easypermissions;
import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.Size;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
import java.util.Arrays;
import pub.devrel.easypermissions.helper.PermissionHelper;
/**
* An immutable model object that holds all of the parameters associated with a permission request,
* such as the permissions, request code, and rationale.
*
* @see EasyPermissions#requestPermissions(PermissionRequest)
* @see PermissionRequest.Builder
*/
public final class PermissionRequest {
private final PermissionHelper mHelper;
private final String[] mPerms;
private final int mRequestCode;
private final String mRationale;
private final String mPositiveButtonText;
private final String mNegativeButtonText;
private final int mTheme;
private PermissionRequest(PermissionHelper helper,
String[] perms,
int requestCode,
String rationale,
String positiveButtonText,
String negativeButtonText,
int theme) {
mHelper = helper;
mPerms = perms.clone();
mRequestCode = requestCode;
mRationale = rationale;
mPositiveButtonText = positiveButtonText;
mNegativeButtonText = negativeButtonText;
mTheme = theme;
}
@NonNull
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public PermissionHelper getHelper() {
return mHelper;
}
@NonNull
public String[] getPerms() {
return mPerms.clone();
}
public int getRequestCode() {
return mRequestCode;
}
@NonNull
public String getRationale() {
return mRationale;
}
@NonNull
public String getPositiveButtonText() {
return mPositiveButtonText;
}
@NonNull
public String getNegativeButtonText() {
return mNegativeButtonText;
}
@StyleRes
public int getTheme() {
return mTheme;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PermissionRequest request = (PermissionRequest) o;
return Arrays.equals(mPerms, request.mPerms) && mRequestCode == request.mRequestCode;
}
@Override
public int hashCode() {
int result = Arrays.hashCode(mPerms);
result = 31 * result + mRequestCode;
return result;
}
@Override
public String toString() {
return "PermissionRequest{" +
"mHelper=" + mHelper +
", mPerms=" + Arrays.toString(mPerms) +
", mRequestCode=" + mRequestCode +
", mRationale='" + mRationale + '\'' +
", mPositiveButtonText='" + mPositiveButtonText + '\'' +
", mNegativeButtonText='" + mNegativeButtonText + '\'' +
", mTheme=" + mTheme +
'}';
}
/**
* Builder to build a permission request with variable options.
*
* @see PermissionRequest
*/
public static final class Builder {
private final PermissionHelper mHelper;
private final int mRequestCode;
private final String[] mPerms;
private String mRationale;
private String mPositiveButtonText;
private String mNegativeButtonText;
private int mTheme = -1;
/**
* Construct a new permission request builder with a host, request code, and the requested
* permissions.
*
* @param activity the permission request host
* @param requestCode request code to track this request; must be &lt; 256
* @param perms the set of permissions to be requested
*/
public Builder(@NonNull Activity activity, int requestCode,
@NonNull @Size(min = 1) String... perms) {
mHelper = PermissionHelper.newInstance(activity);
mRequestCode = requestCode;
mPerms = perms;
}
/**
* @see #Builder(Activity, int, String...)
*/
public Builder(@NonNull Fragment fragment, int requestCode,
@NonNull @Size(min = 1) String... perms) {
mHelper = PermissionHelper.newInstance(fragment);
mRequestCode = requestCode;
mPerms = perms;
}
/**
* Set the rationale to display to the user if they don't allow your permissions on the
* first try. This rationale will be shown as long as the user has denied your permissions
* at least once, but has not yet permanently denied your permissions. Should the user
* permanently deny your permissions, use the {@link AppSettingsDialog} instead.
* <p>
* The default rationale text is {@link R.string#rationale_ask}.
*
* @param rationale the rationale to be displayed to the user should they deny your
* permission at least once
*/
@NonNull
public Builder setRationale(@Nullable String rationale) {
mRationale = rationale;
return this;
}
/**
* @param resId the string resource to be used as a rationale
* @see #setRationale(String)
*/
@NonNull
public Builder setRationale(@StringRes int resId) {
mRationale = mHelper.getContext().getString(resId);
return this;
}
/**
* Set the positive button text for the rationale dialog should it be shown.
* <p>
* The default is {@link android.R.string#ok}
*/
@NonNull
public Builder setPositiveButtonText(@Nullable String positiveButtonText) {
mPositiveButtonText = positiveButtonText;
return this;
}
/**
* @see #setPositiveButtonText(String)
*/
@NonNull
public Builder setPositiveButtonText(@StringRes int resId) {
mPositiveButtonText = mHelper.getContext().getString(resId);
return this;
}
/**
* Set the negative button text for the rationale dialog should it be shown.
* <p>
* The default is {@link android.R.string#cancel}
*/
@NonNull
public Builder setNegativeButtonText(@Nullable String negativeButtonText) {
mNegativeButtonText = negativeButtonText;
return this;
}
/**
* @see #setNegativeButtonText(String)
*/
@NonNull
public Builder setNegativeButtonText(@StringRes int resId) {
mNegativeButtonText = mHelper.getContext().getString(resId);
return this;
}
/**
* Set the theme to be used for the rationale dialog should it be shown.
*
* @param theme a style resource
*/
@NonNull
public Builder setTheme(@StyleRes int theme) {
mTheme = theme;
return this;
}
/**
* Build the permission request.
*
* @return the permission request
* @see EasyPermissions#requestPermissions(PermissionRequest)
* @see PermissionRequest
*/
@NonNull
public PermissionRequest build() {
if (mRationale == null) {
mRationale = mHelper.getContext().getString(R.string.rationale_ask);
}
if (mPositiveButtonText == null) {
mPositiveButtonText = mHelper.getContext().getString(android.R.string.ok);
}
if (mNegativeButtonText == null) {
mNegativeButtonText = mHelper.getContext().getString(android.R.string.cancel);
}
return new PermissionRequest(
mHelper,
mPerms,
mRequestCode,
mRationale,
mPositiveButtonText,
mNegativeButtonText,
mTheme);
}
}
}

@ -0,0 +1,77 @@
package pub.devrel.easypermissions;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import androidx.fragment.app.Fragment;
import java.util.Arrays;
import pub.devrel.easypermissions.helper.PermissionHelper;
/**
* Click listener for either {@link RationaleDialogFragment} or {@link RationaleDialogFragmentCompat}.
*/
class RationaleDialogClickListener implements Dialog.OnClickListener {
private Object mHost;
private RationaleDialogConfig mConfig;
private EasyPermissions.PermissionCallbacks mCallbacks;
private EasyPermissions.RationaleCallbacks mRationaleCallbacks;
RationaleDialogClickListener(RationaleDialogFragmentCompat compatDialogFragment,
RationaleDialogConfig config,
EasyPermissions.PermissionCallbacks callbacks,
EasyPermissions.RationaleCallbacks rationaleCallbacks) {
mHost = compatDialogFragment.getParentFragment() != null
? compatDialogFragment.getParentFragment()
: compatDialogFragment.getActivity();
mConfig = config;
mCallbacks = callbacks;
mRationaleCallbacks = rationaleCallbacks;
}
RationaleDialogClickListener(RationaleDialogFragment dialogFragment,
RationaleDialogConfig config,
EasyPermissions.PermissionCallbacks callbacks,
EasyPermissions.RationaleCallbacks dialogCallback) {
mHost = dialogFragment.getActivity();
mConfig = config;
mCallbacks = callbacks;
mRationaleCallbacks = dialogCallback;
}
@Override
public void onClick(DialogInterface dialog, int which) {
int requestCode = mConfig.requestCode;
if (which == Dialog.BUTTON_POSITIVE) {
String[] permissions = mConfig.permissions;
if (mRationaleCallbacks != null) {
mRationaleCallbacks.onRationaleAccepted(requestCode);
}
if (mHost instanceof Fragment) {
PermissionHelper.newInstance((Fragment) mHost).directRequestPermissions(requestCode, permissions);
} else if (mHost instanceof Activity) {
PermissionHelper.newInstance((Activity) mHost).directRequestPermissions(requestCode, permissions);
} else {
throw new RuntimeException("Host must be an Activity or Fragment!");
}
} else {
if (mRationaleCallbacks != null) {
mRationaleCallbacks.onRationaleDenied(requestCode);
}
notifyPermissionDenied();
}
}
private void notifyPermissionDenied() {
if (mCallbacks != null) {
mCallbacks.onPermissionsDenied(mConfig.requestCode, Arrays.asList(mConfig.permissions));
}
}
}

@ -0,0 +1,95 @@
package pub.devrel.easypermissions;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
/**
* Configuration for either {@link RationaleDialogFragment} or {@link RationaleDialogFragmentCompat}.
*/
class RationaleDialogConfig {
private static final String KEY_POSITIVE_BUTTON = "positiveButton";
private static final String KEY_NEGATIVE_BUTTON = "negativeButton";
private static final String KEY_RATIONALE_MESSAGE = "rationaleMsg";
private static final String KEY_THEME = "theme";
private static final String KEY_REQUEST_CODE = "requestCode";
private static final String KEY_PERMISSIONS = "permissions";
String positiveButton;
String negativeButton;
int theme;
int requestCode;
String rationaleMsg;
String[] permissions;
RationaleDialogConfig(@NonNull String positiveButton,
@NonNull String negativeButton,
@NonNull String rationaleMsg,
@StyleRes int theme,
int requestCode,
@NonNull String[] permissions) {
this.positiveButton = positiveButton;
this.negativeButton = negativeButton;
this.rationaleMsg = rationaleMsg;
this.theme = theme;
this.requestCode = requestCode;
this.permissions = permissions;
}
RationaleDialogConfig(Bundle bundle) {
positiveButton = bundle.getString(KEY_POSITIVE_BUTTON);
negativeButton = bundle.getString(KEY_NEGATIVE_BUTTON);
rationaleMsg = bundle.getString(KEY_RATIONALE_MESSAGE);
theme = bundle.getInt(KEY_THEME);
requestCode = bundle.getInt(KEY_REQUEST_CODE);
permissions = bundle.getStringArray(KEY_PERMISSIONS);
}
Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putString(KEY_POSITIVE_BUTTON, positiveButton);
bundle.putString(KEY_NEGATIVE_BUTTON, negativeButton);
bundle.putString(KEY_RATIONALE_MESSAGE, rationaleMsg);
bundle.putInt(KEY_THEME, theme);
bundle.putInt(KEY_REQUEST_CODE, requestCode);
bundle.putStringArray(KEY_PERMISSIONS, permissions);
return bundle;
}
AlertDialog createSupportDialog(Context context, Dialog.OnClickListener listener) {
AlertDialog.Builder builder;
if (theme > 0) {
builder = new AlertDialog.Builder(context, theme);
} else {
builder = new AlertDialog.Builder(context);
}
return builder
.setCancelable(false)
.setPositiveButton(positiveButton, listener)
.setNegativeButton(negativeButton, listener)
.setMessage(rationaleMsg)
.create();
}
android.app.AlertDialog createFrameworkDialog(Context context, Dialog.OnClickListener listener) {
android.app.AlertDialog.Builder builder;
if (theme > 0) {
builder = new android.app.AlertDialog.Builder(context, theme);
} else {
builder = new android.app.AlertDialog.Builder(context);
}
return builder
.setCancelable(false)
.setPositiveButton(positiveButton, listener)
.setNegativeButton(negativeButton, listener)
.setMessage(rationaleMsg)
.create();
}
}

@ -0,0 +1,113 @@
package pub.devrel.easypermissions;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
/**
* {@link DialogFragment} to display rationale for permission requests when the request comes from
* a Fragment or Activity that can host a Fragment.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class RationaleDialogFragment extends DialogFragment {
public static final String TAG = "RationaleDialogFragment";
private EasyPermissions.PermissionCallbacks mPermissionCallbacks;
private EasyPermissions.RationaleCallbacks mRationaleCallbacks;
private boolean mStateSaved = false;
public static RationaleDialogFragment newInstance(
@NonNull String positiveButton,
@NonNull String negativeButton,
@NonNull String rationaleMsg,
@StyleRes int theme,
int requestCode,
@NonNull String[] permissions) {
// Create new Fragment
RationaleDialogFragment dialogFragment = new RationaleDialogFragment();
// Initialize configuration as arguments
RationaleDialogConfig config = new RationaleDialogConfig(
positiveButton, negativeButton, rationaleMsg, theme, requestCode, permissions);
dialogFragment.setArguments(config.toBundle());
return dialogFragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && getParentFragment() != null) {
if (getParentFragment() instanceof EasyPermissions.PermissionCallbacks) {
mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) getParentFragment();
}
if (getParentFragment() instanceof EasyPermissions.RationaleCallbacks){
mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) getParentFragment();
}
}
if (context instanceof EasyPermissions.PermissionCallbacks) {
mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) context;
}
if (context instanceof EasyPermissions.RationaleCallbacks) {
mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) context;
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
mStateSaved = true;
super.onSaveInstanceState(outState);
}
/**
* Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
* would otherwise occur.
*/
public void showAllowingStateLoss(FragmentManager manager, String tag) {
// API 26 added this convenient method
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (manager.isStateSaved()) {
return;
}
}
if (mStateSaved) {
return;
}
show(manager, tag);
}
@Override
public void onDetach() {
super.onDetach();
mPermissionCallbacks = null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Rationale dialog should not be cancelable
setCancelable(false);
// Get config from arguments, create click listener
RationaleDialogConfig config = new RationaleDialogConfig(getArguments());
RationaleDialogClickListener clickListener =
new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks);
// Create an AlertDialog
return config.createFrameworkDialog(getActivity(), clickListener);
}
}

@ -0,0 +1,97 @@
package pub.devrel.easypermissions;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
import androidx.fragment.app.FragmentManager;
import androidx.appcompat.app.AppCompatDialogFragment;
/**
* {@link AppCompatDialogFragment} to display rationale for permission requests when the request
* comes from a Fragment or Activity that can host a Fragment.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class RationaleDialogFragmentCompat extends AppCompatDialogFragment {
public static final String TAG = "RationaleDialogFragmentCompat";
private EasyPermissions.PermissionCallbacks mPermissionCallbacks;
private EasyPermissions.RationaleCallbacks mRationaleCallbacks;
public static RationaleDialogFragmentCompat newInstance(
@NonNull String rationaleMsg,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String[] permissions) {
// Create new Fragment
RationaleDialogFragmentCompat dialogFragment = new RationaleDialogFragmentCompat();
// Initialize configuration as arguments
RationaleDialogConfig config = new RationaleDialogConfig(
positiveButton, negativeButton, rationaleMsg, theme, requestCode, permissions);
dialogFragment.setArguments(config.toBundle());
return dialogFragment;
}
/**
* Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
* would otherwise occur.
*/
public void showAllowingStateLoss(FragmentManager manager, String tag) {
if (manager.isStateSaved()) {
return;
}
show(manager, tag);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (getParentFragment() != null) {
if (getParentFragment() instanceof EasyPermissions.PermissionCallbacks) {
mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) getParentFragment();
}
if (getParentFragment() instanceof EasyPermissions.RationaleCallbacks){
mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) getParentFragment();
}
}
if (context instanceof EasyPermissions.PermissionCallbacks) {
mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) context;
}
if (context instanceof EasyPermissions.RationaleCallbacks) {
mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) context;
}
}
@Override
public void onDetach() {
super.onDetach();
mPermissionCallbacks = null;
mRationaleCallbacks = null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Rationale dialog should not be cancelable
setCancelable(false);
// Get config from arguments, create click listener
RationaleDialogConfig config = new RationaleDialogConfig(getArguments());
RationaleDialogClickListener clickListener =
new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks);
// Create an AlertDialog
return config.createSupportDialog(getContext(), clickListener);
}
}

@ -0,0 +1,59 @@
package pub.devrel.easypermissions.helper;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.core.app.ActivityCompat;
import android.util.Log;
import pub.devrel.easypermissions.RationaleDialogFragment;
/**
* Permissions helper for {@link Activity}.
*/
class ActivityPermissionHelper extends PermissionHelper<Activity> {
private static final String TAG = "ActPermissionHelper";
public ActivityPermissionHelper(Activity host) {
super(host);
}
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
ActivityCompat.requestPermissions(getHost(), perms, requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
}
@Override
public Context getContext() {
return getHost();
}
@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
FragmentManager fm = getHost().getFragmentManager();
// Check if fragment is already showing
Fragment fragment = fm.findFragmentByTag(RationaleDialogFragment.TAG);
if (fragment instanceof RationaleDialogFragment) {
Log.d(TAG, "Found existing fragment, not showing rationale.");
return;
}
RationaleDialogFragment
.newInstance(positiveButton, negativeButton, rationale, theme, requestCode, perms)
.showAllowingStateLoss(fm, RationaleDialogFragment.TAG);
}
}

@ -0,0 +1,37 @@
package pub.devrel.easypermissions.helper;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentManager;
import androidx.appcompat.app.AppCompatActivity;
/**
* Permissions helper for {@link AppCompatActivity}.
*/
class AppCompatActivityPermissionsHelper extends BaseSupportPermissionsHelper<AppCompatActivity> {
public AppCompatActivityPermissionsHelper(AppCompatActivity host) {
super(host);
}
@Override
public FragmentManager getSupportFragmentManager() {
return getHost().getSupportFragmentManager();
}
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
ActivityCompat.requestPermissions(getHost(), perms, requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
}
@Override
public Context getContext() {
return getHost();
}
}

@ -0,0 +1,45 @@
package pub.devrel.easypermissions.helper;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import android.util.Log;
import pub.devrel.easypermissions.RationaleDialogFragmentCompat;
/**
* Implementation of {@link PermissionHelper} for Support Library host classes.
*/
public abstract class BaseSupportPermissionsHelper<T> extends PermissionHelper<T> {
private static final String TAG = "BSPermissionsHelper";
public BaseSupportPermissionsHelper(@NonNull T host) {
super(host);
}
public abstract FragmentManager getSupportFragmentManager();
@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
FragmentManager fm = getSupportFragmentManager();
// Check if fragment is already showing
Fragment fragment = fm.findFragmentByTag(RationaleDialogFragmentCompat.TAG);
if (fragment instanceof RationaleDialogFragmentCompat) {
Log.d(TAG, "Found existing fragment, not showing rationale.");
return;
}
RationaleDialogFragmentCompat
.newInstance(rationale, positiveButton, negativeButton, theme, requestCode, perms)
.showAllowingStateLoss(fm, RationaleDialogFragmentCompat.TAG);
}
}

@ -0,0 +1,47 @@
package pub.devrel.easypermissions.helper;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
/**
* Permissions helper for apps built against API < 23, which do not need runtime permissions.
*/
class LowApiPermissionsHelper<T> extends PermissionHelper<T> {
public LowApiPermissionsHelper(@NonNull T host) {
super(host);
}
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
throw new IllegalStateException("Should never be requesting permissions on API < 23!");
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return false;
}
@Override
public void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
throw new IllegalStateException("Should never be requesting permissions on API < 23!");
}
@Override
public Context getContext() {
if (getHost() instanceof Activity) {
return (Context) getHost();
} else if (getHost() instanceof Fragment) {
return ((Fragment) getHost()).getContext();
} else {
throw new IllegalStateException("Unknown host: " + getHost());
}
}
}

@ -0,0 +1,113 @@
package pub.devrel.easypermissions.helper;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
import androidx.appcompat.app.AppCompatActivity;
import java.util.List;
/**
* Delegate class to make permission calls based on the 'host' (Fragment, Activity, etc).
*/
public abstract class PermissionHelper<T> {
private T mHost;
@NonNull
public static PermissionHelper<? extends Activity> newInstance(Activity host) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return new LowApiPermissionsHelper<>(host);
}
if (host instanceof AppCompatActivity)
return new AppCompatActivityPermissionsHelper((AppCompatActivity) host);
else {
return new ActivityPermissionHelper(host);
}
}
@NonNull
public static PermissionHelper<Fragment> newInstance(Fragment host) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return new LowApiPermissionsHelper<>(host);
}
return new SupportFragmentPermissionHelper(host);
}
// ============================================================================
// Public concrete methods
// ============================================================================
public PermissionHelper(@NonNull T host) {
mHost = host;
}
private boolean shouldShowRationale(@NonNull String... perms) {
for (String perm : perms) {
if (shouldShowRequestPermissionRationale(perm)) {
return true;
}
}
return false;
}
public void requestPermissions(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms) {
if (shouldShowRationale(perms)) {
showRequestPermissionRationale(
rationale, positiveButton, negativeButton, theme, requestCode, perms);
} else {
directRequestPermissions(requestCode, perms);
}
}
public boolean somePermissionPermanentlyDenied(@NonNull List<String> perms) {
for (String deniedPermission : perms) {
if (permissionPermanentlyDenied(deniedPermission)) {
return true;
}
}
return false;
}
public boolean permissionPermanentlyDenied(@NonNull String perms) {
return !shouldShowRequestPermissionRationale(perms);
}
public boolean somePermissionDenied(@NonNull String... perms) {
return shouldShowRationale(perms);
}
@NonNull
public T getHost() {
return mHost;
}
// ============================================================================
// Public abstract methods
// ============================================================================
public abstract void directRequestPermissions(int requestCode, @NonNull String... perms);
public abstract boolean shouldShowRequestPermissionRationale(@NonNull String perm);
public abstract void showRequestPermissionRationale(@NonNull String rationale,
@NonNull String positiveButton,
@NonNull String negativeButton,
@StyleRes int theme,
int requestCode,
@NonNull String... perms);
public abstract Context getContext();
}

@ -0,0 +1,36 @@
package pub.devrel.easypermissions.helper;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
/**
* Permissions helper for {@link Fragment} from the support library.
*/
class SupportFragmentPermissionHelper extends BaseSupportPermissionsHelper<Fragment> {
public SupportFragmentPermissionHelper(@NonNull Fragment host) {
super(host);
}
@Override
public FragmentManager getSupportFragmentManager() {
return getHost().getChildFragmentManager();
}
@Override
public void directRequestPermissions(int requestCode, @NonNull String... perms) {
getHost().requestPermissions(perms, requestCode);
}
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
return getHost().shouldShowRequestPermissionRationale(perm);
}
@Override
public Context getContext() {
return getHost().getActivity();
}
}

@ -0,0 +1,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package pub.devrel.easypermissions.helper;
import androidx.annotation.RestrictTo;

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Use system defaults for devs not using AppCompat -->
<color name="colorPrimary">#ff212121</color>
<color name="colorPrimaryDark">@android:color/black</color>
<color name="colorAccent">#ff80cbc4</color>
</resources>

@ -0,0 +1,8 @@
<resources>
<string name="rationale_ask">This app may not work correctly without the requested permissions.</string>
<string name="rationale_ask_again">
This app may not work correctly without the requested permissions.
Open the app settings screen to modify app permissions.
</string>
<string name="title_settings_dialog">Permissions Required</string>
</resources>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="EasyPermissions" parent="Theme.AppCompat.DayNight.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="EasyPermissions.Transparent">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>

@ -0,0 +1,186 @@
package pub.devrel.easypermissions;
import android.app.Application;
import android.content.DialogInterface;
import android.content.Intent;
import android.widget.Button;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowIntent;
import java.util.Objects;
import androidx.appcompat.app.AlertDialog;
import androidx.test.core.app.ApplicationProvider;
import pub.devrel.easypermissions.testhelper.ActivityController;
import pub.devrel.easypermissions.testhelper.FragmentController;
import pub.devrel.easypermissions.testhelper.TestActivity;
import pub.devrel.easypermissions.testhelper.TestFragment;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
import static pub.devrel.easypermissions.AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class AppSettingsDialogTest {
private static final String TITLE = "TITLE";
private static final String RATIONALE = "RATIONALE";
private static final String NEGATIVE = "NEGATIVE";
private static final String POSITIVE = "POSITIVE";
private ShadowApplication shadowApp;
private TestActivity spyActivity;
private TestFragment spyFragment;
private FragmentController<TestFragment> fragmentController;
private ActivityController<TestActivity> activityController;
@Mock
private DialogInterface.OnClickListener positiveListener;
@Mock
private DialogInterface.OnClickListener negativeListener;
@Captor
private ArgumentCaptor<Integer> integerCaptor;
@Captor
private ArgumentCaptor<Intent> intentCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
shadowApp = shadowOf((Application) ApplicationProvider.getApplicationContext());
activityController = new ActivityController<>(TestActivity.class);
fragmentController = new FragmentController<>(TestFragment.class);
spyActivity = Mockito.spy(activityController.resume());
spyFragment = Mockito.spy(fragmentController.resume());
}
// ------ From Activity ------
@Test
public void shouldShowExpectedSettingsDialog_whenBuildingFromActivity() {
new AppSettingsDialog.Builder(spyActivity)
.setTitle(android.R.string.dialog_alert_title)
.setRationale(android.R.string.unknownName)
.setPositiveButton(android.R.string.ok)
.setNegativeButton(android.R.string.cancel)
.setThemeResId(R.style.Theme_AppCompat)
.build()
.show();
verify(spyActivity, times(1))
.startActivityForResult(intentCaptor.capture(), integerCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(DEFAULT_SETTINGS_REQ_CODE);
assertThat(Objects.requireNonNull(intentCaptor.getValue().getComponent()).getClassName())
.isEqualTo(AppSettingsDialogHolderActivity.class.getName());
Intent startedIntent = shadowApp.getNextStartedActivity();
ShadowIntent shadowIntent = shadowOf(startedIntent);
assertThat(shadowIntent.getIntentClass()).isEqualTo(AppSettingsDialogHolderActivity.class);
}
@Test
public void shouldPositiveListener_whenClickingPositiveButtonFromActivity() {
AlertDialog alertDialog = new AppSettingsDialog.Builder(spyActivity)
.setTitle(TITLE)
.setRationale(RATIONALE)
.setPositiveButton(POSITIVE)
.setNegativeButton(NEGATIVE)
.setThemeResId(R.style.Theme_AppCompat)
.build()
.showDialog(positiveListener, negativeListener);
Button positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
positive.performClick();
verify(positiveListener, times(1))
.onClick(any(DialogInterface.class), anyInt());
}
@Test
public void shouldNegativeListener_whenClickingPositiveButtonFromActivity() {
AlertDialog alertDialog = new AppSettingsDialog.Builder(spyActivity)
.setTitle(TITLE)
.setRationale(RATIONALE)
.setPositiveButton(POSITIVE)
.setNegativeButton(NEGATIVE)
.setThemeResId(R.style.Theme_AppCompat)
.build()
.showDialog(positiveListener, negativeListener);
Button positive = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
positive.performClick();
verify(negativeListener, times(1))
.onClick(any(DialogInterface.class), anyInt());
}
@Test
public void shouldShowExpectedSettingsDialog_whenBuildingFromSupportFragment() {
new AppSettingsDialog.Builder(spyFragment)
.setTitle(android.R.string.dialog_alert_title)
.setRationale(android.R.string.unknownName)
.setPositiveButton(android.R.string.ok)
.setNegativeButton(android.R.string.cancel)
.setThemeResId(R.style.Theme_AppCompat)
.build()
.show();
verify(spyFragment, times(1))
.startActivityForResult(intentCaptor.capture(), integerCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(DEFAULT_SETTINGS_REQ_CODE);
assertThat(Objects.requireNonNull(intentCaptor.getValue().getComponent()).getClassName())
.isEqualTo(AppSettingsDialogHolderActivity.class.getName());
Intent startedIntent = shadowApp.getNextStartedActivity();
ShadowIntent shadowIntent = shadowOf(startedIntent);
assertThat(shadowIntent.getIntentClass()).isEqualTo(AppSettingsDialogHolderActivity.class);
}
@Test
public void shouldPositiveListener_whenClickingPositiveButtonFromSupportFragment() {
AlertDialog alertDialog = new AppSettingsDialog.Builder(spyFragment)
.setTitle(TITLE)
.setRationale(RATIONALE)
.setPositiveButton(POSITIVE)
.setNegativeButton(NEGATIVE)
.setThemeResId(R.style.Theme_AppCompat)
.build()
.showDialog(positiveListener, negativeListener);
Button positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
positive.performClick();
verify(positiveListener, times(1))
.onClick(any(DialogInterface.class), anyInt());
}
@Test
public void shouldNegativeListener_whenClickingPositiveButtonFromSupportFragment() {
AlertDialog alertDialog = new AppSettingsDialog.Builder(spyFragment)
.setTitle(TITLE)
.setRationale(RATIONALE)
.setPositiveButton(POSITIVE)
.setNegativeButton(NEGATIVE)
.setThemeResId(R.style.Theme_AppCompat)
.build()
.showDialog(positiveListener, negativeListener);
Button positive = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
positive.performClick();
verify(negativeListener, times(1))
.onClick(any(DialogInterface.class), anyInt());
}
}

@ -0,0 +1,121 @@
package pub.devrel.easypermissions;
import android.Manifest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import androidx.test.core.app.ApplicationProvider;
import pub.devrel.easypermissions.testhelper.ActivityController;
import pub.devrel.easypermissions.testhelper.FragmentController;
import pub.devrel.easypermissions.testhelper.TestActivity;
import pub.devrel.easypermissions.testhelper.TestAppCompatActivity;
import pub.devrel.easypermissions.testhelper.TestFragment;
import pub.devrel.easypermissions.testhelper.TestSupportFragmentActivity;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Low-API (SDK = 19) tests for {@link pub.devrel.easypermissions.EasyPermissions}.
*/
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 19)
public class EasyPermissionsLowApiTest {
private static final String RATIONALE = "RATIONALE";
private static final String[] ALL_PERMS = new String[]{
Manifest.permission.READ_SMS, Manifest.permission.ACCESS_FINE_LOCATION};
private TestActivity spyActivity;
private TestSupportFragmentActivity spySupportFragmentActivity;
private TestAppCompatActivity spyAppCompatActivity;
private TestFragment spyFragment;
private FragmentController<TestFragment> fragmentController;
private ActivityController<TestActivity> activityController;
private ActivityController<TestSupportFragmentActivity> supportFragmentActivityController;
private ActivityController<TestAppCompatActivity> appCompatActivityController;
@Captor
private ArgumentCaptor<Integer> integerCaptor;
@Captor
private ArgumentCaptor<ArrayList<String>> listCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
activityController = new ActivityController<>(TestActivity.class);
supportFragmentActivityController = new ActivityController<>(TestSupportFragmentActivity.class);
appCompatActivityController = new ActivityController<>(TestAppCompatActivity.class);
fragmentController = new FragmentController<>(TestFragment.class);
spyActivity = Mockito.spy(activityController.resume());
spySupportFragmentActivity = Mockito.spy(supportFragmentActivityController.resume());
spyAppCompatActivity = Mockito.spy(appCompatActivityController.resume());
spyFragment = Mockito.spy(fragmentController.resume());
}
// ------ General tests ------
@Test
public void shouldHavePermission_whenHasPermissionsBeforeMarshmallow() {
assertThat(EasyPermissions.hasPermissions(ApplicationProvider.getApplicationContext(),
Manifest.permission.ACCESS_COARSE_LOCATION)).isTrue();
}
// ------ From Activity ------
@Test
public void shouldCallbackOnPermissionGranted_whenRequestFromActivity() {
EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);
verify(spyActivity, times(1))
.onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(TestActivity.REQUEST_CODE);
assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
}
// ------ From Support Activity ------
@Test
public void shouldCallbackOnPermissionGranted_whenRequestFromSupportFragmentActivity() {
EasyPermissions.requestPermissions(spySupportFragmentActivity, RATIONALE, TestSupportFragmentActivity.REQUEST_CODE, ALL_PERMS);
verify(spySupportFragmentActivity, times(1))
.onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(TestSupportFragmentActivity.REQUEST_CODE);
assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
}
@Test
public void shouldCallbackOnPermissionGranted_whenRequestFromAppCompatActivity() {
EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);
verify(spyAppCompatActivity, times(1))
.onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(TestAppCompatActivity.REQUEST_CODE);
assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
}
@Test
public void shouldCallbackOnPermissionGranted_whenRequestFromFragment() {
EasyPermissions.requestPermissions(spyFragment, RATIONALE, TestFragment.REQUEST_CODE, ALL_PERMS);
verify(spyFragment, times(1))
.onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(TestFragment.REQUEST_CODE);
assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
}
}

@ -0,0 +1,611 @@
package pub.devrel.easypermissions;
import android.Manifest;
import android.app.Application;
import android.app.Dialog;
import android.app.Fragment;
import android.content.pm.PackageManager;
import android.widget.TextView;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import androidx.test.core.app.ApplicationProvider;
import pub.devrel.easypermissions.testhelper.ActivityController;
import pub.devrel.easypermissions.testhelper.FragmentController;
import pub.devrel.easypermissions.testhelper.TestActivity;
import pub.devrel.easypermissions.testhelper.TestAppCompatActivity;
import pub.devrel.easypermissions.testhelper.TestFragment;
import pub.devrel.easypermissions.testhelper.TestSupportFragmentActivity;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
/**
* Basic Robolectric tests for {@link pub.devrel.easypermissions.EasyPermissions}.
*/
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class EasyPermissionsTest {
private static final String RATIONALE = "RATIONALE";
private static final String POSITIVE = "POSITIVE";
private static final String NEGATIVE = "NEGATIVE";
private static final String[] ONE_PERM = new String[]{Manifest.permission.READ_SMS};
private static final String[] ALL_PERMS = new String[]{
Manifest.permission.READ_SMS, Manifest.permission.ACCESS_FINE_LOCATION};
private static final int[] SMS_DENIED_RESULT = new int[]{
PackageManager.PERMISSION_DENIED, PackageManager.PERMISSION_GRANTED};
private ShadowApplication shadowApp;
private Application app;
private TestActivity spyActivity;
private TestSupportFragmentActivity spySupportFragmentActivity;
private TestAppCompatActivity spyAppCompatActivity;
private TestFragment spyFragment;
private FragmentController<TestFragment> fragmentController;
private ActivityController<TestActivity> activityController;
private ActivityController<TestSupportFragmentActivity> supportFragmentActivityController;
private ActivityController<TestAppCompatActivity> appCompatActivityController;
@Captor
private ArgumentCaptor<Integer> integerCaptor;
@Captor
private ArgumentCaptor<ArrayList<String>> listCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
app = ApplicationProvider.getApplicationContext();
shadowApp = shadowOf(app);
activityController = new ActivityController<>(TestActivity.class);
supportFragmentActivityController = new ActivityController<>(TestSupportFragmentActivity.class);
appCompatActivityController = new ActivityController<>(TestAppCompatActivity.class);
fragmentController = new FragmentController<>(TestFragment.class);
spyActivity = Mockito.spy(activityController.resume());
spySupportFragmentActivity = Mockito.spy(supportFragmentActivityController.resume());
spyAppCompatActivity = Mockito.spy(appCompatActivityController.resume());
spyFragment = Mockito.spy(fragmentController.resume());
}
// ------ General tests ------
@Test
public void shouldNotHavePermissions_whenNoPermissionsGranted() {
assertThat(EasyPermissions.hasPermissions(app, ALL_PERMS)).isFalse();
}
@Test
public void shouldNotHavePermissions_whenNotAllPermissionsGranted() {
shadowApp.grantPermissions(ONE_PERM);
assertThat(EasyPermissions.hasPermissions(app, ALL_PERMS)).isFalse();
}
@Test
public void shouldHavePermissions_whenAllPermissionsGranted() {
shadowApp.grantPermissions(ALL_PERMS);
assertThat(EasyPermissions.hasPermissions(app, ALL_PERMS)).isTrue();
}
@SuppressWarnings("ConstantConditions")
@Test
public void shouldThrowException_whenHasPermissionsWithNullContext() {
try {
EasyPermissions.hasPermissions(null, ALL_PERMS);
fail("IllegalStateException expected because of null context.");
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat()
.isEqualTo("Can't check permissions for null context");
}
}
// ------ From Activity ------
@Test
public void shouldCorrectlyCallback_whenOnRequestPermissionResultCalledFromActivity() {
EasyPermissions.onRequestPermissionsResult(TestActivity.REQUEST_CODE, ALL_PERMS, SMS_DENIED_RESULT, spyActivity);
verify(spyActivity, times(1))
.onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(TestActivity.REQUEST_CODE);
assertThat(listCaptor.getValue())
.containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION)));
verify(spyActivity, times(1))
.onPermissionsDenied(integerCaptor.capture(), listCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(TestActivity.REQUEST_CODE);
assertThat(listCaptor.getValue())
.containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.READ_SMS)));
verify(spyActivity, never()).afterPermissionGranted();
}
@Test
public void shouldCallbackOnPermissionGranted_whenRequestAlreadyGrantedPermissionsFromActivity() {
grantPermissions(ALL_PERMS);
EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);
verify(spyActivity, times(1))
.onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
verify(spyActivity, never()).requestPermissions(any(String[].class), anyInt());
assertThat(integerCaptor.getValue()).isEqualTo(TestActivity.REQUEST_CODE);
assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
}
@Test
public void shouldCallbackAfterPermissionGranted_whenRequestAlreadyGrantedPermissionsFromActivity() {
grantPermissions(ALL_PERMS);
EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);
// Called 2 times because this is a spy and library implementation invokes super classes annotated methods as well
verify(spyActivity, times(2)).afterPermissionGranted();
}
@Test
public void shouldNotCallbackAfterPermissionGranted_whenRequestNotGrantedPermissionsFromActivity() {
grantPermissions(ONE_PERM);
EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);
verify(spyActivity, never()).afterPermissionGranted();
}
@Test
public void shouldRequestPermissions_whenMissingPermissionAndNotShowRationaleFromActivity() {
grantPermissions(ONE_PERM);
showRationale(false, ALL_PERMS);
EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);
verify(spyActivity, times(1))
.requestPermissions(ALL_PERMS, TestActivity.REQUEST_CODE);
}
@Test
public void shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromActivity() {
grantPermissions(ONE_PERM);
showRationale(true, ALL_PERMS);
EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);
Fragment dialogFragment = spyActivity.getFragmentManager()
.findFragmentByTag(RationaleDialogFragment.TAG);
assertThat(dialogFragment).isInstanceOf(RationaleDialogFragment.class);
Dialog dialog = ((RationaleDialogFragment) dialogFragment).getDialog();
assertThatHasExpectedRationale(dialog, RATIONALE);
}
@Test
public void shouldShowCorrectDialogUsingRequest_whenMissingPermissionsAndShowRationaleFromActivity() {
grantPermissions(ONE_PERM);
showRationale(true, ALL_PERMS);
PermissionRequest request = new PermissionRequest.Builder(spyActivity, TestActivity.REQUEST_CODE, ALL_PERMS)
.setPositiveButtonText(android.R.string.ok)
.setNegativeButtonText(android.R.string.cancel)
.setRationale(android.R.string.unknownName)
.setTheme(R.style.Theme_AppCompat)
.build();
EasyPermissions.requestPermissions(request);
Fragment dialogFragment = spyActivity.getFragmentManager()
.findFragmentByTag(RationaleDialogFragment.TAG);
assertThat(dialogFragment).isInstanceOf(RationaleDialogFragment.class);
Dialog dialog = ((RationaleDialogFragment) dialogFragment).getDialog();
assertThatHasExpectedButtonsAndRationale(dialog, android.R.string.unknownName,
android.R.string.ok, android.R.string.cancel);
}
@Test
public void shouldHaveSomePermissionDenied_whenShowRationaleFromActivity() {
showRationale(true, ALL_PERMS);
assertThat(EasyPermissions.somePermissionDenied(spyActivity, ALL_PERMS)).isTrue();
}
@Test
public void shouldNotHaveSomePermissionDenied_whenNotShowRationaleFromActivity() {
showRationale(false, ALL_PERMS);
assertThat(EasyPermissions.somePermissionDenied(spyActivity, ALL_PERMS)).isFalse();
}
@Test
public void shouldHaveSomePermissionPermanentlyDenied_whenNotShowRationaleFromActivity() {
showRationale(false, ALL_PERMS);
assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyActivity, Arrays.asList(ALL_PERMS))).isTrue();
}
@Test
public void shouldNotHaveSomePermissionPermanentlyDenied_whenShowRationaleFromActivity() {
showRationale(true, ALL_PERMS);
assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyActivity, Arrays.asList(ALL_PERMS))).isFalse();
}
@Test
public void shouldHavePermissionPermanentlyDenied_whenNotShowRationaleFromActivity() {
showRationale(false, Manifest.permission.READ_SMS);
assertThat(EasyPermissions.permissionPermanentlyDenied(spyActivity, Manifest.permission.READ_SMS)).isTrue();
}
@Test
public void shouldNotHavePermissionPermanentlyDenied_whenShowRationaleFromActivity() {
showRationale(true, Manifest.permission.READ_SMS);
assertThat(EasyPermissions.permissionPermanentlyDenied(spyActivity, Manifest.permission.READ_SMS)).isFalse();
}
@Test
public void shouldCorrectlyCallback_whenOnRequestPermissionResultCalledFromAppCompatActivity() {
EasyPermissions.onRequestPermissionsResult(TestAppCompatActivity.REQUEST_CODE, ALL_PERMS, SMS_DENIED_RESULT, spyAppCompatActivity);
verify(spyAppCompatActivity, times(1))
.onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(TestAppCompatActivity.REQUEST_CODE);
assertThat(listCaptor.getValue())
.containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION)));
verify(spyAppCompatActivity, times(1))
.onPermissionsDenied(integerCaptor.capture(), listCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(TestAppCompatActivity.REQUEST_CODE);
assertThat(listCaptor.getValue())
.containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.READ_SMS)));
verify(spyAppCompatActivity, never()).afterPermissionGranted();
}
@Test
public void shouldCallbackOnPermissionGranted_whenRequestAlreadyGrantedPermissionsFromAppCompatActivity() {
grantPermissions(ALL_PERMS);
EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);
verify(spyAppCompatActivity, times(1))
.onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
verify(spyAppCompatActivity, never()).requestPermissions(any(String[].class), anyInt());
assertThat(integerCaptor.getValue()).isEqualTo(TestAppCompatActivity.REQUEST_CODE);
assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
}
@Test
public void shouldCallbackAfterPermissionGranted_whenRequestAlreadyGrantedPermissionsFromAppCompatActivity() {
grantPermissions(ALL_PERMS);
EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);
// Called 2 times because this is a spy and library implementation invokes super classes annotated methods as well
verify(spyAppCompatActivity, times(2)).afterPermissionGranted();
}
@Test
public void shouldNotCallbackAfterPermissionGranted_whenRequestNotGrantedPermissionsFromAppCompatActivity() {
grantPermissions(ONE_PERM);
EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);
verify(spyAppCompatActivity, never()).afterPermissionGranted();
}
@Test
public void shouldRequestPermissions_whenMissingPermissionAndNotShowRationaleFromAppCompatActivity() {
grantPermissions(ONE_PERM);
showRationale(false, ALL_PERMS);
EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);
verify(spyAppCompatActivity, times(1))
.requestPermissions(ALL_PERMS, TestAppCompatActivity.REQUEST_CODE);
}
@Test
public void shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromAppCompatActivity() {
grantPermissions(ONE_PERM);
showRationale(true, ALL_PERMS);
EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);
androidx.fragment.app.Fragment dialogFragment = spyAppCompatActivity.getSupportFragmentManager()
.findFragmentByTag(RationaleDialogFragmentCompat.TAG);
assertThat(dialogFragment).isInstanceOf(RationaleDialogFragmentCompat.class);
Dialog dialog = ((RationaleDialogFragmentCompat) dialogFragment).getDialog();
assertThatHasExpectedRationale(dialog, RATIONALE);
}
@Test
public void shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromSupportFragmentActivity() {
grantPermissions(ONE_PERM);
showRationale(true, ALL_PERMS);
EasyPermissions.requestPermissions(spySupportFragmentActivity, RATIONALE, TestSupportFragmentActivity.REQUEST_CODE, ALL_PERMS);
Fragment dialogFragment = spySupportFragmentActivity.getFragmentManager()
.findFragmentByTag(RationaleDialogFragment.TAG);
assertThat(dialogFragment).isInstanceOf(RationaleDialogFragment.class);
Dialog dialog = ((RationaleDialogFragment) dialogFragment).getDialog();
assertThatHasExpectedRationale(dialog, RATIONALE);
}
@Test
public void shouldShowCorrectDialogUsingRequest_whenMissingPermissionsAndShowRationaleFromAppCompatActivity() {
grantPermissions(ONE_PERM);
showRationale(true, ALL_PERMS);
PermissionRequest request = new PermissionRequest.Builder(spyAppCompatActivity, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS)
.setPositiveButtonText(android.R.string.ok)
.setNegativeButtonText(android.R.string.cancel)
.setRationale(android.R.string.unknownName)
.setTheme(R.style.Theme_AppCompat)
.build();
EasyPermissions.requestPermissions(request);
androidx.fragment.app.Fragment dialogFragment = spyAppCompatActivity.getSupportFragmentManager()
.findFragmentByTag(RationaleDialogFragmentCompat.TAG);
assertThat(dialogFragment).isInstanceOf(RationaleDialogFragmentCompat.class);
Dialog dialog = ((RationaleDialogFragmentCompat) dialogFragment).getDialog();
assertThatHasExpectedButtonsAndRationale(dialog, android.R.string.unknownName,
android.R.string.ok, android.R.string.cancel);
}
@Test
public void shouldHaveSomePermissionDenied_whenShowRationaleFromAppCompatActivity() {
showRationale(true, ALL_PERMS);
assertThat(EasyPermissions.somePermissionDenied(spyAppCompatActivity, ALL_PERMS)).isTrue();
}
@Test
public void shouldNotHaveSomePermissionDenied_whenNotShowRationaleFromAppCompatActivity() {
showRationale(false, ALL_PERMS);
assertThat(EasyPermissions.somePermissionDenied(spyAppCompatActivity, ALL_PERMS)).isFalse();
}
@Test
public void shouldHaveSomePermissionPermanentlyDenied_whenNotShowRationaleFromAppCompatActivity() {
showRationale(false, ALL_PERMS);
assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyAppCompatActivity, Arrays.asList(ALL_PERMS))).isTrue();
}
@Test
public void shouldNotHaveSomePermissionPermanentlyDenied_whenShowRationaleFromAppCompatActivity() {
showRationale(true, ALL_PERMS);
assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyAppCompatActivity, Arrays.asList(ALL_PERMS))).isFalse();
}
@Test
public void shouldHavePermissionPermanentlyDenied_whenNotShowRationaleFromAppCompatActivity() {
showRationale(false, Manifest.permission.READ_SMS);
assertThat(EasyPermissions.permissionPermanentlyDenied(spyAppCompatActivity, Manifest.permission.READ_SMS)).isTrue();
}
@Test
public void shouldNotHavePermissionPermanentlyDenied_whenShowRationaleFromAppCompatActivity() {
showRationale(true, Manifest.permission.READ_SMS);
assertThat(EasyPermissions.permissionPermanentlyDenied(spyAppCompatActivity, Manifest.permission.READ_SMS)).isFalse();
}
@Test
public void shouldCorrectlyCallback_whenOnRequestPermissionResultCalledFromFragment() {
EasyPermissions.onRequestPermissionsResult(TestFragment.REQUEST_CODE, ALL_PERMS, SMS_DENIED_RESULT,
spyFragment);
verify(spyFragment, times(1))
.onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(TestFragment.REQUEST_CODE);
assertThat(listCaptor.getValue())
.containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION)));
verify(spyFragment, times(1))
.onPermissionsDenied(integerCaptor.capture(), listCaptor.capture());
assertThat(integerCaptor.getValue()).isEqualTo(TestFragment.REQUEST_CODE);
assertThat(listCaptor.getValue())
.containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.READ_SMS)));
verify(spyFragment, never()).afterPermissionGranted();
}
@Test
public void shouldCallbackOnPermissionGranted_whenRequestAlreadyGrantedPermissionsFromFragment() {
grantPermissions(ALL_PERMS);
EasyPermissions.requestPermissions(spyFragment, RATIONALE,
TestFragment.REQUEST_CODE, ALL_PERMS);
verify(spyFragment, times(1))
.onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
verify(spyFragment, never()).requestPermissions(any(String[].class), anyInt());
assertThat(integerCaptor.getValue()).isEqualTo(TestFragment.REQUEST_CODE);
assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
}
@Test
public void shouldCallbackAfterPermissionGranted_whenRequestAlreadyGrantedPermissionsFragment() {
grantPermissions(ALL_PERMS);
EasyPermissions.requestPermissions(spyFragment, RATIONALE, TestFragment.REQUEST_CODE, ALL_PERMS);
// Called 2 times because this is a spy and library implementation invokes super classes annotated methods as well
verify(spyFragment, times(2)).afterPermissionGranted();
}
@Test
public void shouldNotCallbackAfterPermissionGranted_whenRequestNotGrantedPermissionsFromFragment() {
grantPermissions(ONE_PERM);
EasyPermissions.requestPermissions(spyFragment, RATIONALE, TestFragment.REQUEST_CODE, ALL_PERMS);
verify(spyFragment, never()).afterPermissionGranted();
}
@Test
public void shouldRequestPermissions_whenMissingPermissionsAndNotShowRationaleFromFragment() {
grantPermissions(ONE_PERM);
showRationale(false, ALL_PERMS);
EasyPermissions.requestPermissions(spyFragment, RATIONALE, TestFragment.REQUEST_CODE, ALL_PERMS);
verify(spyFragment, times(1))
.requestPermissions(ALL_PERMS, TestFragment.REQUEST_CODE);
}
@Test
public void shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromFragment() {
grantPermissions(ONE_PERM);
showRationale(true, ALL_PERMS);
EasyPermissions.requestPermissions(spyFragment, RATIONALE, TestFragment.REQUEST_CODE, ALL_PERMS);
androidx.fragment.app.Fragment dialogFragment = spyFragment.getChildFragmentManager()
.findFragmentByTag(RationaleDialogFragmentCompat.TAG);
assertThat(dialogFragment).isInstanceOf(RationaleDialogFragmentCompat.class);
Dialog dialog = ((RationaleDialogFragmentCompat) dialogFragment).getDialog();
assertThatHasExpectedRationale(dialog, RATIONALE);
}
@Test
public void shouldShowCorrectDialogUsingRequest_whenMissingPermissionsAndShowRationaleFromFragment() {
grantPermissions(ONE_PERM);
showRationale(true, ALL_PERMS);
PermissionRequest request = new PermissionRequest.Builder(spyFragment, TestFragment.REQUEST_CODE, ALL_PERMS)
.setPositiveButtonText(POSITIVE)
.setNegativeButtonText(NEGATIVE)
.setRationale(RATIONALE)
.setTheme(R.style.Theme_AppCompat)
.build();
EasyPermissions.requestPermissions(request);
androidx.fragment.app.Fragment dialogFragment = spyFragment.getChildFragmentManager()
.findFragmentByTag(RationaleDialogFragmentCompat.TAG);
assertThat(dialogFragment).isInstanceOf(RationaleDialogFragmentCompat.class);
Dialog dialog = ((RationaleDialogFragmentCompat) dialogFragment).getDialog();
assertThatHasExpectedButtonsAndRationale(dialog, RATIONALE, POSITIVE, NEGATIVE);
}
@Test
public void shouldHaveSomePermissionDenied_whenShowRationaleFromFragment() {
showRationale(true, ALL_PERMS);
assertThat(EasyPermissions.somePermissionDenied(spyFragment, ALL_PERMS)).isTrue();
}
@Test
public void shouldNotHaveSomePermissionDenied_whenNotShowRationaleFromFragment() {
showRationale(false, ALL_PERMS);
assertThat(EasyPermissions.somePermissionDenied(spyFragment, ALL_PERMS)).isFalse();
}
@Test
public void shouldHaveSomePermissionPermanentlyDenied_whenNotShowRationaleFromFragment() {
showRationale(false, ALL_PERMS);
assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyFragment, Arrays.asList(ALL_PERMS))).isTrue();
}
@Test
public void shouldNotHaveSomePermissionPermanentlyDenied_whenShowRationaleFromFragment() {
showRationale(true, ALL_PERMS);
assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyFragment, Arrays.asList(ALL_PERMS))).isFalse();
}
@Test
public void shouldHavePermissionPermanentlyDenied_whenNotShowRationaleFromFragment() {
showRationale(false, Manifest.permission.READ_SMS);
assertThat(EasyPermissions.permissionPermanentlyDenied(spyFragment, Manifest.permission.READ_SMS)).isTrue();
}
@Test
public void shouldNotHavePermissionPermanentlyDenied_whenShowRationaleFromFragment() {
showRationale(true, Manifest.permission.READ_SMS);
assertThat(EasyPermissions.permissionPermanentlyDenied(spyFragment, Manifest.permission.READ_SMS)).isFalse();
}
private void assertThatHasExpectedButtonsAndRationale(Dialog dialog, int rationale,
int positive, int negative) {
TextView dialogMessage = dialog.findViewById(android.R.id.message);
assertThat(dialogMessage.getText().toString()).isEqualTo(app.getString(rationale));
TextView positiveMessage = dialog.findViewById(android.R.id.button1);
assertThat(positiveMessage.getText().toString()).isEqualTo(app.getString(positive));
TextView negativeMessage = dialog.findViewById(android.R.id.button2);
assertThat(negativeMessage.getText().toString()).isEqualTo(app.getString(negative));
}
private void assertThatHasExpectedButtonsAndRationale(Dialog dialog, String rationale,
int positive, int negative) {
TextView dialogMessage = dialog.findViewById(android.R.id.message);
assertThat(dialogMessage.getText().toString()).isEqualTo(rationale);
TextView positiveMessage = dialog.findViewById(android.R.id.button1);
assertThat(positiveMessage.getText().toString()).isEqualTo(app.getString(positive));
TextView negativeMessage = dialog.findViewById(android.R.id.button2);
assertThat(negativeMessage.getText().toString()).isEqualTo(app.getString(negative));
}
private void assertThatHasExpectedButtonsAndRationale(Dialog dialog, String rationale,
String positive, String negative) {
TextView dialogMessage = dialog.findViewById(android.R.id.message);
assertThat(dialogMessage.getText().toString()).isEqualTo(rationale);
TextView positiveMessage = dialog.findViewById(android.R.id.button1);
assertThat(positiveMessage.getText().toString()).isEqualTo(positive);
TextView negativeMessage = dialog.findViewById(android.R.id.button2);
assertThat(negativeMessage.getText().toString()).isEqualTo(negative);
}
private void assertThatHasExpectedRationale(Dialog dialog, String rationale) {
TextView dialogMessage = dialog.findViewById(android.R.id.message);
assertThat(dialogMessage.getText().toString()).isEqualTo(rationale);
}
private void grantPermissions(String[] perms) {
shadowApp.grantPermissions(perms);
}
private void showRationale(boolean show, String... perms) {
for (String perm : perms) {
when(spyActivity.shouldShowRequestPermissionRationale(perm)).thenReturn(show);
when(spySupportFragmentActivity.shouldShowRequestPermissionRationale(perm)).thenReturn(show);
when(spyAppCompatActivity.shouldShowRequestPermissionRationale(perm)).thenReturn(show);
when(spyFragment.shouldShowRequestPermissionRationale(perm)).thenReturn(show);
}
}
}

@ -0,0 +1,134 @@
package pub.devrel.easypermissions;
import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import androidx.fragment.app.Fragment;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.Arrays;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class RationaleDialogClickListenerTest {
private static final int REQUEST_CODE = 5;
private static final String[] PERMS = new String[]{
Manifest.permission.READ_SMS, Manifest.permission.ACCESS_FINE_LOCATION};
@Mock
private RationaleDialogFragment dialogFragment;
@Mock
private RationaleDialogFragmentCompat dialogFragmentCompat;
@Mock
private RationaleDialogConfig dialogConfig;
@Mock
private EasyPermissions.PermissionCallbacks permissionCallbacks;
@Mock
private EasyPermissions.RationaleCallbacks rationaleCallbacks;
@Mock
private DialogInterface dialogInterface;
@Mock
private Activity activity;
@Mock
private Fragment fragment;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(dialogFragment.getActivity()).thenReturn(activity);
dialogConfig.requestCode = REQUEST_CODE;
dialogConfig.permissions = PERMS;
}
@Test
public void shouldOnRationaleAccepted_whenPositiveButtonWithRationaleCallbacks() {
RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
permissionCallbacks, rationaleCallbacks);
listener.onClick(dialogInterface, Dialog.BUTTON_POSITIVE);
verify(rationaleCallbacks, times(1)).onRationaleAccepted(REQUEST_CODE);
}
@Test
public void shouldNotOnRationaleAccepted_whenPositiveButtonWithoutRationaleCallbacks() {
RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
permissionCallbacks, null);
listener.onClick(dialogInterface, Dialog.BUTTON_POSITIVE);
verify(rationaleCallbacks, never()).onRationaleAccepted(anyInt());
}
@Test
public void shouldRequestPermissions_whenPositiveButtonFromActivity() {
RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
permissionCallbacks, rationaleCallbacks);
listener.onClick(dialogInterface, Dialog.BUTTON_POSITIVE);
verify(activity, times(1)).requestPermissions(PERMS, REQUEST_CODE);
}
@Test
public void shouldRequestPermissions_whenPositiveButtonFromFragment() {
when(dialogFragmentCompat.getParentFragment()).thenReturn(fragment);
RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragmentCompat, dialogConfig,
permissionCallbacks, rationaleCallbacks);
listener.onClick(dialogInterface, Dialog.BUTTON_POSITIVE);
verify(fragment, times(1)).requestPermissions(PERMS, REQUEST_CODE);
}
@Test
public void shouldOnRationaleDenied_whenNegativeButtonWithRationaleCallbacks() {
RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
permissionCallbacks, rationaleCallbacks);
listener.onClick(dialogInterface, Dialog.BUTTON_NEGATIVE);
verify(rationaleCallbacks, times(1)).onRationaleDenied(REQUEST_CODE);
}
@Test
public void shouldNotOnRationaleDenied_whenNegativeButtonWithoutRationaleCallbacks() {
RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
permissionCallbacks, null);
listener.onClick(dialogInterface, Dialog.BUTTON_NEGATIVE);
verify(rationaleCallbacks, never()).onRationaleDenied(anyInt());
}
@Test
public void shouldOnPermissionsDenied_whenNegativeButtonWithPermissionCallbacks() {
RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
permissionCallbacks, rationaleCallbacks);
listener.onClick(dialogInterface, Dialog.BUTTON_NEGATIVE);
verify(permissionCallbacks, times(1))
.onPermissionsDenied(REQUEST_CODE, Arrays.asList(PERMS));
}
@Test
public void shouldNotOnPermissionsDenied_whenNegativeButtonWithoutPermissionCallbacks() {
RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
null, rationaleCallbacks);
listener.onClick(dialogInterface, Dialog.BUTTON_NEGATIVE);
verify(permissionCallbacks, never()).onPermissionsDenied(anyInt(), ArgumentMatchers.<String>anyList());
}
}

@ -0,0 +1,45 @@
package pub.devrel.easypermissions.testhelper;
import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.test.core.app.ActivityScenario;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* Helper class to allow starting Activity, similar to the Robolectric ActivityConroller.
*/
public class ActivityController<T extends Activity> {
private ActivityScenario<T> scenario;
public ActivityController(Class<T> clazz) {
scenario = ActivityScenario.launch(clazz);
}
public synchronized T resume() {
final CompletableFuture<T> ActivityFuture = new CompletableFuture<>();
scenario.onActivity(new ActivityScenario.ActivityAction<T>() {
@Override
public void perform(@NonNull T activity) {
ActivityFuture.complete(activity);
}
});
try {
return ActivityFuture.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
public void reset() {
scenario.recreate();
}
}

@ -0,0 +1,44 @@
package pub.devrel.easypermissions.testhelper;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.testing.FragmentScenario;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* Helper class to allow starting Fragments, similar to the old SupportFragmentController.
*/
public class FragmentController<T extends Fragment> {
private FragmentScenario<T> scenario;
public FragmentController(Class<T> clazz) {
scenario = FragmentScenario.launch(clazz);
}
public synchronized T resume() {
final CompletableFuture<T> fragmentFuture = new CompletableFuture<>();
scenario.onFragment(new FragmentScenario.FragmentAction<T>() {
@Override
public void perform(@NonNull T fragment) {
fragmentFuture.complete(fragment);
}
});
try {
return fragmentFuture.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
public void reset() {
scenario.recreate();
}
}

@ -0,0 +1,40 @@
package pub.devrel.easypermissions.testhelper;
import android.app.Activity;
import java.util.List;
import androidx.annotation.NonNull;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;
public class TestActivity extends Activity
implements EasyPermissions.PermissionCallbacks, EasyPermissions.RationaleCallbacks {
public static final int REQUEST_CODE = 1;
@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
}
@AfterPermissionGranted(REQUEST_CODE)
public void afterPermissionGranted() {
}
@Override
public void onRationaleAccepted(int requestCode) {
}
@Override
public void onRationaleDenied(int requestCode) {
}
}

@ -0,0 +1,50 @@
package pub.devrel.easypermissions.testhelper;
import android.os.Bundle;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;
import pub.devrel.easypermissions.R;
public class TestAppCompatActivity extends AppCompatActivity
implements EasyPermissions.PermissionCallbacks, EasyPermissions.RationaleCallbacks {
public static final int REQUEST_CODE = 3;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
getTheme().applyStyle(R.style.Theme_AppCompat, true);
super.onCreate(savedInstanceState);
}
@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
}
@AfterPermissionGranted(REQUEST_CODE)
public void afterPermissionGranted() {
}
@Override
public void onRationaleAccepted(int requestCode) {
}
@Override
public void onRationaleDenied(int requestCode) {
}
}

@ -0,0 +1,55 @@
package pub.devrel.easypermissions.testhelper;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;
import pub.devrel.easypermissions.R;
public class TestFragment extends Fragment
implements EasyPermissions.PermissionCallbacks, EasyPermissions.RationaleCallbacks {
public static final int REQUEST_CODE = 4;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
getContext().getTheme().applyStyle(R.style.Theme_AppCompat, true);
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
}
@AfterPermissionGranted(REQUEST_CODE)
public void afterPermissionGranted() {
}
@Override
public void onRationaleAccepted(int requestCode) {
}
@Override
public void onRationaleDenied(int requestCode) {
}
}

@ -0,0 +1,47 @@
package pub.devrel.easypermissions.testhelper;
import android.os.Bundle;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;
public class TestSupportFragmentActivity extends FragmentActivity
implements EasyPermissions.PermissionCallbacks, EasyPermissions.RationaleCallbacks {
public static final int REQUEST_CODE = 5;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
}
@AfterPermissionGranted(REQUEST_CODE)
public void afterPermissionGranted() {
}
@Override
public void onRationaleAccepted(int requestCode) {
}
@Override
public void onRationaleDenied(int requestCode) {
}
}

@ -62,7 +62,9 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine2: TranslationBase.of(context).signs,
route: VITAL_SIGN_DETAILS,
icon: 'assets/images/svgs/profile_screen/vital signs.svg',
onTap: () {},
// onTap: () {
// print("Hello.");
// },
),
// if (selectedPatientType != 7)
PatientProfileButton(
@ -86,7 +88,7 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).lab,
nameLine2: TranslationBase.of(context).result,
icon: 'assets/images/svgs/profile_screen/lab results.svg',
onTap: () {},
// onTap: () {},
),
// if (int.parse(patientType) == 7 || int.parse(patientType) == 6)
PatientProfileButton(
@ -99,7 +101,7 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).radiology,
nameLine2: TranslationBase.of(context).service,
icon: 'assets/images/svgs/profile_screen/health summary.svg',
onTap: () {},
// onTap: () {},
),
PatientProfileButton(
key: widget.key,
@ -110,7 +112,9 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).patient,
nameLine2: "ECG",
icon: 'assets/images/svgs/profile_screen/ECG.svg',
onTap: () {},
// onTap: () {
//
// },
),
PatientProfileButton(
key: widget.key,
@ -121,7 +125,7 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).orders,
nameLine2: TranslationBase.of(context).prescription,
icon: 'assets/images/svgs/profile_screen/order prescription.svg',
onTap: () {},
// onTap: () {},
),
// if (int.parse(patientType) == 7 || int.parse(patientType) == 6)
PatientProfileButton(
@ -133,7 +137,7 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).orders,
nameLine2: TranslationBase.of(context).procedures,
icon: 'assets/images/svgs/profile_screen/Order Procedures.svg',
onTap: () {},
// onTap: () {},
),
//if (int.parse(patientType) == 7 || int.parse(patientType) == 6)
PatientProfileButton(
@ -145,7 +149,7 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).insurance,
nameLine2: TranslationBase.of(context).service,
icon: 'assets/images/svgs/profile_screen/insurance approval.svg',
onTap: () {},
// onTap: () {},
),
// if (int.parse(patientType) == 7 || int.parse(patientType) == 6)
PatientProfileButton(
@ -157,7 +161,7 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).patientSick,
nameLine2: TranslationBase.of(context).leave,
icon: 'assets/images/svgs/profile_screen/patient sick leave.svg',
onTap: () {},
// onTap: () {},
),
if (widget.patient!.appointmentNo != null && widget.patient!.appointmentNo != 0)
PatientProfileButton(
@ -170,7 +174,7 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).patient,
nameLine2: TranslationBase.of(context).ucaf,
icon: 'assets/images/svgs/profile_screen/UCAF.svg',
onTap: () {},
// onTap: () {},
),
if (widget.patient!.appointmentNo != null && widget.patient!.appointmentNo != 0)
PatientProfileButton(
@ -183,7 +187,7 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).referral,
nameLine2: TranslationBase.of(context).patient,
icon: 'assets/images/svgs/profile_screen/refer patient.svg',
onTap: () {},
// onTap: () {},
),
if (widget.patient!.appointmentNo != null && widget.patient!.appointmentNo != 0)
PatientProfileButton(
@ -196,7 +200,7 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).admission,
nameLine2: TranslationBase.of(context).request,
icon: 'assets/images/svgs/profile_screen/admission req.svg',
onTap: () {},
// onTap: () {},
),
if (widget.isInpatient)
PatientProfileButton(
@ -208,7 +212,7 @@ class _ProfileMedicalInfoWidgetSearchState extends State<ProfileMedicalInfoWidge
nameLine1: TranslationBase.of(context).progress,
nameLine2: TranslationBase.of(context).note,
icon: 'assets/images/svgs/profile_screen/Progress notes.svg',
onTap: () {},
// onTap: () {},
),
if (widget.isInpatient)
PatientProfileButton(

Loading…
Cancel
Save