referral click fixed,
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 >= 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 < 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 < 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) {
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue