diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..9b341ed0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,18 @@ +name: test + +on: + - pull_request + - push + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Gradle + run: | + ./gradlew build :easypermissions:test diff --git a/.gitignore b/.gitignore index 8bc4d342..7a597d85 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,7 @@ captures/ # Misc .DS_Store +.classpath +.project +.settings +.vscode diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a0ced18f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: android -jdk: oraclejdk8 - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - - $HOME/.android/build-cache - -before_install: - - mkdir "$ANDROID_HOME/licenses" || true - - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license" - - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" -android: - components: - # https://github.com/travis-ci/travis-ci/issues/6040#issuecomment-219367943 - - tools - - tools - -before_script: echo y | ${ANDROID_HOME}tools/bin/sdkmanager --channel=3 "tools" "platform-tools" "platforms;android-27" -script: ./gradlew build - -after_failure: - - cat app/build/reports/tests/testDebugUnitTest/index.html diff --git a/PUBLISHING.md b/PUBLISHING.md new file mode 100644 index 00000000..f61ba635 --- /dev/null +++ b/PUBLISHING.md @@ -0,0 +1,61 @@ +## Publishing + +### Credentials + +The library is published to Maven Central by the firebase-sonatype account, Googlers can find the +password for this account in [Valentine](http://valentine/) + +### GPG Key + +You will need to create a private GPG keyring on your machine, if you don't have one do the +following steps: + + 1. Run `gpg --full-generate-key` + 1. Choose `RSA and RSA` for the key type + 1. Use `4096` for the key size + 1. Use `0` for the expiration (never) + 1. Use any name, email address, and password + +This creates your key in `~/.gnupg/openpgp-revocs.d/` with `.rev` format. The last 8 characters +before the `.rev` extension are your **Key ID**. + +To export the key, run: + +``` +gpg --export-secret-keys -o $HOME/sonatype.gpg +``` + +Finally upload your key to the keyserver: + +``` +gpg --keyserver hkp://keys.openpgp.org --send-keys +``` + +### Local Properties + +Open your `$HOME/.gradle/gradle.properties` file at and fill in the values: + +``` +signing.keyId= +signing.password= +signing.secretKeyRingFile= +mavenCentralRepositoryUsername=firebase-sonatype +mavenCentralRepositoryUsername= +``` + +### Publish + +To publish, run: + +``` +./gradlew publish +``` + +### Release + +Follow [the instructions here](https://central.sonatype.org/pages/releasing-the-deployment.html): + + 1. Navigate to https://s01.oss.sonatype.org/ and **Log In** + 1. On the left side click **Build Promotion** and look for the `com.firebase` repo + 1. Click **Close** ... wait a few minutes (you can check status with **Refresh**) + 1. Click **Release** diff --git a/README.md b/README.md index c7e523f4..6da19621 100644 --- a/README.md +++ b/README.md @@ -3,20 +3,23 @@ EasyPermissions is a wrapper library to simplify basic system permissions logic when targeting Android M or higher. +**Note:** If your app is written in Kotlin consider the [easypermissions-ktx](https://github.com/VMadalin/easypermissions-ktx) +library which adds Kotlin extensions to the core EasyPermissions library. + ## Installation EasyPermissions is installed by adding the following dependency to your `build.gradle` file: ```groovy dependencies { -    implementation 'pub.devrel:easypermissions:1.1.1' + // For developers using AndroidX in their applications +    implementation 'pub.devrel:easypermissions:3.0.0' + + // For developers using the Android Support Library + implementation 'pub.devrel:easypermissions:2.0.1' } ``` -Note that EasyPermissions depends on Android Support Library `27.0.1` so you will need to use -`compileSdkVersion 27` or higher. This change should be safe as `compileSdkVersion` does not change -app behavior. - ## Usage ### Basic @@ -128,6 +131,12 @@ these permissions from the user and they must be changed in app settings. You ca method `EasyPermissions.somePermissionPermanentlyDenied(...)` to display a dialog to the user in this situation and direct them to the system setting screen for your app: +**Note**: Due to a limitation in the information provided by the Android +framework permissions API, the `somePermissionPermanentlyDenied` 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. + ```java @Override public void onPermissionsDenied(int requestCode, List perms) { @@ -152,6 +161,26 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } ``` +### Interacting with the rationale dialog + +Implement the `EasyPermissions.RationaleCallbacks` if you want to interact with the rationale dialog. + +```java +@Override +public void onRationaleAccepted(int requestCode) { + // Rationale accepted to request some permissions + // ... +} + +@Override +public void onRationaleDenied(int requestCode) { + // Rationale denied to request some permissions + // ... +} +``` + +Rationale callbacks don't necessarily imply permission changes. To check for those, see the `EasyPermissions.PermissionCallbacks`. + ## LICENSE ``` @@ -171,7 +200,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { ``` -[1]: https://travis-ci.org/googlesamples/easypermissions.svg?branch=master -[2]: https://travis-ci.org/googlesamples/easypermissions +[1]: https://github.com/googlesamples/easypermissions/workflows/test/badge.svg +[2]: https://github.com/googlesamples/easypermissions/actions [3]: https://img.shields.io/badge/Android%20Weekly-%23185-2CB3E5.svg?style=flat [4]: http://androidweekly.net/issues/issue-185 diff --git a/app/build.gradle b/app/build.gradle index 3901b18e..731b6762 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,12 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion compileSdk + compileSdkVersion 30 + testOptions.unitTests.includeAndroidResources = true defaultConfig { applicationId "pub.devrel.easypermissions.sample" minSdkVersion 14 - targetSdkVersion targetSdk + targetSdkVersion 30 versionCode 1 versionName "1.0" } @@ -17,10 +18,18 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + compileOptions { + // Flag to enable support for the new language APIs + coreLibraryDesugaringEnabled false + // Sets Java compatibility to Java 8 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { - implementation "com.android.support:appcompat-v7:$support_library_version" - + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation "androidx.annotation:annotation:1.1.0" implementation project(':easypermissions') } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 5b86c085..3c7532c6 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,14 +1,10 @@ # Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html -# Add any project specific keep options here: - # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1a3388ff..88db6b13 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ + cameraTask()); // Button click listener that will request two permissions. - findViewById(R.id.button_location_and_contacts).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - locationAndContactsTask(); - } - }); + findViewById(R.id.button_location_and_contacts).setOnClickListener(v -> locationAndContactsTask()); } private boolean hasCameraPermission() { @@ -73,6 +64,10 @@ private boolean hasSmsPermission() { return EasyPermissions.hasPermissions(this, Manifest.permission.READ_SMS); } + private boolean hasStoragePermission() { + return EasyPermissions.hasPermissions(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); + } + @AfterPermissionGranted(RC_CAMERA_PERM) public void cameraTask() { if (hasCameraPermission()) { @@ -148,4 +143,14 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { .show(); } } + + @Override + public void onRationaleAccepted(int requestCode) { + Log.d(TAG, "onRationaleAccepted:" + requestCode); + } + + @Override + public void onRationaleDenied(int requestCode) { + Log.d(TAG, "onRationaleDenied:" + requestCode); + } } diff --git a/app/src/main/java/pub/devrel/easypermissions/sample/MainFragment.java b/app/src/main/java/pub/devrel/easypermissions/sample/MainFragment.java index 73ca2faa..55451d9f 100644 --- a/app/src/main/java/pub/devrel/easypermissions/sample/MainFragment.java +++ b/app/src/main/java/pub/devrel/easypermissions/sample/MainFragment.java @@ -2,14 +2,15 @@ import android.Manifest; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + import java.util.List; import pub.devrel.easypermissions.AfterPermissionGranted; @@ -24,7 +25,7 @@ public class MainFragment extends Fragment implements EasyPermissions.Permission private static final int RC_SMS_PERM = 122; @Override - public View onCreateView(LayoutInflater inflater, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); @@ -33,12 +34,7 @@ public View onCreateView(LayoutInflater inflater, View v = inflater.inflate(R.layout.fragment_main, container); // Button click listener - v.findViewById(R.id.button_sms).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - smsTask(); - } - }); + v.findViewById(R.id.button_sms).setOnClickListener(v1 -> smsTask()); return v; } @@ -55,7 +51,7 @@ public void onRequestPermissionsResult(int requestCode, @AfterPermissionGranted(RC_SMS_PERM) private void smsTask() { - if (EasyPermissions.hasPermissions(getContext(), Manifest.permission.READ_SMS)) { + if (EasyPermissions.hasPermissions(requireContext(), Manifest.permission.READ_SMS)) { // Have permission, do the thing! Toast.makeText(getActivity(), "TODO: SMS things", Toast.LENGTH_LONG).show(); } else { diff --git a/build.gradle b/build.gradle index c2ec530e..eac6482c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,11 +2,12 @@ buildscript { repositories { jcenter() google() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.vanniktech:gradle-maven-publish-plugin:0.14.2' } } @@ -20,21 +21,3 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir } - -ext { - projectName = 'EasyPermissions' - projectDesc = 'A wrapper library for basic Android M system permissions logic' - - githubUrl = 'https://github.com/googlesamples/easypermissions' - - mavenGroup = 'pub.devrel' - mavenArtifactId = 'easypermissions' - mavenVersion = '1.1.1' - - bintrayOrg = 'easygoogle' - - support_library_version = '27.0.1' - - compileSdk = 27 - targetSdk = 27 -} diff --git a/easypermissions/bintray.gradle b/easypermissions/bintray.gradle deleted file mode 100644 index 94e31875..00000000 --- a/easypermissions/bintray.gradle +++ /dev/null @@ -1,121 +0,0 @@ -apply plugin: 'com.jfrog.bintray' -apply plugin: 'maven-publish' - -group = mavenGroup -version = mavenVersion - -task sourcesJar(type: Jar) { - from android.sourceSets.main.java.srcDirs - classifier = 'sources' -} - -task javadoc(type: Javadoc) { - source = android.sourceSets.main.java.srcDirs - classpath += configurations.compile - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - - failOnError = false -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} - -artifacts { - archives sourcesJar - archives javadocJar -} - -publishing { - publications { - library(MavenPublication) { - - groupId mavenGroup - artifactId mavenArtifactId - version mavenVersion - - artifact "$buildDir/outputs/aar/$mavenArtifactId-release.aar" - artifact javadocJar - artifact sourcesJar - - pom.withXml { - // Name - asNode().appendNode('name', "EasyPermissions") - - // Description - asNode().appendNode('description', 'A wrapper library for basic Android M system permissions logic') - - // URL - asNode().appendNode('url', 'https://github.com/googlesamples/easypermissions') - - // Inception Year - asNode().appendNode('inceptionYear', '2015') - - // Licenses - def license = asNode().appendNode('licenses').appendNode('license') - license.appendNode('name', 'The Apache Software License, Version 2.0') - license.appendNode('url', 'http://www.apache.org/licenses/LICENSE-2.0.txt') - license.appendNode('distribution', 'repo') - - // Developers - def developer = asNode().appendNode('developers').appendNode('developer') - developer.appendNode('name', 'Google') - - // SCM - def scm = asNode().appendNode('scm') - scm.appendNode('connection', 'https://github.com/googlesamples/easypermissions.git') - scm.appendNode('url', 'https://github.com/googlesamples/easypermissions') - - // Dependencies - def dependenciesNode = asNode().getAt('dependencies')[0] - if (dependenciesNode == null) { - dependenciesNode = asNode().appendNode('dependencies') - } - - // Add all that are 'compile' - configurations.api.allDependencies.each { - def dependencyNode = dependenciesNode.appendNode('dependency') - dependencyNode.appendNode('groupId', it.group) - dependencyNode.appendNode('artifactId', it.name) - dependencyNode.appendNode('version', it.version) - } - } - } - } -} - -bintray { - user = hasProperty('BINTRAY_USER') ? getProperty('BINTRAY_USER') : System.getenv('BINTRAY_USER') - key = hasProperty('BINTRAY_KEY') ? getProperty('BINTRAY_KEY') : System.getenv('BINTRAY_KEY') - - filesSpec { - from "$buildDir/publications/library/pom-default.xml" - into "pub/devrel/$mavenArtifactId/$mavenVersion/" - rename { String fileName -> - "${mavenArtifactId}-${mavenVersion}.pom" - } - } - - configurations = [ 'archives' ] - - pkg { - repo = projectName - name = mavenArtifactId - userOrg = bintrayOrg - licenses = [ 'Apache-2.0' ] - vcsUrl = "${githubUrl}.git" - - version { - name = mavenVersion - released = new Date() - } - } -} - -afterEvaluate { project -> - def pomTask = "generatePomFileForLibraryPublication" - - // Convenience task to prepare everything we need for releases - task prepareArtifacts(dependsOn: [javadocJar, sourcesJar, assembleRelease, pomTask]) {} -} diff --git a/easypermissions/build.gradle b/easypermissions/build.gradle index 73731e51..5c6af4bc 100644 --- a/easypermissions/build.gradle +++ b/easypermissions/build.gradle @@ -1,30 +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 compileSdk + compileSdkVersion 30 + testOptions.unitTests.includeAndroidResources = true defaultConfig { minSdkVersion 14 - targetSdkVersion targetSdk + targetSdkVersion 30 versionCode 1 - versionName mavenVersion + versionName "3.0.0" } buildTypes { + debug { + testCoverageEnabled true + } release { minifyEnabled false consumerProguardFiles 'proguard-rules.pro' } } + + testOptions { + unitTests { + includeAndroidResources = true + } + } + } dependencies { - api "com.android.support:appcompat-v7:$support_library_version" - api "com.android.support:support-compat:$support_library_version" - api "com.android.support:support-fragment:$support_library_version" + 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.12' - testImplementation 'org.robolectric:robolectric:3.5.1' + 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' } - -apply from: 'bintray.gradle' diff --git a/easypermissions/gradle.properties b/easypermissions/gradle.properties new file mode 100644 index 00000000..3e0ef642 --- /dev/null +++ b/easypermissions/gradle.properties @@ -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 diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialog.java b/easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialog.java index eb22384b..4407e4fc 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialog.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialog.java @@ -6,23 +6,28 @@ import android.content.Intent; import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; -import android.support.annotation.StringRes; -import android.support.annotation.StyleRes; -import android.support.v4.app.Fragment; -import android.support.v7.app.AlertDialog; 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 {@link Activity#onActivityResult(int, int, Intent)}. + * Activity via {@see Activity#onActivityResult(int, int, Intent)}. *

* 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) @@ -47,6 +52,7 @@ public AppSettingsDialog[] newArray(int size) { private final String mPositiveButtonText; private final String mNegativeButtonText; private final int mRequestCode; + private final int mIntentFlags; private Object mActivityOrFragment; private Context mContext; @@ -58,6 +64,7 @@ private AppSettingsDialog(Parcel in) { mPositiveButtonText = in.readString(); mNegativeButtonText = in.readString(); mRequestCode = in.readInt(); + mIntentFlags = in.readInt(); } private AppSettingsDialog(@NonNull final Object activityOrFragment, @@ -66,7 +73,8 @@ private AppSettingsDialog(@NonNull final Object activityOrFragment, @Nullable String title, @Nullable String positiveButtonText, @Nullable String negativeButtonText, - int requestCode) { + int requestCode, + int intentFlags) { setActivityOrFragment(activityOrFragment); mThemeResId = themeResId; mRationale = rationale; @@ -74,10 +82,24 @@ private AppSettingsDialog(@NonNull final Object activityOrFragment, 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; } @@ -89,8 +111,6 @@ private void setActivityOrFragment(Object activityOrFragment) { mContext = (Activity) activityOrFragment; } else if (activityOrFragment instanceof Fragment) { mContext = ((Fragment) activityOrFragment).getContext(); - } else if (activityOrFragment instanceof android.app.Fragment) { - mContext = ((android.app.Fragment) activityOrFragment).getActivity(); } else { throw new IllegalStateException("Unknown object: " + activityOrFragment); } @@ -101,9 +121,6 @@ private void startForResult(Intent intent) { ((Activity) mActivityOrFragment).startActivityForResult(intent, mRequestCode); } else if (mActivityOrFragment instanceof Fragment) { ((Fragment) mActivityOrFragment).startActivityForResult(intent, mRequestCode); - } else if (mActivityOrFragment instanceof android.app.Fragment) { - ((android.app.Fragment) mActivityOrFragment).startActivityForResult(intent, - mRequestCode); } } @@ -120,7 +137,7 @@ public void show() { AlertDialog showDialog(DialogInterface.OnClickListener positiveListener, DialogInterface.OnClickListener negativeListener) { AlertDialog.Builder builder; - if (mThemeResId > 0) { + if (mThemeResId != -1) { builder = new AlertDialog.Builder(mContext, mThemeResId); } else { builder = new AlertDialog.Builder(mContext); @@ -147,6 +164,11 @@ public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mPositiveButtonText); dest.writeString(mNegativeButtonText); dest.writeInt(mRequestCode); + dest.writeInt(mIntentFlags); + } + + int getIntentFlags() { + return mIntentFlags; } /** @@ -163,6 +185,7 @@ public static class Builder { private String mPositiveButtonText; private String mNegativeButtonText; private int mRequestCode = -1; + private boolean mOpenInNewTask = false; /** * Create a new Builder for an {@link AppSettingsDialog}. @@ -184,16 +207,6 @@ public Builder(@NonNull Fragment fragment) { mContext = fragment.getContext(); } - /** - * Create a new Builder for an {@link AppSettingsDialog}. - * - * @param fragment the {@link android.app.Fragment} in which to display the dialog. - */ - public Builder(@NonNull android.app.Fragment fragment) { - mActivityOrFragment = fragment; - mContext = fragment.getActivity(); - } - /** * Set the dialog theme. */ @@ -265,7 +278,7 @@ public Builder setPositiveButton(@StringRes int textId) { * Set the negative button text, default is {@link android.R.string#cancel}. *

* To know if a user cancelled the request, check if your permissions were given with {@link - * EasyPermissions#hasPermissions(Context, String...)} in {@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. */ @@ -286,7 +299,7 @@ public Builder setNegativeButton(@StringRes int textId) { /** * Set the request code use when launching the Settings screen for result, can be retrieved - * in the calling Activity's {@link Activity#onActivityResult(int, int, Intent)} method. + * in the calling Activity's {@see Activity#onActivityResult(int, int, Intent)} method. * Default is {@link #DEFAULT_SETTINGS_REQ_CODE}. */ @NonNull @@ -295,6 +308,17 @@ public Builder setRequestCode(int 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()}. @@ -311,6 +335,11 @@ public AppSettingsDialog build() { 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, @@ -318,7 +347,8 @@ public AppSettingsDialog build() { mTitle, mPositiveButtonText, mNegativeButtonText, - mRequestCode); + mRequestCode, + intentFlags); } } diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialogHolderActivity.java b/easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialogHolderActivity.java index 0b1ff084..d996dd7f 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialogHolderActivity.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialogHolderActivity.java @@ -8,25 +8,29 @@ import android.net.Uri; import android.os.Bundle; import android.provider.Settings; -import android.support.annotation.RestrictTo; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; +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) { - return new Intent(context, AppSettingsDialogHolderActivity.class) - .putExtra(AppSettingsDialog.EXTRA_APP_SETTINGS, 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); - mDialog = AppSettingsDialog.fromIntent(getIntent(), this).showDialog(this, this); + AppSettingsDialog appSettingsDialog = AppSettingsDialog.fromIntent(getIntent(), this); + mIntentFlags = appSettingsDialog.getIntentFlags(); + mDialog = appSettingsDialog.showDialog(this, this); } @Override @@ -40,10 +44,10 @@ protected void onDestroy() { @Override public void onClick(DialogInterface dialog, int which) { if (which == Dialog.BUTTON_POSITIVE) { - startActivityForResult( - new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - .setData(Uri.fromParts("package", getPackageName(), null)), - APP_SETTINGS_RC); + 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(); diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java b/easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java index 07f18883..dfc20d0e 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java @@ -20,19 +20,17 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Size; -import android.support.annotation.StringRes; -import android.support.v4.app.ActivityCompat; -import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; +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; /** @@ -49,7 +47,15 @@ public interface PermissionCallbacks extends ActivityCompat.OnRequestPermissions void onPermissionsGranted(int requestCode, @NonNull List perms); void onPermissionsDenied(int requestCode, @NonNull List 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"; @@ -101,7 +107,7 @@ public static boolean hasPermissions(@NonNull Context context, */ public static void requestPermissions( @NonNull Activity host, @NonNull String rationale, - int requestCode, @Size(min = 1) @NonNull String... perms) { + @IntRange(from = 0, to = 255) int requestCode, @Size(min = 1) @NonNull String... perms) { requestPermissions( new PermissionRequest.Builder(host, requestCode, perms) .setRationale(rationale) @@ -115,86 +121,10 @@ public static void requestPermissions( */ public static void requestPermissions( @NonNull Fragment host, @NonNull String rationale, - int requestCode, @Size(min = 1) @NonNull String... perms) { - requestPermissions( - new PermissionRequest.Builder(host, requestCode, perms) - .setRationale(rationale) - .build()); - } - - /** - * Request permissions from a standard Fragment with standard OK/Cancel buttons. - * - * @see #requestPermissions(Activity, String, int, String...) - */ - public static void requestPermissions( - @NonNull android.app.Fragment host, @NonNull String rationale, - int requestCode, @Size(min = 1) @NonNull String... perms) { - requestPermissions( - new PermissionRequest.Builder(host, requestCode, perms) - .setRationale(rationale) - .build()); - } - - /** - * Request a set of permissions, showing 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 positiveButton custom text for positive button - * @param negativeButton custom text for negative button - * @param requestCode request code to track this request, must be < 256. - * @param perms a set of permissions to be requested. - * @see Manifest.permission - * @deprecated use {@link #requestPermissions(PermissionRequest)} instead - */ - @Deprecated - public static void requestPermissions( - @NonNull Activity host, @NonNull String rationale, - @StringRes int positiveButton, @StringRes int negativeButton, - int requestCode, @Size(min = 1) @NonNull String... perms) { - requestPermissions( - new PermissionRequest.Builder(host, requestCode, perms) - .setRationale(rationale) - .setPositiveButtonText(positiveButton) - .setNegativeButtonText(negativeButton) - .build()); - } - - /** - * Request permissions from a Support Fragment. - * - * @see #requestPermissions(Activity, String, int, int, int, String...) - * @deprecated use {@link #requestPermissions(PermissionRequest)} instead - */ - @Deprecated - public static void requestPermissions( - @NonNull Fragment host, @NonNull String rationale, - @StringRes int positiveButton, @StringRes int negativeButton, - int requestCode, @Size(min = 1) @NonNull String... perms) { - requestPermissions( - new PermissionRequest.Builder(host, requestCode, perms) - .setRationale(rationale) - .setPositiveButtonText(positiveButton) - .setNegativeButtonText(negativeButton) - .build()); - } - - /** - * @see #requestPermissions(Activity, String, int, int, int, String...) - * @deprecated use {@link #requestPermissions(PermissionRequest)} instead - */ - @Deprecated - public static void requestPermissions( - @NonNull android.app.Fragment host, @NonNull String rationale, - @StringRes int positiveButton, @StringRes int negativeButton, - int requestCode, @Size(min = 1) @NonNull String... perms) { + @IntRange(from = 0, to = 255) int requestCode, @Size(min = 1) @NonNull String... perms) { requestPermissions( new PermissionRequest.Builder(host, requestCode, perms) .setRationale(rationale) - .setPositiveButtonText(positiveButton) - .setNegativeButtonText(negativeButton) .build()); } @@ -238,7 +168,7 @@ public static void requestPermissions(PermissionRequest request) { * @param receivers an array of objects that have a method annotated with {@link * AfterPermissionGranted} or implement {@link PermissionCallbacks}. */ - public static void onRequestPermissionsResult(int requestCode, + public static void onRequestPermissionsResult(@IntRange(from = 0, to = 255) int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, @NonNull Object... receivers) { @@ -281,6 +211,12 @@ public static void onRequestPermissionsResult(int requestCode, * Check if at least one permission in the list of denied permissions has been permanently * denied (user clicked "Never ask again"). * + * Note: 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)} @@ -301,15 +237,6 @@ public static boolean somePermissionPermanentlyDenied(@NonNull Fragment host, .somePermissionPermanentlyDenied(deniedPermissions); } - /** - * @see #somePermissionPermanentlyDenied(Activity, List). - */ - public static boolean somePermissionPermanentlyDenied(@NonNull android.app.Fragment host, - @NonNull List deniedPermissions) { - return PermissionHelper.newInstance(host) - .somePermissionPermanentlyDenied(deniedPermissions); - } - /** * Check if a permission has been permanently denied (user clicked "Never ask again"). * @@ -330,14 +257,6 @@ public static boolean permissionPermanentlyDenied(@NonNull Fragment host, return PermissionHelper.newInstance(host).permissionPermanentlyDenied(deniedPermission); } - /** - * @see #permissionPermanentlyDenied(Activity, String). - */ - public static boolean permissionPermanentlyDenied(@NonNull android.app.Fragment host, - @NonNull String deniedPermission) { - return PermissionHelper.newInstance(host).permissionPermanentlyDenied(deniedPermission); - } - /** * See if some denied permission has been permanently denied. * @@ -359,14 +278,6 @@ public static boolean somePermissionDenied(@NonNull Fragment host, return PermissionHelper.newInstance(host).somePermissionDenied(perms); } - /** - * @see #somePermissionDenied(Activity, String...) - */ - public static boolean somePermissionDenied(@NonNull android.app.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}. diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java b/easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java index a9e4b1a2..02d2e6d1 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java @@ -1,13 +1,13 @@ package pub.devrel.easypermissions; import android.app.Activity; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; -import android.support.annotation.Size; -import android.support.annotation.StringRes; -import android.support.annotation.StyleRes; -import android.support.v4.app.Fragment; +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; @@ -150,16 +150,6 @@ public Builder(@NonNull Fragment fragment, int requestCode, mPerms = perms; } - /** - * @see #Builder(Activity, int, String...) - */ - public Builder(@NonNull android.app.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 diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogClickListener.java b/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogClickListener.java index 8cefc341..d07afffa 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogClickListener.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogClickListener.java @@ -3,8 +3,7 @@ import android.app.Activity; import android.app.Dialog; import android.content.DialogInterface; -import android.os.Build; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import java.util.Arrays; @@ -18,10 +17,12 @@ 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.PermissionCallbacks callbacks, + EasyPermissions.RationaleCallbacks rationaleCallbacks) { mHost = compatDialogFragment.getParentFragment() != null ? compatDialogFragment.getParentFragment() @@ -29,48 +30,48 @@ class RationaleDialogClickListener implements Dialog.OnClickListener { mConfig = config; mCallbacks = callbacks; + mRationaleCallbacks = rationaleCallbacks; + } RationaleDialogClickListener(RationaleDialogFragment dialogFragment, RationaleDialogConfig config, - EasyPermissions.PermissionCallbacks callbacks) { + EasyPermissions.PermissionCallbacks callbacks, + EasyPermissions.RationaleCallbacks dialogCallback) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - mHost = dialogFragment.getParentFragment() != null ? - dialogFragment.getParentFragment() : - dialogFragment.getActivity(); - } else { - mHost = dialogFragment.getActivity(); - } + 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( - mConfig.requestCode, mConfig.permissions); - } else if (mHost instanceof android.app.Fragment) { - PermissionHelper.newInstance((android.app.Fragment) mHost).directRequestPermissions( - mConfig.requestCode, mConfig.permissions); + PermissionHelper.newInstance((Fragment) mHost).directRequestPermissions(requestCode, permissions); } else if (mHost instanceof Activity) { - PermissionHelper.newInstance((Activity) mHost).directRequestPermissions( - mConfig.requestCode, mConfig.permissions); + 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)); + mCallbacks.onPermissionsDenied(mConfig.requestCode, Arrays.asList(mConfig.permissions)); } } } diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogConfig.java b/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogConfig.java index b829e4a7..c64999c6 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogConfig.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogConfig.java @@ -3,9 +3,9 @@ import android.app.Dialog; import android.content.Context; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.StyleRes; -import android.support.v7.app.AlertDialog; +import androidx.annotation.NonNull; +import androidx.annotation.StyleRes; +import androidx.appcompat.app.AlertDialog; /** * Configuration for either {@link RationaleDialogFragment} or {@link RationaleDialogFragmentCompat}. diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragment.java b/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragment.java index 9718a744..c055a4f7 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragment.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragment.java @@ -6,9 +6,9 @@ import android.content.Context; import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; -import android.support.annotation.StyleRes; +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 @@ -20,6 +20,7 @@ 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( @@ -44,13 +45,23 @@ public static RationaleDialogFragment newInstance( @Override public void onAttach(Context context) { super.onAttach(context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 - && getParentFragment() != null - && getParentFragment() instanceof EasyPermissions.PermissionCallbacks) { - mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) getParentFragment(); - } else if (context instanceof EasyPermissions.PermissionCallbacks) { + 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 @@ -93,7 +104,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { // Get config from arguments, create click listener RationaleDialogConfig config = new RationaleDialogConfig(getArguments()); RationaleDialogClickListener clickListener = - new RationaleDialogClickListener(this, config, mPermissionCallbacks); + new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks); // Create an AlertDialog return config.createFrameworkDialog(getActivity(), clickListener); diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java b/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java index bd801992..5a9306f4 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java @@ -3,11 +3,11 @@ import android.app.Dialog; import android.content.Context; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.RestrictTo; -import android.support.annotation.StyleRes; -import android.support.v4.app.FragmentManager; -import android.support.v7.app.AppCompatDialogFragment; +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 @@ -19,6 +19,7 @@ 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, @@ -54,17 +55,29 @@ public void showAllowingStateLoss(FragmentManager manager, String tag) { @Override public void onAttach(Context context) { super.onAttach(context); - if (getParentFragment() != null && getParentFragment() instanceof EasyPermissions.PermissionCallbacks) { - mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) getParentFragment(); - } else if (context instanceof EasyPermissions.PermissionCallbacks) { + 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 @@ -76,7 +89,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { // Get config from arguments, create click listener RationaleDialogConfig config = new RationaleDialogConfig(getArguments()); RationaleDialogClickListener clickListener = - new RationaleDialogClickListener(this, config, mPermissionCallbacks); + new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks); // Create an AlertDialog return config.createSupportDialog(getContext(), clickListener); diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java index 7be70ff8..74aa4228 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java @@ -1,25 +1,26 @@ package pub.devrel.easypermissions.helper; import android.app.Activity; +import android.app.Fragment; import android.app.FragmentManager; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; +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 BaseFrameworkPermissionsHelper { +class ActivityPermissionHelper extends PermissionHelper { + private static final String TAG = "ActPermissionHelper"; public ActivityPermissionHelper(Activity host) { super(host); } - @Override - public FragmentManager getFragmentManager() { - return getHost().getFragmentManager(); - } - @Override public void directRequestPermissions(int requestCode, @NonNull String... perms) { ActivityCompat.requestPermissions(getHost(), perms, requestCode); @@ -34,4 +35,25 @@ public boolean shouldShowRequestPermissionRationale(@NonNull String perm) { 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); + } } diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/AppCompatActivityPermissionHelper.java b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/AppCompatActivityPermissionsHelper.java similarity index 67% rename from easypermissions/src/main/java/pub/devrel/easypermissions/helper/AppCompatActivityPermissionHelper.java rename to easypermissions/src/main/java/pub/devrel/easypermissions/helper/AppCompatActivityPermissionsHelper.java index f0a4c628..0bc80a59 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/AppCompatActivityPermissionHelper.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/AppCompatActivityPermissionsHelper.java @@ -1,17 +1,17 @@ package pub.devrel.easypermissions.helper; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; -import android.support.v4.app.FragmentManager; -import android.support.v7.app.AppCompatActivity; +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 AppCompatActivityPermissionHelper extends BaseSupportPermissionsHelper { +class AppCompatActivityPermissionsHelper extends BaseSupportPermissionsHelper { - public AppCompatActivityPermissionHelper(AppCompatActivity host) { + public AppCompatActivityPermissionsHelper(AppCompatActivity host) { super(host); } diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/BaseFrameworkPermissionsHelper.java b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/BaseFrameworkPermissionsHelper.java deleted file mode 100644 index 8e88a739..00000000 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/BaseFrameworkPermissionsHelper.java +++ /dev/null @@ -1,31 +0,0 @@ -package pub.devrel.easypermissions.helper; - -import android.app.FragmentManager; -import android.support.annotation.NonNull; -import android.support.annotation.StyleRes; - -import pub.devrel.easypermissions.RationaleDialogFragment; - -/** - * Implementation of {@link PermissionHelper} for framework host classes. - */ -public abstract class BaseFrameworkPermissionsHelper extends PermissionHelper { - - public BaseFrameworkPermissionsHelper(@NonNull T host) { - super(host); - } - - public abstract FragmentManager getFragmentManager(); - - @Override - public void showRequestPermissionRationale(@NonNull String rationale, - @NonNull String positiveButton, - @NonNull String negativeButton, - @StyleRes int theme, - int requestCode, - @NonNull String... perms) { - RationaleDialogFragment - .newInstance(positiveButton, negativeButton, rationale, theme, requestCode, perms) - .showAllowingStateLoss(getFragmentManager(), RationaleDialogFragment.TAG); - } -} diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/BaseSupportPermissionsHelper.java b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/BaseSupportPermissionsHelper.java index 11952e12..185da5a2 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/BaseSupportPermissionsHelper.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/BaseSupportPermissionsHelper.java @@ -1,8 +1,10 @@ package pub.devrel.easypermissions.helper; -import android.support.annotation.NonNull; -import android.support.annotation.StyleRes; -import android.support.v4.app.FragmentManager; +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; @@ -11,6 +13,8 @@ */ public abstract class BaseSupportPermissionsHelper extends PermissionHelper { + private static final String TAG = "BSPermissionsHelper"; + public BaseSupportPermissionsHelper(@NonNull T host) { super(host); } @@ -24,8 +28,18 @@ public void showRequestPermissionRationale(@NonNull String rationale, @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(getSupportFragmentManager(), RationaleDialogFragmentCompat.TAG); + .showAllowingStateLoss(fm, RationaleDialogFragmentCompat.TAG); } } diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/FrameworkFragmentPermissionHelper.java b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/FrameworkFragmentPermissionHelper.java deleted file mode 100644 index 501a2e9c..00000000 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/FrameworkFragmentPermissionHelper.java +++ /dev/null @@ -1,42 +0,0 @@ -package pub.devrel.easypermissions.helper; - -import android.annotation.SuppressLint; -import android.app.Fragment; -import android.app.FragmentManager; -import android.content.Context; -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.RequiresApi; - -/** - * Permissions helper for {@link Fragment} from the framework. - */ -class FrameworkFragmentPermissionHelper extends BaseFrameworkPermissionsHelper { - - public FrameworkFragmentPermissionHelper(@NonNull Fragment host) { - super(host); - } - - @Override - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) - public FragmentManager getFragmentManager() { - return getHost().getChildFragmentManager(); - } - - @Override - @SuppressLint("NewApi") - public void directRequestPermissions(int requestCode, @NonNull String... perms) { - getHost().requestPermissions(perms, requestCode); - } - - @Override - @SuppressLint("NewApi") - public boolean shouldShowRequestPermissionRationale(@NonNull String perm) { - return getHost().shouldShowRequestPermissionRationale(perm); - } - - @Override - public Context getContext() { - return getHost().getActivity(); - } -} diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/LowApiPermissionsHelper.java b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/LowApiPermissionsHelper.java index 92a93b5b..6ec74376 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/LowApiPermissionsHelper.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/LowApiPermissionsHelper.java @@ -2,9 +2,9 @@ import android.app.Activity; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.StyleRes; -import android.support.v4.app.Fragment; +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. @@ -40,8 +40,6 @@ public Context getContext() { return (Context) getHost(); } else if (getHost() instanceof Fragment) { return ((Fragment) getHost()).getContext(); - } else if (getHost() instanceof android.app.Fragment) { - return ((Fragment) getHost()).getContext(); } else { throw new IllegalStateException("Unknown host: " + getHost()); } diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java index e278120b..db95a350 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java @@ -3,10 +3,10 @@ import android.app.Activity; import android.content.Context; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.StyleRes; -import android.support.v4.app.Fragment; -import android.support.v7.app.AppCompatActivity; +import androidx.annotation.NonNull; +import androidx.annotation.StyleRes; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AppCompatActivity; import java.util.List; @@ -24,7 +24,7 @@ public static PermissionHelper newInstance(Activity host) { } if (host instanceof AppCompatActivity) - return new AppCompatActivityPermissionHelper((AppCompatActivity) host); + return new AppCompatActivityPermissionsHelper((AppCompatActivity) host); else { return new ActivityPermissionHelper(host); } @@ -39,15 +39,6 @@ public static PermissionHelper newInstance(Fragment host) { return new SupportFragmentPermissionHelper(host); } - @NonNull - public static PermissionHelper newInstance(android.app.Fragment host) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return new LowApiPermissionsHelper<>(host); - } - - return new FrameworkFragmentPermissionHelper(host); - } - // ============================================================================ // Public concrete methods // ============================================================================ diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/SupportFragmentPermissionHelper.java b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/SupportFragmentPermissionHelper.java index 0b7e6437..cf72eeb3 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/SupportFragmentPermissionHelper.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/SupportFragmentPermissionHelper.java @@ -1,9 +1,9 @@ package pub.devrel.easypermissions.helper; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; /** * Permissions helper for {@link Fragment} from the support library. diff --git a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/package-info.java b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/package-info.java index c745cb16..61617bce 100644 --- a/easypermissions/src/main/java/pub/devrel/easypermissions/helper/package-info.java +++ b/easypermissions/src/main/java/pub/devrel/easypermissions/helper/package-info.java @@ -1,4 +1,4 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) package pub.devrel.easypermissions.helper; -import android.support.annotation.RestrictTo; +import androidx.annotation.RestrictTo; diff --git a/easypermissions/src/test/java/pub/devrel/easypermissions/AppSettingsDialogTest.java b/easypermissions/src/test/java/pub/devrel/easypermissions/AppSettingsDialogTest.java new file mode 100644 index 00000000..2b353846 --- /dev/null +++ b/easypermissions/src/test/java/pub/devrel/easypermissions/AppSettingsDialogTest.java @@ -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 fragmentController; + private ActivityController activityController; + @Mock + private DialogInterface.OnClickListener positiveListener; + @Mock + private DialogInterface.OnClickListener negativeListener; + @Captor + private ArgumentCaptor integerCaptor; + @Captor + private ArgumentCaptor 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()); + } + +} diff --git a/easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsLowApiTest.java b/easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsLowApiTest.java index 13a430ee..a6c6976b 100644 --- a/easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsLowApiTest.java +++ b/easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsLowApiTest.java @@ -2,24 +2,120 @@ 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.RuntimeEnvironment; import org.robolectric.annotation.Config; -import static junit.framework.Assert.assertTrue; +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(constants = BuildConfig.class, sdk = 19) +@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 fragmentController; + private ActivityController activityController; + private ActivityController supportFragmentActivityController; + private ActivityController appCompatActivityController; + @Captor + private ArgumentCaptor integerCaptor; + @Captor + private ArgumentCaptor> 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 testHasPermissions() { - // On low-API devices, we should always get 'true' when we call 'hasPermissions' - assertTrue(EasyPermissions.hasPermissions(RuntimeEnvironment.application, - Manifest.permission.ACCESS_COARSE_LOCATION)); + 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); + } + } diff --git a/easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsTest.java b/easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsTest.java index 8d4c20dc..06ce723c 100644 --- a/easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsTest.java +++ b/easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsTest.java @@ -2,41 +2,610 @@ 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.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowApplication; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; +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(constants = BuildConfig.class, sdk = 23) +@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 fragmentController; + private ActivityController activityController; + private ActivityController supportFragmentActivityController; + private ActivityController appCompatActivityController; + @Captor + private ArgumentCaptor integerCaptor; + @Captor + private ArgumentCaptor> 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 testHasPermissions() { - Application app = RuntimeEnvironment.application; + public void shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromAppCompatActivity() { + grantPermissions(ONE_PERM); + showRationale(true, ALL_PERMS); - String[] perms = new String[]{ - Manifest.permission.READ_SMS, - Manifest.permission.ACCESS_FINE_LOCATION - }; + EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS); - // Wes should not have permissions before any are granted - assertFalse(EasyPermissions.hasPermissions(app, perms)); + androidx.fragment.app.Fragment dialogFragment = spyAppCompatActivity.getSupportFragmentManager() + .findFragmentByTag(RationaleDialogFragmentCompat.TAG); + assertThat(dialogFragment).isInstanceOf(RationaleDialogFragmentCompat.class); - // Granting one permission should not make the whole set appear granted - ShadowApplication.getInstance().grantPermissions(perms[0]); - assertFalse(EasyPermissions.hasPermissions(app, perms)); + 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); + } - // Granting all permissions should make the whole set granted - ShadowApplication.getInstance().grantPermissions(perms); - assertTrue(EasyPermissions.hasPermissions(app, 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); + } } } diff --git a/easypermissions/src/test/java/pub/devrel/easypermissions/RationaleDialogClickListenerTest.java b/easypermissions/src/test/java/pub/devrel/easypermissions/RationaleDialogClickListenerTest.java new file mode 100644 index 00000000..c05f99b1 --- /dev/null +++ b/easypermissions/src/test/java/pub/devrel/easypermissions/RationaleDialogClickListenerTest.java @@ -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.anyList()); + } +} diff --git a/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/ActivityController.java b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/ActivityController.java new file mode 100644 index 00000000..fc44a2ec --- /dev/null +++ b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/ActivityController.java @@ -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 { + + private ActivityScenario scenario; + + public ActivityController(Class clazz) { + scenario = ActivityScenario.launch(clazz); + } + + public synchronized T resume() { + final CompletableFuture ActivityFuture = new CompletableFuture<>(); + + scenario.onActivity(new ActivityScenario.ActivityAction() { + @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(); + } + +} diff --git a/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/FragmentController.java b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/FragmentController.java new file mode 100644 index 00000000..60447a65 --- /dev/null +++ b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/FragmentController.java @@ -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 { + + private FragmentScenario scenario; + + public FragmentController(Class clazz) { + scenario = FragmentScenario.launch(clazz); + } + + public synchronized T resume() { + final CompletableFuture fragmentFuture = new CompletableFuture<>(); + + scenario.onFragment(new FragmentScenario.FragmentAction() { + @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(); + } + +} diff --git a/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestActivity.java b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestActivity.java new file mode 100644 index 00000000..cc1b14c0 --- /dev/null +++ b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestActivity.java @@ -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 perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, @NonNull List perms) { + + } + + @AfterPermissionGranted(REQUEST_CODE) + public void afterPermissionGranted() { + + } + + @Override + public void onRationaleAccepted(int requestCode) { + + } + + @Override + public void onRationaleDenied(int requestCode) { + + } +} diff --git a/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestAppCompatActivity.java b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestAppCompatActivity.java new file mode 100644 index 00000000..3ca8accc --- /dev/null +++ b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestAppCompatActivity.java @@ -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 perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, @NonNull List perms) { + + } + + @AfterPermissionGranted(REQUEST_CODE) + public void afterPermissionGranted() { + + } + + @Override + public void onRationaleAccepted(int requestCode) { + + } + + @Override + public void onRationaleDenied(int requestCode) { + + } + +} diff --git a/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestFragment.java b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestFragment.java new file mode 100644 index 00000000..96dbbcbe --- /dev/null +++ b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestFragment.java @@ -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 perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, @NonNull List perms) { + + } + + @AfterPermissionGranted(REQUEST_CODE) + public void afterPermissionGranted() { + + } + + @Override + public void onRationaleAccepted(int requestCode) { + + } + + @Override + public void onRationaleDenied(int requestCode) { + + } +} diff --git a/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestSupportFragmentActivity.java b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestSupportFragmentActivity.java new file mode 100644 index 00000000..83187e00 --- /dev/null +++ b/easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestSupportFragmentActivity.java @@ -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 perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, @NonNull List perms) { + + } + + @AfterPermissionGranted(REQUEST_CODE) + public void afterPermissionGranted() { + + } + + @Override + public void onRationaleAccepted(int requestCode) { + + } + + @Override + public void onRationaleDenied(int requestCode) { + + } +} diff --git a/gradle.properties b/gradle.properties index e8cf5b93..43a055e2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,11 +9,19 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx10248m -XX:MaxPermSize=256m -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +# Configure on demand +org.gradle.configureondemand=true + +# Required by Robolectric 4.x +#android.enableUnitTestBinaryResources=true + +# Move transitive dependencies to AndroidX +android.useAndroidX=true +android.enableJetifier=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 6b6ea3ab..e708b1c0 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 702c4b68..442d9132 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip diff --git a/gradlew b/gradlew index cccdd3d5..4f906e0c 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index e95643d6..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell