master 2b2b5c40d11c cached
60 files
163.6 KB
36.1k tokens
276 symbols
1 requests
Download .txt
Repository: googlesamples/easypermissions
Branch: master
Commit: 2b2b5c40d11c
Files: 60
Total size: 163.6 KB

Directory structure:
gitextract_zbuou_b2/

├── .github/
│   ├── ISSUE_TEMPLATE.md
│   └── workflows/
│       └── test.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── PUBLISHING.md
├── README.md
├── app/
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── pub/
│           │       └── devrel/
│           │           └── easypermissions/
│           │               └── sample/
│           │                   ├── MainActivity.java
│           │                   └── MainFragment.java
│           └── res/
│               ├── layout/
│               │   ├── activity_basic.xml
│               │   ├── activity_main.xml
│               │   └── fragment_main.xml
│               ├── values/
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               └── values-w820dp/
│                   └── dimens.xml
├── build.gradle
├── easypermissions/
│   ├── build.gradle
│   ├── gradle.properties
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── pub/
│       │   │       └── devrel/
│       │   │           └── easypermissions/
│       │   │               ├── AfterPermissionGranted.java
│       │   │               ├── AppSettingsDialog.java
│       │   │               ├── AppSettingsDialogHolderActivity.java
│       │   │               ├── EasyPermissions.java
│       │   │               ├── PermissionRequest.java
│       │   │               ├── RationaleDialogClickListener.java
│       │   │               ├── RationaleDialogConfig.java
│       │   │               ├── RationaleDialogFragment.java
│       │   │               ├── RationaleDialogFragmentCompat.java
│       │   │               └── helper/
│       │   │                   ├── ActivityPermissionHelper.java
│       │   │                   ├── AppCompatActivityPermissionsHelper.java
│       │   │                   ├── BaseSupportPermissionsHelper.java
│       │   │                   ├── LowApiPermissionsHelper.java
│       │   │                   ├── PermissionHelper.java
│       │   │                   ├── SupportFragmentPermissionHelper.java
│       │   │                   └── package-info.java
│       │   └── res/
│       │       └── values/
│       │           ├── colors.xml
│       │           ├── strings.xml
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── pub/
│                   └── devrel/
│                       └── easypermissions/
│                           ├── AppSettingsDialogTest.java
│                           ├── EasyPermissionsLowApiTest.java
│                           ├── EasyPermissionsTest.java
│                           ├── RationaleDialogClickListenerTest.java
│                           └── testhelper/
│                               ├── ActivityController.java
│                               ├── FragmentController.java
│                               ├── TestActivity.java
│                               ├── TestAppCompatActivity.java
│                               ├── TestFragment.java
│                               └── TestSupportFragmentActivity.java
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
## Basic Information

Device type: ________
OS version: ________
EasyPermissions version: ________

## Describe the problem

What happened?  What did you expect to happen?

## Code and logs

```
// TODO(you): show the code that produces the problem,
//            and any relevant logs.
```


================================================
FILE: .github/workflows/test.yml
================================================
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


================================================
FILE: .gitignore
================================================
# Gradle files
.gradle
build

# Local configuration file (sdk path, etc)
local.properties

# IntelliJ project files
**.iml
.idea

# Android Studio captures folder
captures/

# Misc
.DS_Store
.classpath
.project
.settings
.vscode


================================================
FILE: CONTRIBUTING.md
================================================
Not Found


================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.

"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.

"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.

2. Grant of Copyright License.

Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.

3. Grant of Patent License.

Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.

4. Redistribution.

You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:

You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.

5. Submission of Contributions.

Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.

6. Trademarks.

This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.

7. Disclaimer of Warranty.

Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.

8. Limitation of Liability.

In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability.

While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work

To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.

   Copyright 2017 Google

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: PUBLISHING.md
================================================
## 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 <YOUR KEY ID>
```

### Local Properties

Open your `$HOME/.gradle/gradle.properties` file at and fill in the values:

```
signing.keyId=<KEY ID>
signing.password=<PASSWORD YOU CHOSE>
signing.secretKeyRingFile=<FULL PATH TO YOUR GPG FILE>
mavenCentralRepositoryUsername=firebase-sonatype
mavenCentralRepositoryUsername=<PASSWORD FROM VALENTINE>
```

### 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**


================================================
FILE: README.md
================================================
# EasyPermissions [![Build Status][1]][2] [![Android Weekly][3]][4]

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 {
    // 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'
}
```

## Usage

### Basic

To begin using EasyPermissions, have your `Activity` (or `Fragment`) override the `onRequestPermissionsResult` method:

```java
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        // Forward results to EasyPermissions
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }
}
```

### Request Permissions

The example below shows how to request permissions for a method that requires both
`CAMERA` and `ACCESS_FINE_LOCATION` permissions. There are a few things to note:

  * Using `EasyPermissions#hasPermissions(...)` to check if the app already has the
    required permissions. This method can take any number of permissions as its final
    argument.
  * Requesting permissions with `EasyPermissions#requestPermissions`. This method
    will request the system permissions and show the rationale string provided if
    necessary. The request code provided should be unique to this request, and the method
    can take any number of permissions as its final argument.
  * Use of the `AfterPermissionGranted` annotation. This is optional, but provided for
    convenience. If all of the permissions in a given request are granted, *all* methods
    annotated with the proper request code will be executed(be sure to have an unique request code). The annotated method needs to be *void* and *without input parameters* (instead, you can use *onSaveInstanceState* in order to keep the state of your suppressed parameters). This is to simplify the common
    flow of needing to run the requesting method after all of its permissions have been granted.
    This can also be achieved by adding logic on the `onPermissionsGranted` callback.

```java
@AfterPermissionGranted(RC_CAMERA_AND_LOCATION)
private void methodRequiresTwoPermission() {
    String[] perms = {Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION};
    if (EasyPermissions.hasPermissions(this, perms)) {
        // Already have permission, do the thing
        // ...
    } else {
        // Do not have permissions, request them now
        EasyPermissions.requestPermissions(this, getString(R.string.camera_and_location_rationale),
                RC_CAMERA_AND_LOCATION, perms);
    }
}
```

Or for finer control over the rationale dialog, use a `PermissionRequest`:

```java
EasyPermissions.requestPermissions(
        new PermissionRequest.Builder(this, RC_CAMERA_AND_LOCATION, perms)
                .setRationale(R.string.camera_and_location_rationale)
                .setPositiveButtonText(R.string.rationale_ask_ok)
                .setNegativeButtonText(R.string.rationale_ask_cancel)
                .setTheme(R.style.my_fancy_style)
                .build());
```

Optionally, for a finer control, you can have your `Activity` / `Fragment` implement
the `PermissionCallbacks` interface.

```java
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        // Forward results to EasyPermissions
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    @Override
    public void onPermissionsGranted(int requestCode, List<String> list) {
        // Some permissions have been granted
        // ...
    }

    @Override
    public void onPermissionsDenied(int requestCode, List<String> list) {
        // Some permissions have been denied
        // ...
    }
}
```

### Required Permissions

In some cases your app will not function properly without certain permissions. If the user
denies these permissions with the "Never Ask Again" option, you will be unable to request
these permissions from the user and they must be changed in app settings. You can use the
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<String> perms) {
    Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());

    // (Optional) Check whether the user denied any permissions and checked "NEVER ASK AGAIN."
    // This will display a dialog directing them to enable the permission in app settings.
    if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
        new AppSettingsDialog.Builder(this).build().show();
    }
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) {
        // Do something after user returned from app settings screen, like showing a Toast.
        Toast.makeText(this, R.string.returned_from_app_settings_to_activity, Toast.LENGTH_SHORT)
                .show();
    }
}
```

### 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

```
	Copyright 2017 Google

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

```

[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


================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'

android {
    compileSdkVersion 30
    testOptions.unitTests.includeAndroidResources = true

    defaultConfig {
        applicationId "pub.devrel.easypermissions.sample"
        minSdkVersion 14
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled true
            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 'androidx.appcompat:appcompat:1.1.0'
    implementation "androidx.annotation:annotation:1.1.0"
    implementation project(':easypermissions')
}


================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# 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

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="pub.devrel.easypermissions.sample">

    <uses-permission android:name="android.permission.CAMERA" />

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />

    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:label="@string/app_name"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="AllowBackup,GoogleAppIndexingWarning">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>


================================================
FILE: app/src/main/java/pub/devrel/easypermissions/sample/MainActivity.java
================================================
/*
 * Copyright Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package pub.devrel.easypermissions.sample;

import android.Manifest;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import java.util.List;

import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.AppSettingsDialog;
import pub.devrel.easypermissions.EasyPermissions;

public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks,
                                                               EasyPermissions.RationaleCallbacks{

    private static final String TAG = "MainActivity";
    private static final String[] LOCATION_AND_CONTACTS =
            {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CONTACTS};

    private static final int RC_CAMERA_PERM = 123;
    private static final int RC_LOCATION_CONTACTS_PERM = 124;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Button click listener that will request one permission.
        findViewById(R.id.button_camera).setOnClickListener(v -> cameraTask());

        // Button click listener that will request two permissions.
        findViewById(R.id.button_location_and_contacts).setOnClickListener(v -> locationAndContactsTask());
    }

    private boolean hasCameraPermission() {
        return EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA);
    }

    private boolean hasLocationAndContactsPermissions() {
        return EasyPermissions.hasPermissions(this, LOCATION_AND_CONTACTS);
    }

    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()) {
            // Have permission, do the thing!
            Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
        } else {
            // Ask for one permission
            EasyPermissions.requestPermissions(
                    this,
                    getString(R.string.rationale_camera),
                    RC_CAMERA_PERM,
                    Manifest.permission.CAMERA);
        }
    }

    @AfterPermissionGranted(RC_LOCATION_CONTACTS_PERM)
    public void locationAndContactsTask() {
        if (hasLocationAndContactsPermissions()) {
            // Have permissions, do the thing!
            Toast.makeText(this, "TODO: Location and Contacts things", Toast.LENGTH_LONG).show();
        } else {
            // Ask for both permissions
            EasyPermissions.requestPermissions(
                    this,
                    getString(R.string.rationale_location_contacts),
                    RC_LOCATION_CONTACTS_PERM,
                    LOCATION_AND_CONTACTS);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        // EasyPermissions handles the request result.
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
        Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());
    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());

        // (Optional) Check whether the user denied any permissions and checked "NEVER ASK AGAIN."
        // This will display a dialog directing them to enable the permission in app settings.
        if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
            new AppSettingsDialog.Builder(this).build().show();
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) {
            String yes = getString(R.string.yes);
            String no = getString(R.string.no);

            // Do something after user returned from app settings screen, like showing a Toast.
            Toast.makeText(
                    this,
                    getString(R.string.returned_from_app_settings_to_activity,
                              hasCameraPermission() ? yes : no,
                              hasLocationAndContactsPermissions() ? yes : no,
                              hasSmsPermission() ? yes : no),
                    Toast.LENGTH_LONG)
                    .show();
        }
    }

    @Override
    public void onRationaleAccepted(int requestCode) {
        Log.d(TAG, "onRationaleAccepted:" + requestCode);
    }

    @Override
    public void onRationaleDenied(int requestCode) {
        Log.d(TAG, "onRationaleDenied:" + requestCode);
    }
}


================================================
FILE: app/src/main/java/pub/devrel/easypermissions/sample/MainFragment.java
================================================
package pub.devrel.easypermissions.sample;

import android.Manifest;
import android.os.Bundle;
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;
import pub.devrel.easypermissions.EasyPermissions;

/**
 * Created in {@link R.layout#activity_main}
 */
public class MainFragment extends Fragment implements EasyPermissions.PermissionCallbacks {

    private static final String TAG = "MainFragment";
    private static final int RC_SMS_PERM = 122;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container,
                             Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);

        // Create view
        View v = inflater.inflate(R.layout.fragment_main, container);

        // Button click listener
        v.findViewById(R.id.button_sms).setOnClickListener(v1 -> smsTask());

        return v;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        // EasyPermissions handles the request result.
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    @AfterPermissionGranted(RC_SMS_PERM)
    private void smsTask() {
        if (EasyPermissions.hasPermissions(requireContext(), Manifest.permission.READ_SMS)) {
            // Have permission, do the thing!
            Toast.makeText(getActivity(), "TODO: SMS things", Toast.LENGTH_LONG).show();
        } else {
            // Request one permission
            EasyPermissions.requestPermissions(this, getString(R.string.rationale_sms),
                    RC_SMS_PERM, Manifest.permission.READ_SMS);
        }
    }

    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
        Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());
    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());
    }
}


================================================
FILE: app/src/main/res/layout/activity_basic.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".BasicActivity">

    <Button
        android:id="@+id/button_request"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Request"
        tools:ignore="HardcodedText"/>

</LinearLayout>


================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <Button
        android:id="@+id/button_camera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/camera" />

    <Button
        android:id="@+id/button_location_and_contacts"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/location_and_contacts" />

    <fragment
        android:id="@+id/fragment"
        android:name="pub.devrel.easypermissions.sample.MainFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:layout="@layout/fragment_main" />

</LinearLayout>


================================================
FILE: app/src/main/res/layout/fragment_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <Button
        android:id="@+id/button_sms"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/sms" />

</FrameLayout>


================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>


================================================
FILE: app/src/main/res/values/dimens.xml
================================================
<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
</resources>


================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
    <string name="app_name">Easy Permissions</string>
    <string name="yes">Yes</string>
    <string name="no">No</string>

    <string name="rationale_camera">This app needs access to your camera so you can take pictures.</string>
    <string name="rationale_location_contacts">This app needs access to your location and contacts to know where and who you are.</string>
    <string name="rationale_sms">This app needs access to your sms to read all your great messages.</string>
    <string name="returned_from_app_settings_to_activity">
        Returned from app settings to MainActivity with the following permissions:
        \n\nCamera: %s
        \nLocation &amp; Contacts: %s
        \nSMS: %s
    </string>
    <string name="camera">Camera</string>
    <string name="location_and_contacts">Location and Contacts</string>
    <string name="sms">SMS</string>
</resources>


================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>


================================================
FILE: app/src/main/res/values-w820dp/dimens.xml
================================================
<resources>
    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
         (such as screen margins) for screens with more than 820dp of available width. This
         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
    <dimen name="activity_horizontal_margin">64dp</dimen>
</resources>


================================================
FILE: build.gradle
================================================
buildscript {
    repositories {
        jcenter()
        google()
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.3'
        classpath 'com.vanniktech:gradle-maven-publish-plugin:0.14.2'
    }
}

allprojects {
    repositories {
        jcenter()
        google()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


================================================
FILE: easypermissions/build.gradle
================================================
apply plugin: 'com.android.library'

// See: https://github.com/vanniktech/gradle-maven-publish-plugin/issues/206
ext {
  RELEASE_REPOSITORY_URL = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
  SNAPSHOT_REPOSITORY_URL = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
}

apply plugin: 'com.vanniktech.maven.publish'

android {
    compileSdkVersion 30
    testOptions.unitTests.includeAndroidResources = true

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 30
        versionCode 1
        versionName "3.0.0"
    }

    buildTypes {
        debug {
            testCoverageEnabled true
        }
        release {
            minifyEnabled false
            consumerProguardFiles 'proguard-rules.pro'
        }
    }

    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }

}

dependencies {
    api "androidx.appcompat:appcompat:1.1.0"
    api "androidx.annotation:annotation:1.1.0"
    api "androidx.core:core:1.3.0"
    api "androidx.fragment:fragment:1.2.5"

    testImplementation 'junit:junit:4.13'
    testImplementation 'com.google.truth:truth:0.42'
    testImplementation 'org.robolectric:robolectric:4.1'
    testImplementation 'androidx.test:core:1.3.0-rc01'
    testImplementation 'androidx.fragment:fragment-testing:1.2.5'
    testImplementation 'org.mockito:mockito-core:2.23.4'
}


================================================
FILE: easypermissions/gradle.properties
================================================
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


================================================
FILE: easypermissions/proguard-rules.pro
================================================
-keepclassmembers class * {
    @pub.devrel.easypermissions.AfterPermissionGranted <methods>;
}


================================================
FILE: easypermissions/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="pub.devrel.easypermissions">

    <application>
        <activity
            android:name="pub.devrel.easypermissions.AppSettingsDialogHolderActivity"
            android:exported="false"
            android:label=""
            android:theme="@style/EasyPermissions.Transparent"/>
    </application>

</manifest>


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/AfterPermissionGranted.java
================================================
/*
 * Copyright Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package pub.devrel.easypermissions;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterPermissionGranted {

    int value();

}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialog.java
================================================
package pub.devrel.easypermissions;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;

/**
 * Dialog to prompt the user to go to the app's settings screen and enable permissions. If the user
 * clicks 'OK' on the dialog, they are sent to the settings screen. The result is returned to the
 * Activity via {@see Activity#onActivityResult(int, int, Intent)}.
 * <p>
 * Use the {@link Builder} to create and display a dialog.
 */
public class AppSettingsDialog implements Parcelable {

    private static final String TAG = "EasyPermissions";

    public static final int DEFAULT_SETTINGS_REQ_CODE = 16061;

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static final Parcelable.Creator<AppSettingsDialog> CREATOR = new Parcelable.Creator<AppSettingsDialog>() {
        @Override
        public AppSettingsDialog createFromParcel(Parcel in) {
            return new AppSettingsDialog(in);
        }

        @Override
        public AppSettingsDialog[] newArray(int size) {
            return new AppSettingsDialog[size];
        }
    };

    static final String EXTRA_APP_SETTINGS = "extra_app_settings";

    @StyleRes
    private final int mThemeResId;
    private final String mRationale;
    private final String mTitle;
    private final String mPositiveButtonText;
    private final String mNegativeButtonText;
    private final int mRequestCode;
    private final int mIntentFlags;

    private Object mActivityOrFragment;
    private Context mContext;

    private AppSettingsDialog(Parcel in) {
        mThemeResId = in.readInt();
        mRationale = in.readString();
        mTitle = in.readString();
        mPositiveButtonText = in.readString();
        mNegativeButtonText = in.readString();
        mRequestCode = in.readInt();
        mIntentFlags = in.readInt();
    }

    private AppSettingsDialog(@NonNull final Object activityOrFragment,
                              @StyleRes int themeResId,
                              @Nullable String rationale,
                              @Nullable String title,
                              @Nullable String positiveButtonText,
                              @Nullable String negativeButtonText,
                              int requestCode,
                              int intentFlags) {
        setActivityOrFragment(activityOrFragment);
        mThemeResId = themeResId;
        mRationale = rationale;
        mTitle = title;
        mPositiveButtonText = positiveButtonText;
        mNegativeButtonText = negativeButtonText;
        mRequestCode = requestCode;
        mIntentFlags = intentFlags;
    }

    static AppSettingsDialog fromIntent(Intent intent, Activity activity) {
        AppSettingsDialog dialog = intent.getParcelableExtra(AppSettingsDialog.EXTRA_APP_SETTINGS);

        // It's not clear how this could happen, but in the case that it does we should try
        // to avoid a runtime crash and just use the default dialog.
        // https://github.com/googlesamples/easypermissions/issues/278
        if (dialog == null) {
            Log.e(TAG, "Intent contains null value for EXTRA_APP_SETTINGS: "
                    + "intent=" + intent
                    + ", "
                    + "extras=" + intent.getExtras());

            dialog = new AppSettingsDialog.Builder(activity).build();
        }

        dialog.setActivityOrFragment(activity);
        return dialog;
    }

    private void setActivityOrFragment(Object activityOrFragment) {
        mActivityOrFragment = activityOrFragment;

        if (activityOrFragment instanceof Activity) {
            mContext = (Activity) activityOrFragment;
        } else if (activityOrFragment instanceof Fragment) {
            mContext = ((Fragment) activityOrFragment).getContext();
        } else {
            throw new IllegalStateException("Unknown object: " + activityOrFragment);
        }
    }

    private void startForResult(Intent intent) {
        if (mActivityOrFragment instanceof Activity) {
            ((Activity) mActivityOrFragment).startActivityForResult(intent, mRequestCode);
        } else if (mActivityOrFragment instanceof Fragment) {
            ((Fragment) mActivityOrFragment).startActivityForResult(intent, mRequestCode);
        }
    }

    /**
     * Display the built dialog.
     */
    public void show() {
        startForResult(AppSettingsDialogHolderActivity.createShowDialogIntent(mContext, this));
    }

    /**
     * Show the dialog. {@link #show()} is a wrapper to ensure backwards compatibility
     */
    AlertDialog showDialog(DialogInterface.OnClickListener positiveListener,
                           DialogInterface.OnClickListener negativeListener) {
        AlertDialog.Builder builder;
        if (mThemeResId != -1) {
            builder = new AlertDialog.Builder(mContext, mThemeResId);
        } else {
            builder = new AlertDialog.Builder(mContext);
        }
        return builder
                .setCancelable(false)
                .setTitle(mTitle)
                .setMessage(mRationale)
                .setPositiveButton(mPositiveButtonText, positiveListener)
                .setNegativeButton(mNegativeButtonText, negativeListener)
                .show();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mThemeResId);
        dest.writeString(mRationale);
        dest.writeString(mTitle);
        dest.writeString(mPositiveButtonText);
        dest.writeString(mNegativeButtonText);
        dest.writeInt(mRequestCode);
        dest.writeInt(mIntentFlags);
    }

    int getIntentFlags() {
        return mIntentFlags;
    }

    /**
     * Builder for an {@link AppSettingsDialog}.
     */
    public static class Builder {

        private final Object mActivityOrFragment;
        private final Context mContext;
        @StyleRes
        private int mThemeResId = -1;
        private String mRationale;
        private String mTitle;
        private String mPositiveButtonText;
        private String mNegativeButtonText;
        private int mRequestCode = -1;
        private boolean mOpenInNewTask = false;

        /**
         * Create a new Builder for an {@link AppSettingsDialog}.
         *
         * @param activity the {@link Activity} in which to display the dialog.
         */
        public Builder(@NonNull Activity activity) {
            mActivityOrFragment = activity;
            mContext = activity;
        }

        /**
         * Create a new Builder for an {@link AppSettingsDialog}.
         *
         * @param fragment the {@link Fragment} in which to display the dialog.
         */
        public Builder(@NonNull Fragment fragment) {
            mActivityOrFragment = fragment;
            mContext = fragment.getContext();
        }

        /**
         * Set the dialog theme.
         */
        @NonNull
        public Builder setThemeResId(@StyleRes int themeResId) {
            mThemeResId = themeResId;
            return this;
        }

        /**
         * Set the title dialog. Default is "Permissions Required".
         */
        @NonNull
        public Builder setTitle(@Nullable String title) {
            mTitle = title;
            return this;
        }

        /**
         * Set the title dialog. Default is "Permissions Required".
         */
        @NonNull
        public Builder setTitle(@StringRes int title) {
            mTitle = mContext.getString(title);
            return this;
        }

        /**
         * Set the rationale dialog. Default is
         * "This app may not work correctly without the requested permissions.
         * Open the app settings screen to modify app permissions."
         */
        @NonNull
        public Builder setRationale(@Nullable String rationale) {
            mRationale = rationale;
            return this;
        }

        /**
         * Set the rationale dialog. Default is
         * "This app may not work correctly without the requested permissions.
         * Open the app settings screen to modify app permissions."
         */
        @NonNull
        public Builder setRationale(@StringRes int rationale) {
            mRationale = mContext.getString(rationale);
            return this;
        }

        /**
         * Set the positive button text, default is {@link android.R.string#ok}.
         */
        @NonNull
        public Builder setPositiveButton(@Nullable String text) {
            mPositiveButtonText = text;
            return this;
        }

        /**
         * Set the positive button text, default is {@link android.R.string#ok}.
         */
        @NonNull
        public Builder setPositiveButton(@StringRes int textId) {
            mPositiveButtonText = mContext.getString(textId);
            return this;
        }

        /**
         * Set the negative button text, default is {@link android.R.string#cancel}.
         * <p>
         * To know if a user cancelled the request, check if your permissions were given with {@link
         * EasyPermissions#hasPermissions(Context, String...)} in {@see
         * Activity#onActivityResult(int, int, Intent)}. If you still don't have the right
         * permissions, then the request was cancelled.
         */
        @NonNull
        public Builder setNegativeButton(@Nullable String text) {
            mNegativeButtonText = text;
            return this;
        }

        /**
         * Set the negative button text, default is {@link android.R.string#cancel}.
         */
        @NonNull
        public Builder setNegativeButton(@StringRes int textId) {
            mNegativeButtonText = mContext.getString(textId);
            return this;
        }

        /**
         * Set the request code use when launching the Settings screen for result, can be retrieved
         * in the calling Activity's {@see Activity#onActivityResult(int, int, Intent)} method.
         * Default is {@link #DEFAULT_SETTINGS_REQ_CODE}.
         */
        @NonNull
        public Builder setRequestCode(int requestCode) {
            mRequestCode = requestCode;
            return this;
        }

        /**
         * Set whether the settings screen should be opened in a separate task. This is achieved by
         * setting {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK#FLAG_ACTIVITY_NEW_TASK} on
         * the Intent used to open the settings screen.
         */
        @NonNull
        public Builder setOpenInNewTask(boolean openInNewTask) {
            mOpenInNewTask = openInNewTask;
            return this;
        }

        /**
         * Build the {@link AppSettingsDialog} from the specified options. Generally followed by a
         * call to {@link AppSettingsDialog#show()}.
         */
        @NonNull
        public AppSettingsDialog build() {
            mRationale = TextUtils.isEmpty(mRationale) ?
                    mContext.getString(R.string.rationale_ask_again) : mRationale;
            mTitle = TextUtils.isEmpty(mTitle) ?
                    mContext.getString(R.string.title_settings_dialog) : mTitle;
            mPositiveButtonText = TextUtils.isEmpty(mPositiveButtonText) ?
                    mContext.getString(android.R.string.ok) : mPositiveButtonText;
            mNegativeButtonText = TextUtils.isEmpty(mNegativeButtonText) ?
                    mContext.getString(android.R.string.cancel) : mNegativeButtonText;
            mRequestCode = mRequestCode > 0 ? mRequestCode : DEFAULT_SETTINGS_REQ_CODE;

            int intentFlags = 0;
            if (mOpenInNewTask) {
                intentFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
            }

            return new AppSettingsDialog(
                    mActivityOrFragment,
                    mThemeResId,
                    mRationale,
                    mTitle,
                    mPositiveButtonText,
                    mNegativeButtonText,
                    mRequestCode,
                    intentFlags);
        }

    }

}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialogHolderActivity.java
================================================
package pub.devrel.easypermissions;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import androidx.annotation.RestrictTo;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class AppSettingsDialogHolderActivity extends AppCompatActivity implements DialogInterface.OnClickListener {
    private static final int APP_SETTINGS_RC = 7534;

    private AlertDialog mDialog;
    private int mIntentFlags;

    public static Intent createShowDialogIntent(Context context, AppSettingsDialog dialog) {
        Intent intent = new Intent(context, AppSettingsDialogHolderActivity.class);
        intent.putExtra(AppSettingsDialog.EXTRA_APP_SETTINGS, dialog);
        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AppSettingsDialog appSettingsDialog = AppSettingsDialog.fromIntent(getIntent(), this);
        mIntentFlags = appSettingsDialog.getIntentFlags();
        mDialog = appSettingsDialog.showDialog(this, this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.dismiss();
        }
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        if (which == Dialog.BUTTON_POSITIVE) {
            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                    .setData(Uri.fromParts("package", getPackageName(), null));
            intent.addFlags(mIntentFlags);
            startActivityForResult(intent, APP_SETTINGS_RC);
        } else if (which == Dialog.BUTTON_NEGATIVE) {
            setResult(Activity.RESULT_CANCELED);
            finish();
        } else {
            throw new IllegalStateException("Unknown button type: " + which);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        setResult(resultCode, data);
        finish();
    }
}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java
================================================
/*
 * Copyright Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package pub.devrel.easypermissions;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Size;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.core.content.ContextCompat;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import pub.devrel.easypermissions.helper.PermissionHelper;

/**
 * Utility to request and check System permissions for apps targeting Android M (API &gt;= 23).
 */
public class EasyPermissions {

    /**
     * Callback interface to receive the results of {@code EasyPermissions.requestPermissions()}
     * calls.
     */
    public interface PermissionCallbacks extends ActivityCompat.OnRequestPermissionsResultCallback {

        void onPermissionsGranted(int requestCode, @NonNull List<String> perms);

        void onPermissionsDenied(int requestCode, @NonNull List<String> perms);
    }

    /**
     * Callback interface to receive button clicked events of the rationale dialog
     */
    public interface RationaleCallbacks {
        void onRationaleAccepted(int requestCode);

        void onRationaleDenied(int requestCode);
    }

    private static final String TAG = "EasyPermissions";

    /**
     * Check if the calling context has a set of permissions.
     *
     * @param context the calling context.
     * @param perms   one ore more permissions, such as {@link Manifest.permission#CAMERA}.
     * @return true if all permissions are already granted, false if at least one permission is not
     * yet granted.
     * @see Manifest.permission
     */
    public static boolean hasPermissions(@NonNull Context context,
                                         @Size(min = 1) @NonNull String... perms) {
        // Always return true for SDK < M, let the system deal with the permissions
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            Log.w(TAG, "hasPermissions: API version < M, returning true by default");

            // DANGER ZONE!!! Changing this will break the library.
            return true;
        }

        // Null context may be passed if we have detected Low API (less than M) so getting
        // to this point with a null context should not be possible.
        if (context == null) {
            throw new IllegalArgumentException("Can't check permissions for null context");
        }

        for (String perm : perms) {
            if (ContextCompat.checkSelfPermission(context, perm)
                    != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }

        return true;
    }

    /**
     * Request a set of permissions, showing a rationale if the system requests it.
     *
     * @param host        requesting context.
     * @param rationale   a message explaining why the application needs this set of permissions;
     *                    will be displayed if the user rejects the request the first time.
     * @param requestCode request code to track this request, must be &lt; 256.
     * @param perms       a set of permissions to be requested.
     * @see Manifest.permission
     */
    public static void requestPermissions(
            @NonNull Activity host, @NonNull String rationale,
            @IntRange(from = 0, to = 255) int requestCode, @Size(min = 1) @NonNull String... perms) {
        requestPermissions(
                new PermissionRequest.Builder(host, requestCode, perms)
                        .setRationale(rationale)
                        .build());
    }

    /**
     * Request permissions from a Support Fragment with standard OK/Cancel buttons.
     *
     * @see #requestPermissions(Activity, String, int, String...)
     */
    public static void requestPermissions(
            @NonNull Fragment host, @NonNull String rationale,
            @IntRange(from = 0, to = 255) int requestCode, @Size(min = 1) @NonNull String... perms) {
        requestPermissions(
                new PermissionRequest.Builder(host, requestCode, perms)
                        .setRationale(rationale)
                        .build());
    }

    /**
     * Request a set of permissions.
     *
     * @param request the permission request
     * @see PermissionRequest
     */
    public static void requestPermissions(PermissionRequest request) {

        // Check for permissions before dispatching the request
        if (hasPermissions(request.getHelper().getContext(), request.getPerms())) {
            notifyAlreadyHasPermissions(
                    request.getHelper().getHost(), request.getRequestCode(), request.getPerms());
            return;
        }

        // Request permissions
        request.getHelper().requestPermissions(
                request.getRationale(),
                request.getPositiveButtonText(),
                request.getNegativeButtonText(),
                request.getTheme(),
                request.getRequestCode(),
                request.getPerms());
    }

    /**
     * Handle the result of a permission request, should be called from the calling {@link
     * Activity}'s {@link ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int,
     * String[], int[])} method.
     * <p>
     * If any permissions were granted or denied, the {@code object} will receive the appropriate
     * callbacks through {@link PermissionCallbacks} and methods annotated with {@link
     * AfterPermissionGranted} will be run if appropriate.
     *
     * @param requestCode  requestCode argument to permission result callback.
     * @param permissions  permissions argument to permission result callback.
     * @param grantResults grantResults argument to permission result callback.
     * @param receivers    an array of objects that have a method annotated with {@link
     *                     AfterPermissionGranted} or implement {@link PermissionCallbacks}.
     */
    public static void onRequestPermissionsResult(@IntRange(from = 0, to = 255) int requestCode,
                                                  @NonNull String[] permissions,
                                                  @NonNull int[] grantResults,
                                                  @NonNull Object... receivers) {
        // Make a collection of granted and denied permissions from the request.
        List<String> granted = new ArrayList<>();
        List<String> denied = new ArrayList<>();
        for (int i = 0; i < permissions.length; i++) {
            String perm = permissions[i];
            if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                granted.add(perm);
            } else {
                denied.add(perm);
            }
        }

        // iterate through all receivers
        for (Object object : receivers) {
            // Report granted permissions, if any.
            if (!granted.isEmpty()) {
                if (object instanceof PermissionCallbacks) {
                    ((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted);
                }
            }

            // Report denied permissions, if any.
            if (!denied.isEmpty()) {
                if (object instanceof PermissionCallbacks) {
                    ((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied);
                }
            }

            // If 100% successful, call annotated methods
            if (!granted.isEmpty() && denied.isEmpty()) {
                runAnnotatedMethods(object, requestCode);
            }
        }
    }

    /**
     * Check if at least one permission in the list of denied permissions has been permanently
     * denied (user clicked "Never ask again").
     *
     * <b>Note</b>: Due to a limitation in the information provided by the Android
     * framework permissions API, this method only works after the permission
     * has been denied and your app has received the onPermissionsDenied callback.
     * Otherwise the library cannot distinguish permanent denial from the
     * "not yet denied" case.
     *
     * @param host              context requesting permissions.
     * @param deniedPermissions list of denied permissions, usually from {@link
     *                          PermissionCallbacks#onPermissionsDenied(int, List)}
     * @return {@code true} if at least one permission in the list was permanently denied.
     */
    public static boolean somePermissionPermanentlyDenied(@NonNull Activity host,
                                                          @NonNull List<String> deniedPermissions) {
        return PermissionHelper.newInstance(host)
                .somePermissionPermanentlyDenied(deniedPermissions);
    }

    /**
     * @see #somePermissionPermanentlyDenied(Activity, List)
     */
    public static boolean somePermissionPermanentlyDenied(@NonNull Fragment host,
                                                          @NonNull List<String> deniedPermissions) {
        return PermissionHelper.newInstance(host)
                .somePermissionPermanentlyDenied(deniedPermissions);
    }

    /**
     * Check if a permission has been permanently denied (user clicked "Never ask again").
     *
     * @param host             context requesting permissions.
     * @param deniedPermission denied permission.
     * @return {@code true} if the permissions has been permanently denied.
     */
    public static boolean permissionPermanentlyDenied(@NonNull Activity host,
                                                      @NonNull String deniedPermission) {
        return PermissionHelper.newInstance(host).permissionPermanentlyDenied(deniedPermission);
    }

    /**
     * @see #permissionPermanentlyDenied(Activity, String)
     */
    public static boolean permissionPermanentlyDenied(@NonNull Fragment host,
                                                      @NonNull String deniedPermission) {
        return PermissionHelper.newInstance(host).permissionPermanentlyDenied(deniedPermission);
    }

    /**
     * See if some denied permission has been permanently denied.
     *
     * @param host  requesting context.
     * @param perms array of permissions.
     * @return true if the user has previously denied any of the {@code perms} and we should show a
     * rationale, false otherwise.
     */
    public static boolean somePermissionDenied(@NonNull Activity host,
                                               @NonNull String... perms) {
        return PermissionHelper.newInstance(host).somePermissionDenied(perms);
    }

    /**
     * @see #somePermissionDenied(Activity, String...)
     */
    public static boolean somePermissionDenied(@NonNull Fragment host,
                                               @NonNull String... perms) {
        return PermissionHelper.newInstance(host).somePermissionDenied(perms);
    }

    /**
     * Run permission callbacks on an object that requested permissions but already has them by
     * simulating {@link PackageManager#PERMISSION_GRANTED}.
     *
     * @param object      the object requesting permissions.
     * @param requestCode the permission request code.
     * @param perms       a list of permissions requested.
     */
    private static void notifyAlreadyHasPermissions(@NonNull Object object,
                                                    int requestCode,
                                                    @NonNull String[] perms) {
        int[] grantResults = new int[perms.length];
        for (int i = 0; i < perms.length; i++) {
            grantResults[i] = PackageManager.PERMISSION_GRANTED;
        }

        onRequestPermissionsResult(requestCode, perms, grantResults, object);
    }

    /**
     * Find all methods annotated with {@link AfterPermissionGranted} on a given object with the
     * correct requestCode argument.
     *
     * @param object      the object with annotated methods.
     * @param requestCode the requestCode passed to the annotation.
     */
    private static void runAnnotatedMethods(@NonNull Object object, int requestCode) {
        Class clazz = object.getClass();
        if (isUsingAndroidAnnotations(object)) {
            clazz = clazz.getSuperclass();
        }

        while (clazz != null) {
            for (Method method : clazz.getDeclaredMethods()) {
                AfterPermissionGranted ann = method.getAnnotation(AfterPermissionGranted.class);
                if (ann != null) {
                    // Check for annotated methods with matching request code.
                    if (ann.value() == requestCode) {
                        // Method must be void so that we can invoke it
                        if (method.getParameterTypes().length > 0) {
                            throw new RuntimeException(
                                    "Cannot execute method " + method.getName() + " because it is non-void method and/or has input parameters.");
                        }

                        try {
                            // Make method accessible if private
                            if (!method.isAccessible()) {
                                method.setAccessible(true);
                            }
                            method.invoke(object);
                        } catch (IllegalAccessException e) {
                            Log.e(TAG, "runDefaultMethod:IllegalAccessException", e);
                        } catch (InvocationTargetException e) {
                            Log.e(TAG, "runDefaultMethod:InvocationTargetException", e);
                        }
                    }
                }
            }

            clazz = clazz.getSuperclass();
        }
    }

    /**
     * Determine if the project is using the AndroidAnnotations library.
     */
    private static boolean isUsingAndroidAnnotations(@NonNull Object object) {
        if (!object.getClass().getSimpleName().endsWith("_")) {
            return false;
        }
        try {
            Class clazz = Class.forName("org.androidannotations.api.view.HasViews");
            return clazz.isInstance(object);
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java
================================================
package pub.devrel.easypermissions;

import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.Size;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;

import java.util.Arrays;

import pub.devrel.easypermissions.helper.PermissionHelper;

/**
 * An immutable model object that holds all of the parameters associated with a permission request,
 * such as the permissions, request code, and rationale.
 *
 * @see EasyPermissions#requestPermissions(PermissionRequest)
 * @see PermissionRequest.Builder
 */
public final class PermissionRequest {
    private final PermissionHelper mHelper;
    private final String[] mPerms;
    private final int mRequestCode;
    private final String mRationale;
    private final String mPositiveButtonText;
    private final String mNegativeButtonText;
    private final int mTheme;

    private PermissionRequest(PermissionHelper helper,
                              String[] perms,
                              int requestCode,
                              String rationale,
                              String positiveButtonText,
                              String negativeButtonText,
                              int theme) {
        mHelper = helper;
        mPerms = perms.clone();
        mRequestCode = requestCode;
        mRationale = rationale;
        mPositiveButtonText = positiveButtonText;
        mNegativeButtonText = negativeButtonText;
        mTheme = theme;
    }

    @NonNull
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public PermissionHelper getHelper() {
        return mHelper;
    }

    @NonNull
    public String[] getPerms() {
        return mPerms.clone();
    }

    public int getRequestCode() {
        return mRequestCode;
    }

    @NonNull
    public String getRationale() {
        return mRationale;
    }

    @NonNull
    public String getPositiveButtonText() {
        return mPositiveButtonText;
    }

    @NonNull
    public String getNegativeButtonText() {
        return mNegativeButtonText;
    }

    @StyleRes
    public int getTheme() {
        return mTheme;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        PermissionRequest request = (PermissionRequest) o;

        return Arrays.equals(mPerms, request.mPerms) && mRequestCode == request.mRequestCode;
    }

    @Override
    public int hashCode() {
        int result = Arrays.hashCode(mPerms);
        result = 31 * result + mRequestCode;
        return result;
    }

    @Override
    public String toString() {
        return "PermissionRequest{" +
                "mHelper=" + mHelper +
                ", mPerms=" + Arrays.toString(mPerms) +
                ", mRequestCode=" + mRequestCode +
                ", mRationale='" + mRationale + '\'' +
                ", mPositiveButtonText='" + mPositiveButtonText + '\'' +
                ", mNegativeButtonText='" + mNegativeButtonText + '\'' +
                ", mTheme=" + mTheme +
                '}';
    }

    /**
     * Builder to build a permission request with variable options.
     *
     * @see PermissionRequest
     */
    public static final class Builder {
        private final PermissionHelper mHelper;
        private final int mRequestCode;
        private final String[] mPerms;

        private String mRationale;
        private String mPositiveButtonText;
        private String mNegativeButtonText;
        private int mTheme = -1;

        /**
         * Construct a new permission request builder with a host, request code, and the requested
         * permissions.
         *
         * @param activity    the permission request host
         * @param requestCode request code to track this request; must be &lt; 256
         * @param perms       the set of permissions to be requested
         */
        public Builder(@NonNull Activity activity, int requestCode,
                       @NonNull @Size(min = 1) String... perms) {
            mHelper = PermissionHelper.newInstance(activity);
            mRequestCode = requestCode;
            mPerms = perms;
        }

        /**
         * @see #Builder(Activity, int, String...)
         */
        public Builder(@NonNull Fragment fragment, int requestCode,
                       @NonNull @Size(min = 1) String... perms) {
            mHelper = PermissionHelper.newInstance(fragment);
            mRequestCode = requestCode;
            mPerms = perms;
        }

        /**
         * Set the rationale to display to the user if they don't allow your permissions on the
         * first try. This rationale will be shown as long as the user has denied your permissions
         * at least once, but has not yet permanently denied your permissions. Should the user
         * permanently deny your permissions, use the {@link AppSettingsDialog} instead.
         * <p>
         * The default rationale text is {@link R.string#rationale_ask}.
         *
         * @param rationale the rationale to be displayed to the user should they deny your
         *                  permission at least once
         */
        @NonNull
        public Builder setRationale(@Nullable String rationale) {
            mRationale = rationale;
            return this;
        }

        /**
         * @param resId the string resource to be used as a rationale
         * @see #setRationale(String)
         */
        @NonNull
        public Builder setRationale(@StringRes int resId) {
            mRationale = mHelper.getContext().getString(resId);
            return this;
        }

        /**
         * Set the positive button text for the rationale dialog should it be shown.
         * <p>
         * The default is {@link android.R.string#ok}
         */
        @NonNull
        public Builder setPositiveButtonText(@Nullable String positiveButtonText) {
            mPositiveButtonText = positiveButtonText;
            return this;
        }

        /**
         * @see #setPositiveButtonText(String)
         */
        @NonNull
        public Builder setPositiveButtonText(@StringRes int resId) {
            mPositiveButtonText = mHelper.getContext().getString(resId);
            return this;
        }

        /**
         * Set the negative button text for the rationale dialog should it be shown.
         * <p>
         * The default is {@link android.R.string#cancel}
         */
        @NonNull
        public Builder setNegativeButtonText(@Nullable String negativeButtonText) {
            mNegativeButtonText = negativeButtonText;
            return this;
        }

        /**
         * @see #setNegativeButtonText(String)
         */
        @NonNull
        public Builder setNegativeButtonText(@StringRes int resId) {
            mNegativeButtonText = mHelper.getContext().getString(resId);
            return this;
        }

        /**
         * Set the theme to be used for the rationale dialog should it be shown.
         *
         * @param theme a style resource
         */
        @NonNull
        public Builder setTheme(@StyleRes int theme) {
            mTheme = theme;
            return this;
        }

        /**
         * Build the permission request.
         *
         * @return the permission request
         * @see EasyPermissions#requestPermissions(PermissionRequest)
         * @see PermissionRequest
         */
        @NonNull
        public PermissionRequest build() {
            if (mRationale == null) {
                mRationale = mHelper.getContext().getString(R.string.rationale_ask);
            }
            if (mPositiveButtonText == null) {
                mPositiveButtonText = mHelper.getContext().getString(android.R.string.ok);
            }
            if (mNegativeButtonText == null) {
                mNegativeButtonText = mHelper.getContext().getString(android.R.string.cancel);
            }

            return new PermissionRequest(
                    mHelper,
                    mPerms,
                    mRequestCode,
                    mRationale,
                    mPositiveButtonText,
                    mNegativeButtonText,
                    mTheme);
        }
    }
}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogClickListener.java
================================================
package pub.devrel.easypermissions;

import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import androidx.fragment.app.Fragment;

import java.util.Arrays;

import pub.devrel.easypermissions.helper.PermissionHelper;

/**
 * Click listener for either {@link RationaleDialogFragment} or {@link RationaleDialogFragmentCompat}.
 */
class RationaleDialogClickListener implements Dialog.OnClickListener {

    private Object mHost;
    private RationaleDialogConfig mConfig;
    private EasyPermissions.PermissionCallbacks mCallbacks;
    private EasyPermissions.RationaleCallbacks mRationaleCallbacks;

    RationaleDialogClickListener(RationaleDialogFragmentCompat compatDialogFragment,
                                 RationaleDialogConfig config,
                                 EasyPermissions.PermissionCallbacks callbacks,
                                 EasyPermissions.RationaleCallbacks rationaleCallbacks) {

        mHost = compatDialogFragment.getParentFragment() != null
                ? compatDialogFragment.getParentFragment()
                : compatDialogFragment.getActivity();

        mConfig = config;
        mCallbacks = callbacks;
        mRationaleCallbacks = rationaleCallbacks;

    }

    RationaleDialogClickListener(RationaleDialogFragment dialogFragment,
                                 RationaleDialogConfig config,
                                 EasyPermissions.PermissionCallbacks callbacks,
                                 EasyPermissions.RationaleCallbacks dialogCallback) {

        mHost = dialogFragment.getActivity();

        mConfig = config;
        mCallbacks = callbacks;
        mRationaleCallbacks = dialogCallback;
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        int requestCode = mConfig.requestCode;
        if (which == Dialog.BUTTON_POSITIVE) {
            String[] permissions = mConfig.permissions;
            if (mRationaleCallbacks != null) {
                mRationaleCallbacks.onRationaleAccepted(requestCode);
            }
            if (mHost instanceof Fragment) {
                PermissionHelper.newInstance((Fragment) mHost).directRequestPermissions(requestCode, permissions);
            } else if (mHost instanceof Activity) {
                PermissionHelper.newInstance((Activity) mHost).directRequestPermissions(requestCode, permissions);
            } else {
                throw new RuntimeException("Host must be an Activity or Fragment!");
            }
        } else {
            if (mRationaleCallbacks != null) {
                mRationaleCallbacks.onRationaleDenied(requestCode);
            }
            notifyPermissionDenied();
        }
    }

    private void notifyPermissionDenied() {
        if (mCallbacks != null) {
            mCallbacks.onPermissionsDenied(mConfig.requestCode, Arrays.asList(mConfig.permissions));
        }
    }
}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogConfig.java
================================================
package pub.devrel.easypermissions;

import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;

/**
 * Configuration for either {@link RationaleDialogFragment} or {@link RationaleDialogFragmentCompat}.
 */
class RationaleDialogConfig {

    private static final String KEY_POSITIVE_BUTTON = "positiveButton";
    private static final String KEY_NEGATIVE_BUTTON = "negativeButton";
    private static final String KEY_RATIONALE_MESSAGE = "rationaleMsg";
    private static final String KEY_THEME = "theme";
    private static final String KEY_REQUEST_CODE = "requestCode";
    private static final String KEY_PERMISSIONS = "permissions";

    String positiveButton;
    String negativeButton;
    int theme;
    int requestCode;
    String rationaleMsg;
    String[] permissions;

    RationaleDialogConfig(@NonNull String positiveButton,
                          @NonNull String negativeButton,
                          @NonNull String rationaleMsg,
                          @StyleRes int theme,
                          int requestCode,
                          @NonNull String[] permissions) {

        this.positiveButton = positiveButton;
        this.negativeButton = negativeButton;
        this.rationaleMsg = rationaleMsg;
        this.theme = theme;
        this.requestCode = requestCode;
        this.permissions = permissions;
    }

    RationaleDialogConfig(Bundle bundle) {
        positiveButton = bundle.getString(KEY_POSITIVE_BUTTON);
        negativeButton = bundle.getString(KEY_NEGATIVE_BUTTON);
        rationaleMsg = bundle.getString(KEY_RATIONALE_MESSAGE);
        theme = bundle.getInt(KEY_THEME);
        requestCode = bundle.getInt(KEY_REQUEST_CODE);
        permissions = bundle.getStringArray(KEY_PERMISSIONS);
    }

    Bundle toBundle() {
        Bundle bundle = new Bundle();
        bundle.putString(KEY_POSITIVE_BUTTON, positiveButton);
        bundle.putString(KEY_NEGATIVE_BUTTON, negativeButton);
        bundle.putString(KEY_RATIONALE_MESSAGE, rationaleMsg);
        bundle.putInt(KEY_THEME, theme);
        bundle.putInt(KEY_REQUEST_CODE, requestCode);
        bundle.putStringArray(KEY_PERMISSIONS, permissions);

        return bundle;
    }

    AlertDialog createSupportDialog(Context context, Dialog.OnClickListener listener) {
        AlertDialog.Builder builder;
        if (theme > 0) {
            builder = new AlertDialog.Builder(context, theme);
        } else {
            builder = new AlertDialog.Builder(context);
        }
        return builder
                .setCancelable(false)
                .setPositiveButton(positiveButton, listener)
                .setNegativeButton(negativeButton, listener)
                .setMessage(rationaleMsg)
                .create();
    }

    android.app.AlertDialog createFrameworkDialog(Context context, Dialog.OnClickListener listener) {
        android.app.AlertDialog.Builder builder;
        if (theme > 0) {
            builder = new android.app.AlertDialog.Builder(context, theme);
        } else {
            builder = new android.app.AlertDialog.Builder(context);
        }
        return builder
                .setCancelable(false)
                .setPositiveButton(positiveButton, listener)
                .setNegativeButton(negativeButton, listener)
                .setMessage(rationaleMsg)
                .create();
    }

}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragment.java
================================================
package pub.devrel.easypermissions;

import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;

/**
 * {@link DialogFragment} to display rationale for permission requests when the request comes from
 * a Fragment or Activity that can host a Fragment.
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class RationaleDialogFragment extends DialogFragment {

    public static final String TAG = "RationaleDialogFragment";

    private EasyPermissions.PermissionCallbacks mPermissionCallbacks;
    private EasyPermissions.RationaleCallbacks mRationaleCallbacks;
    private boolean mStateSaved = false;

    public static RationaleDialogFragment newInstance(
            @NonNull String positiveButton,
            @NonNull String negativeButton,
            @NonNull String rationaleMsg,
            @StyleRes int theme,
            int requestCode,
            @NonNull String[] permissions) {

        // Create new Fragment
        RationaleDialogFragment dialogFragment = new RationaleDialogFragment();

        // Initialize configuration as arguments
        RationaleDialogConfig config = new RationaleDialogConfig(
                positiveButton, negativeButton, rationaleMsg, theme, requestCode, permissions);
        dialogFragment.setArguments(config.toBundle());

        return dialogFragment;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && getParentFragment() != null) {
            if (getParentFragment() instanceof EasyPermissions.PermissionCallbacks) {
                mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) getParentFragment();
            }
            if (getParentFragment() instanceof EasyPermissions.RationaleCallbacks){
                mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) getParentFragment();
            }

        }

        if (context instanceof EasyPermissions.PermissionCallbacks) {
            mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) context;
        }

        if (context instanceof EasyPermissions.RationaleCallbacks) {
            mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) context;
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        mStateSaved = true;
        super.onSaveInstanceState(outState);
    }

    /**
     * Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
     * would otherwise occur.
     */
    public void showAllowingStateLoss(FragmentManager manager, String tag) {
        // API 26 added this convenient method
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (manager.isStateSaved()) {
                return;
            }
        }

        if (mStateSaved) {
            return;
        }

        show(manager, tag);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mPermissionCallbacks = null;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Rationale dialog should not be cancelable
        setCancelable(false);

        // Get config from arguments, create click listener
        RationaleDialogConfig config = new RationaleDialogConfig(getArguments());
        RationaleDialogClickListener clickListener =
                new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks);

        // Create an AlertDialog
        return config.createFrameworkDialog(getActivity(), clickListener);
    }

}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java
================================================
package pub.devrel.easypermissions;

import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
import androidx.fragment.app.FragmentManager;
import androidx.appcompat.app.AppCompatDialogFragment;

/**
 * {@link AppCompatDialogFragment} to display rationale for permission requests when the request
 * comes from a Fragment or Activity that can host a Fragment.
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class RationaleDialogFragmentCompat extends AppCompatDialogFragment {

    public static final String TAG = "RationaleDialogFragmentCompat";

    private EasyPermissions.PermissionCallbacks mPermissionCallbacks;
    private EasyPermissions.RationaleCallbacks mRationaleCallbacks;

    public static RationaleDialogFragmentCompat newInstance(
            @NonNull String rationaleMsg,
            @NonNull String positiveButton,
            @NonNull String negativeButton,
            @StyleRes int theme,
            int requestCode,
            @NonNull String[] permissions) {

        // Create new Fragment
        RationaleDialogFragmentCompat dialogFragment = new RationaleDialogFragmentCompat();

        // Initialize configuration as arguments
        RationaleDialogConfig config = new RationaleDialogConfig(
                positiveButton, negativeButton, rationaleMsg, theme, requestCode, permissions);
        dialogFragment.setArguments(config.toBundle());

        return dialogFragment;
    }

    /**
     * Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
     * would otherwise occur.
     */
    public void showAllowingStateLoss(FragmentManager manager, String tag) {
        if (manager.isStateSaved()) {
            return;
        }

        show(manager, tag);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (getParentFragment() != null) {
            if (getParentFragment() instanceof EasyPermissions.PermissionCallbacks) {
                mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) getParentFragment();
            }
            if (getParentFragment() instanceof EasyPermissions.RationaleCallbacks){
                mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) getParentFragment();
            }
        }

        if (context instanceof EasyPermissions.PermissionCallbacks) {
            mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) context;
        }

        if (context instanceof EasyPermissions.RationaleCallbacks) {
            mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) context;
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mPermissionCallbacks = null;
        mRationaleCallbacks = null;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Rationale dialog should not be cancelable
        setCancelable(false);

        // Get config from arguments, create click listener
        RationaleDialogConfig config = new RationaleDialogConfig(getArguments());
        RationaleDialogClickListener clickListener =
                new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks);

        // Create an AlertDialog
        return config.createSupportDialog(getContext(), clickListener);
    }
}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java
================================================
package pub.devrel.easypermissions.helper;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.core.app.ActivityCompat;
import android.util.Log;

import pub.devrel.easypermissions.RationaleDialogFragment;

/**
 * Permissions helper for {@link Activity}.
 */
class ActivityPermissionHelper extends PermissionHelper<Activity> {
    private static final String TAG = "ActPermissionHelper";

    public ActivityPermissionHelper(Activity host) {
        super(host);
    }

    @Override
    public void directRequestPermissions(int requestCode, @NonNull String... perms) {
        ActivityCompat.requestPermissions(getHost(), perms, requestCode);
    }

    @Override
    public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
        return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
    }

    @Override
    public Context getContext() {
        return getHost();
    }

    @Override
    public void showRequestPermissionRationale(@NonNull String rationale,
                                               @NonNull String positiveButton,
                                               @NonNull String negativeButton,
                                               @StyleRes int theme,
                                               int requestCode,
                                               @NonNull String... perms) {
        FragmentManager fm = getHost().getFragmentManager();

        // Check if fragment is already showing
        Fragment fragment = fm.findFragmentByTag(RationaleDialogFragment.TAG);
        if (fragment instanceof RationaleDialogFragment) {
            Log.d(TAG, "Found existing fragment, not showing rationale.");
            return;
        }

        RationaleDialogFragment
                .newInstance(positiveButton, negativeButton, rationale, theme, requestCode, perms)
                .showAllowingStateLoss(fm, RationaleDialogFragment.TAG);
    }
}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/AppCompatActivityPermissionsHelper.java
================================================
package pub.devrel.easypermissions.helper;

import android.content.Context;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentManager;
import androidx.appcompat.app.AppCompatActivity;

/**
 * Permissions helper for {@link AppCompatActivity}.
 */
class AppCompatActivityPermissionsHelper extends BaseSupportPermissionsHelper<AppCompatActivity> {

    public AppCompatActivityPermissionsHelper(AppCompatActivity host) {
        super(host);
    }

    @Override
    public FragmentManager getSupportFragmentManager() {
        return getHost().getSupportFragmentManager();
    }

    @Override
    public void directRequestPermissions(int requestCode, @NonNull String... perms) {
        ActivityCompat.requestPermissions(getHost(), perms, requestCode);
    }

    @Override
    public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
        return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
    }

    @Override
    public Context getContext() {
        return getHost();
    }
}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/BaseSupportPermissionsHelper.java
================================================
package pub.devrel.easypermissions.helper;

import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import android.util.Log;

import pub.devrel.easypermissions.RationaleDialogFragmentCompat;

/**
 * Implementation of {@link PermissionHelper} for Support Library host classes.
 */
public abstract class BaseSupportPermissionsHelper<T> extends PermissionHelper<T> {

    private static final String TAG = "BSPermissionsHelper";

    public BaseSupportPermissionsHelper(@NonNull T host) {
        super(host);
    }

    public abstract FragmentManager getSupportFragmentManager();

    @Override
    public void showRequestPermissionRationale(@NonNull String rationale,
                                               @NonNull String positiveButton,
                                               @NonNull String negativeButton,
                                               @StyleRes int theme,
                                               int requestCode,
                                               @NonNull String... perms) {

        FragmentManager fm = getSupportFragmentManager();

        // Check if fragment is already showing
        Fragment fragment = fm.findFragmentByTag(RationaleDialogFragmentCompat.TAG);
        if (fragment instanceof RationaleDialogFragmentCompat) {
            Log.d(TAG, "Found existing fragment, not showing rationale.");
            return;
        }

        RationaleDialogFragmentCompat
                .newInstance(rationale, positiveButton, negativeButton, theme, requestCode, perms)
                .showAllowingStateLoss(fm, RationaleDialogFragmentCompat.TAG);
    }
}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/LowApiPermissionsHelper.java
================================================
package pub.devrel.easypermissions.helper;

import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;

/**
 * Permissions helper for apps built against API < 23, which do not need runtime permissions.
 */
class LowApiPermissionsHelper<T> extends PermissionHelper<T> {
    public LowApiPermissionsHelper(@NonNull T host) {
        super(host);
    }

    @Override
    public void directRequestPermissions(int requestCode, @NonNull String... perms) {
        throw new IllegalStateException("Should never be requesting permissions on API < 23!");
    }

    @Override
    public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
        return false;
    }

    @Override
    public void showRequestPermissionRationale(@NonNull String rationale,
                                               @NonNull String positiveButton,
                                               @NonNull String negativeButton,
                                               @StyleRes int theme,
                                               int requestCode,
                                               @NonNull String... perms) {
        throw new IllegalStateException("Should never be requesting permissions on API < 23!");
    }

    @Override
    public Context getContext() {
        if (getHost() instanceof Activity) {
            return (Context) getHost();
        } else if (getHost() instanceof Fragment) {
            return ((Fragment) getHost()).getContext();
        } else {
            throw new IllegalStateException("Unknown host: " + getHost());
        }
    }
}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java
================================================
package pub.devrel.easypermissions.helper;

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
import androidx.appcompat.app.AppCompatActivity;

import java.util.List;

/**
 * Delegate class to make permission calls based on the 'host' (Fragment, Activity, etc).
 */
public abstract class PermissionHelper<T> {

    private T mHost;

    @NonNull
    public static PermissionHelper<? extends Activity> newInstance(Activity host) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return new LowApiPermissionsHelper<>(host);
        }

        if (host instanceof AppCompatActivity)
            return new AppCompatActivityPermissionsHelper((AppCompatActivity) host);
        else {
            return new ActivityPermissionHelper(host);
        }
    }

    @NonNull
    public static PermissionHelper<Fragment> newInstance(Fragment host) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return new LowApiPermissionsHelper<>(host);
        }

        return new SupportFragmentPermissionHelper(host);
    }

    // ============================================================================
    // Public concrete methods
    // ============================================================================

    public PermissionHelper(@NonNull T host) {
        mHost = host;
    }

    private boolean shouldShowRationale(@NonNull String... perms) {
        for (String perm : perms) {
            if (shouldShowRequestPermissionRationale(perm)) {
                return true;
            }
        }
        return false;
    }

    public void requestPermissions(@NonNull String rationale,
                                   @NonNull String positiveButton,
                                   @NonNull String negativeButton,
                                   @StyleRes int theme,
                                   int requestCode,
                                   @NonNull String... perms) {
        if (shouldShowRationale(perms)) {
            showRequestPermissionRationale(
                    rationale, positiveButton, negativeButton, theme, requestCode, perms);
        } else {
            directRequestPermissions(requestCode, perms);
        }
    }

    public boolean somePermissionPermanentlyDenied(@NonNull List<String> perms) {
        for (String deniedPermission : perms) {
            if (permissionPermanentlyDenied(deniedPermission)) {
                return true;
            }
        }

        return false;
    }

    public boolean permissionPermanentlyDenied(@NonNull String perms) {
        return !shouldShowRequestPermissionRationale(perms);
    }

    public boolean somePermissionDenied(@NonNull String... perms) {
        return shouldShowRationale(perms);
    }

    @NonNull
    public T getHost() {
        return mHost;
    }

    // ============================================================================
    // Public abstract methods
    // ============================================================================

    public abstract void directRequestPermissions(int requestCode, @NonNull String... perms);

    public abstract boolean shouldShowRequestPermissionRationale(@NonNull String perm);

    public abstract void showRequestPermissionRationale(@NonNull String rationale,
                                                        @NonNull String positiveButton,
                                                        @NonNull String negativeButton,
                                                        @StyleRes int theme,
                                                        int requestCode,
                                                        @NonNull String... perms);

    public abstract Context getContext();

}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/SupportFragmentPermissionHelper.java
================================================
package pub.devrel.easypermissions.helper;

import android.content.Context;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;

/**
 * Permissions helper for {@link Fragment} from the support library.
 */
class SupportFragmentPermissionHelper extends BaseSupportPermissionsHelper<Fragment> {

    public SupportFragmentPermissionHelper(@NonNull Fragment host) {
        super(host);
    }

    @Override
    public FragmentManager getSupportFragmentManager() {
        return getHost().getChildFragmentManager();
    }

    @Override
    public void directRequestPermissions(int requestCode, @NonNull String... perms) {
        getHost().requestPermissions(perms, requestCode);
    }

    @Override
    public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
        return getHost().shouldShowRequestPermissionRationale(perm);
    }

    @Override
    public Context getContext() {
        return getHost().getActivity();
    }
}


================================================
FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/package-info.java
================================================
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package pub.devrel.easypermissions.helper;

import androidx.annotation.RestrictTo;


================================================
FILE: easypermissions/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Use system defaults for devs not using AppCompat -->
    <color name="colorPrimary">#ff212121</color>
    <color name="colorPrimaryDark">@android:color/black</color>
    <color name="colorAccent">#ff80cbc4</color>
</resources>


================================================
FILE: easypermissions/src/main/res/values/strings.xml
================================================
<resources>
    <string name="rationale_ask">This app may not work correctly without the requested permissions.</string>
    <string name="rationale_ask_again">
        This app may not work correctly without the requested permissions.
        Open the app settings screen to modify app permissions.
    </string>
    <string name="title_settings_dialog">Permissions Required</string>
</resources>


================================================
FILE: easypermissions/src/main/res/values/styles.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="EasyPermissions" parent="Theme.AppCompat.DayNight.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="EasyPermissions.Transparent">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:backgroundDimEnabled">false</item>
    </style>

</resources>


================================================
FILE: easypermissions/src/test/java/pub/devrel/easypermissions/AppSettingsDialogTest.java
================================================
package pub.devrel.easypermissions;

import android.app.Application;
import android.content.DialogInterface;
import android.content.Intent;
import android.widget.Button;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowIntent;

import java.util.Objects;

import androidx.appcompat.app.AlertDialog;
import androidx.test.core.app.ApplicationProvider;
import pub.devrel.easypermissions.testhelper.ActivityController;
import pub.devrel.easypermissions.testhelper.FragmentController;
import pub.devrel.easypermissions.testhelper.TestActivity;
import pub.devrel.easypermissions.testhelper.TestFragment;

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
import static pub.devrel.easypermissions.AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class AppSettingsDialogTest {

    private static final String TITLE = "TITLE";
    private static final String RATIONALE = "RATIONALE";
    private static final String NEGATIVE = "NEGATIVE";
    private static final String POSITIVE = "POSITIVE";
    private ShadowApplication shadowApp;
    private TestActivity spyActivity;
    private TestFragment spyFragment;
    private FragmentController<TestFragment> fragmentController;
    private ActivityController<TestActivity> activityController;
    @Mock
    private DialogInterface.OnClickListener positiveListener;
    @Mock
    private DialogInterface.OnClickListener negativeListener;
    @Captor
    private ArgumentCaptor<Integer> integerCaptor;
    @Captor
    private ArgumentCaptor<Intent> intentCaptor;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        shadowApp = shadowOf((Application) ApplicationProvider.getApplicationContext());

        activityController = new ActivityController<>(TestActivity.class);
        fragmentController = new FragmentController<>(TestFragment.class);

        spyActivity = Mockito.spy(activityController.resume());
        spyFragment = Mockito.spy(fragmentController.resume());
    }

    // ------ From Activity ------

    @Test
    public void shouldShowExpectedSettingsDialog_whenBuildingFromActivity() {
        new AppSettingsDialog.Builder(spyActivity)
                .setTitle(android.R.string.dialog_alert_title)
                .setRationale(android.R.string.unknownName)
                .setPositiveButton(android.R.string.ok)
                .setNegativeButton(android.R.string.cancel)
                .setThemeResId(R.style.Theme_AppCompat)
                .build()
                .show();

        verify(spyActivity, times(1))
                .startActivityForResult(intentCaptor.capture(), integerCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(DEFAULT_SETTINGS_REQ_CODE);
        assertThat(Objects.requireNonNull(intentCaptor.getValue().getComponent()).getClassName())
                .isEqualTo(AppSettingsDialogHolderActivity.class.getName());

        Intent startedIntent = shadowApp.getNextStartedActivity();
        ShadowIntent shadowIntent = shadowOf(startedIntent);
        assertThat(shadowIntent.getIntentClass()).isEqualTo(AppSettingsDialogHolderActivity.class);
    }

    @Test
    public void shouldPositiveListener_whenClickingPositiveButtonFromActivity() {
        AlertDialog alertDialog = new AppSettingsDialog.Builder(spyActivity)
                .setTitle(TITLE)
                .setRationale(RATIONALE)
                .setPositiveButton(POSITIVE)
                .setNegativeButton(NEGATIVE)
                .setThemeResId(R.style.Theme_AppCompat)
                .build()
                .showDialog(positiveListener, negativeListener);
        Button positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
        positive.performClick();

        verify(positiveListener, times(1))
                .onClick(any(DialogInterface.class), anyInt());
    }

    @Test
    public void shouldNegativeListener_whenClickingPositiveButtonFromActivity() {
        AlertDialog alertDialog = new AppSettingsDialog.Builder(spyActivity)
                .setTitle(TITLE)
                .setRationale(RATIONALE)
                .setPositiveButton(POSITIVE)
                .setNegativeButton(NEGATIVE)
                .setThemeResId(R.style.Theme_AppCompat)
                .build()
                .showDialog(positiveListener, negativeListener);
        Button positive = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
        positive.performClick();

        verify(negativeListener, times(1))
                .onClick(any(DialogInterface.class), anyInt());
    }

    @Test
    public void shouldShowExpectedSettingsDialog_whenBuildingFromSupportFragment() {
        new AppSettingsDialog.Builder(spyFragment)
                .setTitle(android.R.string.dialog_alert_title)
                .setRationale(android.R.string.unknownName)
                .setPositiveButton(android.R.string.ok)
                .setNegativeButton(android.R.string.cancel)
                .setThemeResId(R.style.Theme_AppCompat)
                .build()
                .show();

        verify(spyFragment, times(1))
                .startActivityForResult(intentCaptor.capture(), integerCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(DEFAULT_SETTINGS_REQ_CODE);
        assertThat(Objects.requireNonNull(intentCaptor.getValue().getComponent()).getClassName())
                .isEqualTo(AppSettingsDialogHolderActivity.class.getName());

        Intent startedIntent = shadowApp.getNextStartedActivity();
        ShadowIntent shadowIntent = shadowOf(startedIntent);
        assertThat(shadowIntent.getIntentClass()).isEqualTo(AppSettingsDialogHolderActivity.class);
    }

    @Test
    public void shouldPositiveListener_whenClickingPositiveButtonFromSupportFragment() {
        AlertDialog alertDialog = new AppSettingsDialog.Builder(spyFragment)
                .setTitle(TITLE)
                .setRationale(RATIONALE)
                .setPositiveButton(POSITIVE)
                .setNegativeButton(NEGATIVE)
                .setThemeResId(R.style.Theme_AppCompat)
                .build()
                .showDialog(positiveListener, negativeListener);
        Button positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
        positive.performClick();

        verify(positiveListener, times(1))
                .onClick(any(DialogInterface.class), anyInt());
    }

    @Test
    public void shouldNegativeListener_whenClickingPositiveButtonFromSupportFragment() {
        AlertDialog alertDialog = new AppSettingsDialog.Builder(spyFragment)
                .setTitle(TITLE)
                .setRationale(RATIONALE)
                .setPositiveButton(POSITIVE)
                .setNegativeButton(NEGATIVE)
                .setThemeResId(R.style.Theme_AppCompat)
                .build()
                .showDialog(positiveListener, negativeListener);
        Button positive = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
        positive.performClick();

        verify(negativeListener, times(1))
                .onClick(any(DialogInterface.class), anyInt());
    }

}


================================================
FILE: easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsLowApiTest.java
================================================
package pub.devrel.easypermissions;

import android.Manifest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import java.util.ArrayList;

import androidx.test.core.app.ApplicationProvider;
import pub.devrel.easypermissions.testhelper.ActivityController;
import pub.devrel.easypermissions.testhelper.FragmentController;
import pub.devrel.easypermissions.testhelper.TestActivity;
import pub.devrel.easypermissions.testhelper.TestAppCompatActivity;
import pub.devrel.easypermissions.testhelper.TestFragment;
import pub.devrel.easypermissions.testhelper.TestSupportFragmentActivity;

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
 * Low-API (SDK = 19) tests for {@link pub.devrel.easypermissions.EasyPermissions}.
 */
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 19)
public class EasyPermissionsLowApiTest {

    private static final String RATIONALE = "RATIONALE";
    private static final String[] ALL_PERMS = new String[]{
            Manifest.permission.READ_SMS, Manifest.permission.ACCESS_FINE_LOCATION};

    private TestActivity spyActivity;
    private TestSupportFragmentActivity spySupportFragmentActivity;
    private TestAppCompatActivity spyAppCompatActivity;
    private TestFragment spyFragment;
    private FragmentController<TestFragment> fragmentController;
    private ActivityController<TestActivity> activityController;
    private ActivityController<TestSupportFragmentActivity> supportFragmentActivityController;
    private ActivityController<TestAppCompatActivity> appCompatActivityController;
    @Captor
    private ArgumentCaptor<Integer> integerCaptor;
    @Captor
    private ArgumentCaptor<ArrayList<String>> listCaptor;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        activityController = new ActivityController<>(TestActivity.class);
        supportFragmentActivityController = new ActivityController<>(TestSupportFragmentActivity.class);
        appCompatActivityController = new ActivityController<>(TestAppCompatActivity.class);
        fragmentController = new FragmentController<>(TestFragment.class);

        spyActivity = Mockito.spy(activityController.resume());
        spySupportFragmentActivity = Mockito.spy(supportFragmentActivityController.resume());
        spyAppCompatActivity = Mockito.spy(appCompatActivityController.resume());
        spyFragment = Mockito.spy(fragmentController.resume());
    }

    // ------ General tests ------

    @Test
    public void shouldHavePermission_whenHasPermissionsBeforeMarshmallow() {
        assertThat(EasyPermissions.hasPermissions(ApplicationProvider.getApplicationContext(),
                Manifest.permission.ACCESS_COARSE_LOCATION)).isTrue();
    }

    // ------ From Activity ------

    @Test
    public void shouldCallbackOnPermissionGranted_whenRequestFromActivity() {
        EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);

        verify(spyActivity, times(1))
                .onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(TestActivity.REQUEST_CODE);
        assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
    }

    // ------ From Support Activity ------

    @Test
    public void shouldCallbackOnPermissionGranted_whenRequestFromSupportFragmentActivity() {
        EasyPermissions.requestPermissions(spySupportFragmentActivity, RATIONALE, TestSupportFragmentActivity.REQUEST_CODE, ALL_PERMS);

        verify(spySupportFragmentActivity, times(1))
                .onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(TestSupportFragmentActivity.REQUEST_CODE);
        assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
    }


    @Test
    public void shouldCallbackOnPermissionGranted_whenRequestFromAppCompatActivity() {
        EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);

        verify(spyAppCompatActivity, times(1))
                .onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(TestAppCompatActivity.REQUEST_CODE);
        assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
    }

    @Test
    public void shouldCallbackOnPermissionGranted_whenRequestFromFragment() {
        EasyPermissions.requestPermissions(spyFragment, RATIONALE, TestFragment.REQUEST_CODE, ALL_PERMS);

        verify(spyFragment, times(1))
                .onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(TestFragment.REQUEST_CODE);
        assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
    }

}


================================================
FILE: easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsTest.java
================================================
package pub.devrel.easypermissions;

import android.Manifest;
import android.app.Application;
import android.app.Dialog;
import android.app.Fragment;
import android.content.pm.PackageManager;
import android.widget.TextView;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

import androidx.test.core.app.ApplicationProvider;
import pub.devrel.easypermissions.testhelper.ActivityController;
import pub.devrel.easypermissions.testhelper.FragmentController;
import pub.devrel.easypermissions.testhelper.TestActivity;
import pub.devrel.easypermissions.testhelper.TestAppCompatActivity;
import pub.devrel.easypermissions.testhelper.TestFragment;
import pub.devrel.easypermissions.testhelper.TestSupportFragmentActivity;

import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;

/**
 * Basic Robolectric tests for {@link pub.devrel.easypermissions.EasyPermissions}.
 */
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class EasyPermissionsTest {

    private static final String RATIONALE = "RATIONALE";
    private static final String POSITIVE = "POSITIVE";
    private static final String NEGATIVE = "NEGATIVE";
    private static final String[] ONE_PERM = new String[]{Manifest.permission.READ_SMS};
    private static final String[] ALL_PERMS = new String[]{
            Manifest.permission.READ_SMS, Manifest.permission.ACCESS_FINE_LOCATION};
    private static final int[] SMS_DENIED_RESULT = new int[]{
            PackageManager.PERMISSION_DENIED, PackageManager.PERMISSION_GRANTED};

    private ShadowApplication shadowApp;
    private Application app;
    private TestActivity spyActivity;
    private TestSupportFragmentActivity spySupportFragmentActivity;
    private TestAppCompatActivity spyAppCompatActivity;
    private TestFragment spyFragment;
    private FragmentController<TestFragment> fragmentController;
    private ActivityController<TestActivity> activityController;
    private ActivityController<TestSupportFragmentActivity> supportFragmentActivityController;
    private ActivityController<TestAppCompatActivity> appCompatActivityController;
    @Captor
    private ArgumentCaptor<Integer> integerCaptor;
    @Captor
    private ArgumentCaptor<ArrayList<String>> listCaptor;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        app = ApplicationProvider.getApplicationContext();
        shadowApp = shadowOf(app);

        activityController = new ActivityController<>(TestActivity.class);
        supportFragmentActivityController = new ActivityController<>(TestSupportFragmentActivity.class);
        appCompatActivityController = new ActivityController<>(TestAppCompatActivity.class);
        fragmentController = new FragmentController<>(TestFragment.class);

        spyActivity = Mockito.spy(activityController.resume());
        spySupportFragmentActivity = Mockito.spy(supportFragmentActivityController.resume());
        spyAppCompatActivity = Mockito.spy(appCompatActivityController.resume());
        spyFragment = Mockito.spy(fragmentController.resume());
    }

    // ------ General tests ------

    @Test
    public void shouldNotHavePermissions_whenNoPermissionsGranted() {
        assertThat(EasyPermissions.hasPermissions(app, ALL_PERMS)).isFalse();
    }

    @Test
    public void shouldNotHavePermissions_whenNotAllPermissionsGranted() {
        shadowApp.grantPermissions(ONE_PERM);
        assertThat(EasyPermissions.hasPermissions(app, ALL_PERMS)).isFalse();
    }

    @Test
    public void shouldHavePermissions_whenAllPermissionsGranted() {
        shadowApp.grantPermissions(ALL_PERMS);
        assertThat(EasyPermissions.hasPermissions(app, ALL_PERMS)).isTrue();
    }

    @SuppressWarnings("ConstantConditions")
    @Test
    public void shouldThrowException_whenHasPermissionsWithNullContext() {
        try {
            EasyPermissions.hasPermissions(null, ALL_PERMS);
            fail("IllegalStateException expected because of null context.");
        } catch (IllegalArgumentException e) {
            assertThat(e).hasMessageThat()
                    .isEqualTo("Can't check permissions for null context");
        }
    }

    // ------ From Activity ------

    @Test
    public void shouldCorrectlyCallback_whenOnRequestPermissionResultCalledFromActivity() {
        EasyPermissions.onRequestPermissionsResult(TestActivity.REQUEST_CODE, ALL_PERMS, SMS_DENIED_RESULT, spyActivity);

        verify(spyActivity, times(1))
                .onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(TestActivity.REQUEST_CODE);
        assertThat(listCaptor.getValue())
                .containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION)));

        verify(spyActivity, times(1))
                .onPermissionsDenied(integerCaptor.capture(), listCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(TestActivity.REQUEST_CODE);
        assertThat(listCaptor.getValue())
                .containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.READ_SMS)));

        verify(spyActivity, never()).afterPermissionGranted();
    }

    @Test
    public void shouldCallbackOnPermissionGranted_whenRequestAlreadyGrantedPermissionsFromActivity() {
        grantPermissions(ALL_PERMS);

        EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);

        verify(spyActivity, times(1))
                .onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
        verify(spyActivity, never()).requestPermissions(any(String[].class), anyInt());
        assertThat(integerCaptor.getValue()).isEqualTo(TestActivity.REQUEST_CODE);
        assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
    }

    @Test
    public void shouldCallbackAfterPermissionGranted_whenRequestAlreadyGrantedPermissionsFromActivity() {
        grantPermissions(ALL_PERMS);

        EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);

        // Called 2 times because this is a spy and library implementation invokes super classes annotated methods as well
        verify(spyActivity, times(2)).afterPermissionGranted();
    }

    @Test
    public void shouldNotCallbackAfterPermissionGranted_whenRequestNotGrantedPermissionsFromActivity() {
        grantPermissions(ONE_PERM);

        EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);

        verify(spyActivity, never()).afterPermissionGranted();
    }

    @Test
    public void shouldRequestPermissions_whenMissingPermissionAndNotShowRationaleFromActivity() {
        grantPermissions(ONE_PERM);
        showRationale(false, ALL_PERMS);

        EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);

        verify(spyActivity, times(1))
                .requestPermissions(ALL_PERMS, TestActivity.REQUEST_CODE);
    }

    @Test
    public void shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromActivity() {
        grantPermissions(ONE_PERM);
        showRationale(true, ALL_PERMS);

        EasyPermissions.requestPermissions(spyActivity, RATIONALE, TestActivity.REQUEST_CODE, ALL_PERMS);

        Fragment dialogFragment = spyActivity.getFragmentManager()
                .findFragmentByTag(RationaleDialogFragment.TAG);
        assertThat(dialogFragment).isInstanceOf(RationaleDialogFragment.class);

        Dialog dialog = ((RationaleDialogFragment) dialogFragment).getDialog();
        assertThatHasExpectedRationale(dialog, RATIONALE);
    }

    @Test
    public void shouldShowCorrectDialogUsingRequest_whenMissingPermissionsAndShowRationaleFromActivity() {
        grantPermissions(ONE_PERM);
        showRationale(true, ALL_PERMS);

        PermissionRequest request = new PermissionRequest.Builder(spyActivity, TestActivity.REQUEST_CODE, ALL_PERMS)
                .setPositiveButtonText(android.R.string.ok)
                .setNegativeButtonText(android.R.string.cancel)
                .setRationale(android.R.string.unknownName)
                .setTheme(R.style.Theme_AppCompat)
                .build();
        EasyPermissions.requestPermissions(request);

        Fragment dialogFragment = spyActivity.getFragmentManager()
                .findFragmentByTag(RationaleDialogFragment.TAG);
        assertThat(dialogFragment).isInstanceOf(RationaleDialogFragment.class);

        Dialog dialog = ((RationaleDialogFragment) dialogFragment).getDialog();
        assertThatHasExpectedButtonsAndRationale(dialog, android.R.string.unknownName,
                android.R.string.ok, android.R.string.cancel);
    }

    @Test
    public void shouldHaveSomePermissionDenied_whenShowRationaleFromActivity() {
        showRationale(true, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionDenied(spyActivity, ALL_PERMS)).isTrue();
    }

    @Test
    public void shouldNotHaveSomePermissionDenied_whenNotShowRationaleFromActivity() {
        showRationale(false, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionDenied(spyActivity, ALL_PERMS)).isFalse();
    }

    @Test
    public void shouldHaveSomePermissionPermanentlyDenied_whenNotShowRationaleFromActivity() {
        showRationale(false, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyActivity, Arrays.asList(ALL_PERMS))).isTrue();
    }

    @Test
    public void shouldNotHaveSomePermissionPermanentlyDenied_whenShowRationaleFromActivity() {
        showRationale(true, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyActivity, Arrays.asList(ALL_PERMS))).isFalse();
    }

    @Test
    public void shouldHavePermissionPermanentlyDenied_whenNotShowRationaleFromActivity() {
        showRationale(false, Manifest.permission.READ_SMS);

        assertThat(EasyPermissions.permissionPermanentlyDenied(spyActivity, Manifest.permission.READ_SMS)).isTrue();
    }

    @Test
    public void shouldNotHavePermissionPermanentlyDenied_whenShowRationaleFromActivity() {
        showRationale(true, Manifest.permission.READ_SMS);

        assertThat(EasyPermissions.permissionPermanentlyDenied(spyActivity, Manifest.permission.READ_SMS)).isFalse();
    }

    @Test
    public void shouldCorrectlyCallback_whenOnRequestPermissionResultCalledFromAppCompatActivity() {
        EasyPermissions.onRequestPermissionsResult(TestAppCompatActivity.REQUEST_CODE, ALL_PERMS, SMS_DENIED_RESULT, spyAppCompatActivity);

        verify(spyAppCompatActivity, times(1))
                .onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(TestAppCompatActivity.REQUEST_CODE);
        assertThat(listCaptor.getValue())
                .containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION)));

        verify(spyAppCompatActivity, times(1))
                .onPermissionsDenied(integerCaptor.capture(), listCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(TestAppCompatActivity.REQUEST_CODE);
        assertThat(listCaptor.getValue())
                .containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.READ_SMS)));

        verify(spyAppCompatActivity, never()).afterPermissionGranted();
    }

    @Test
    public void shouldCallbackOnPermissionGranted_whenRequestAlreadyGrantedPermissionsFromAppCompatActivity() {
        grantPermissions(ALL_PERMS);

        EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);

        verify(spyAppCompatActivity, times(1))
                .onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
        verify(spyAppCompatActivity, never()).requestPermissions(any(String[].class), anyInt());
        assertThat(integerCaptor.getValue()).isEqualTo(TestAppCompatActivity.REQUEST_CODE);
        assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
    }

    @Test
    public void shouldCallbackAfterPermissionGranted_whenRequestAlreadyGrantedPermissionsFromAppCompatActivity() {
        grantPermissions(ALL_PERMS);

        EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);

        // Called 2 times because this is a spy and library implementation invokes super classes annotated methods as well
        verify(spyAppCompatActivity, times(2)).afterPermissionGranted();
    }

    @Test
    public void shouldNotCallbackAfterPermissionGranted_whenRequestNotGrantedPermissionsFromAppCompatActivity() {
        grantPermissions(ONE_PERM);

        EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);

        verify(spyAppCompatActivity, never()).afterPermissionGranted();
    }

    @Test
    public void shouldRequestPermissions_whenMissingPermissionAndNotShowRationaleFromAppCompatActivity() {
        grantPermissions(ONE_PERM);
        showRationale(false, ALL_PERMS);

        EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);

        verify(spyAppCompatActivity, times(1))
                .requestPermissions(ALL_PERMS, TestAppCompatActivity.REQUEST_CODE);
    }

    @Test
    public void shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromAppCompatActivity() {
        grantPermissions(ONE_PERM);
        showRationale(true, ALL_PERMS);

        EasyPermissions.requestPermissions(spyAppCompatActivity, RATIONALE, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS);

        androidx.fragment.app.Fragment dialogFragment = spyAppCompatActivity.getSupportFragmentManager()
                .findFragmentByTag(RationaleDialogFragmentCompat.TAG);
        assertThat(dialogFragment).isInstanceOf(RationaleDialogFragmentCompat.class);

        Dialog dialog = ((RationaleDialogFragmentCompat) dialogFragment).getDialog();
        assertThatHasExpectedRationale(dialog, RATIONALE);
    }

    @Test
    public void shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromSupportFragmentActivity() {
        grantPermissions(ONE_PERM);
        showRationale(true, ALL_PERMS);

        EasyPermissions.requestPermissions(spySupportFragmentActivity, RATIONALE, TestSupportFragmentActivity.REQUEST_CODE, ALL_PERMS);

        Fragment dialogFragment = spySupportFragmentActivity.getFragmentManager()
                .findFragmentByTag(RationaleDialogFragment.TAG);
        assertThat(dialogFragment).isInstanceOf(RationaleDialogFragment.class);

        Dialog dialog = ((RationaleDialogFragment) dialogFragment).getDialog();
        assertThatHasExpectedRationale(dialog, RATIONALE);
    }

    @Test
    public void shouldShowCorrectDialogUsingRequest_whenMissingPermissionsAndShowRationaleFromAppCompatActivity() {
        grantPermissions(ONE_PERM);
        showRationale(true, ALL_PERMS);

        PermissionRequest request = new PermissionRequest.Builder(spyAppCompatActivity, TestAppCompatActivity.REQUEST_CODE, ALL_PERMS)
                .setPositiveButtonText(android.R.string.ok)
                .setNegativeButtonText(android.R.string.cancel)
                .setRationale(android.R.string.unknownName)
                .setTheme(R.style.Theme_AppCompat)
                .build();
        EasyPermissions.requestPermissions(request);

        androidx.fragment.app.Fragment dialogFragment = spyAppCompatActivity.getSupportFragmentManager()
                .findFragmentByTag(RationaleDialogFragmentCompat.TAG);
        assertThat(dialogFragment).isInstanceOf(RationaleDialogFragmentCompat.class);

        Dialog dialog = ((RationaleDialogFragmentCompat) dialogFragment).getDialog();
        assertThatHasExpectedButtonsAndRationale(dialog, android.R.string.unknownName,
                android.R.string.ok, android.R.string.cancel);
    }

    @Test
    public void shouldHaveSomePermissionDenied_whenShowRationaleFromAppCompatActivity() {
        showRationale(true, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionDenied(spyAppCompatActivity, ALL_PERMS)).isTrue();
    }

    @Test
    public void shouldNotHaveSomePermissionDenied_whenNotShowRationaleFromAppCompatActivity() {
        showRationale(false, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionDenied(spyAppCompatActivity, ALL_PERMS)).isFalse();
    }

    @Test
    public void shouldHaveSomePermissionPermanentlyDenied_whenNotShowRationaleFromAppCompatActivity() {
        showRationale(false, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyAppCompatActivity, Arrays.asList(ALL_PERMS))).isTrue();
    }

    @Test
    public void shouldNotHaveSomePermissionPermanentlyDenied_whenShowRationaleFromAppCompatActivity() {
        showRationale(true, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyAppCompatActivity, Arrays.asList(ALL_PERMS))).isFalse();
    }

    @Test
    public void shouldHavePermissionPermanentlyDenied_whenNotShowRationaleFromAppCompatActivity() {
        showRationale(false, Manifest.permission.READ_SMS);

        assertThat(EasyPermissions.permissionPermanentlyDenied(spyAppCompatActivity, Manifest.permission.READ_SMS)).isTrue();
    }

    @Test
    public void shouldNotHavePermissionPermanentlyDenied_whenShowRationaleFromAppCompatActivity() {
        showRationale(true, Manifest.permission.READ_SMS);

        assertThat(EasyPermissions.permissionPermanentlyDenied(spyAppCompatActivity, Manifest.permission.READ_SMS)).isFalse();
    }

    @Test
    public void shouldCorrectlyCallback_whenOnRequestPermissionResultCalledFromFragment() {
        EasyPermissions.onRequestPermissionsResult(TestFragment.REQUEST_CODE, ALL_PERMS, SMS_DENIED_RESULT,
                spyFragment);

        verify(spyFragment, times(1))
                .onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(TestFragment.REQUEST_CODE);
        assertThat(listCaptor.getValue())
                .containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION)));

        verify(spyFragment, times(1))
                .onPermissionsDenied(integerCaptor.capture(), listCaptor.capture());
        assertThat(integerCaptor.getValue()).isEqualTo(TestFragment.REQUEST_CODE);
        assertThat(listCaptor.getValue())
                .containsAllIn(new ArrayList<>(Collections.singletonList(Manifest.permission.READ_SMS)));

        verify(spyFragment, never()).afterPermissionGranted();
    }

    @Test
    public void shouldCallbackOnPermissionGranted_whenRequestAlreadyGrantedPermissionsFromFragment() {
        grantPermissions(ALL_PERMS);

        EasyPermissions.requestPermissions(spyFragment, RATIONALE,
                TestFragment.REQUEST_CODE, ALL_PERMS);

        verify(spyFragment, times(1))
                .onPermissionsGranted(integerCaptor.capture(), listCaptor.capture());
        verify(spyFragment, never()).requestPermissions(any(String[].class), anyInt());
        assertThat(integerCaptor.getValue()).isEqualTo(TestFragment.REQUEST_CODE);
        assertThat(listCaptor.getValue()).containsAllIn(ALL_PERMS);
    }

    @Test
    public void shouldCallbackAfterPermissionGranted_whenRequestAlreadyGrantedPermissionsFragment() {
        grantPermissions(ALL_PERMS);

        EasyPermissions.requestPermissions(spyFragment, RATIONALE, TestFragment.REQUEST_CODE, ALL_PERMS);

        // Called 2 times because this is a spy and library implementation invokes super classes annotated methods as well
        verify(spyFragment, times(2)).afterPermissionGranted();
    }

    @Test
    public void shouldNotCallbackAfterPermissionGranted_whenRequestNotGrantedPermissionsFromFragment() {
        grantPermissions(ONE_PERM);

        EasyPermissions.requestPermissions(spyFragment, RATIONALE, TestFragment.REQUEST_CODE, ALL_PERMS);

        verify(spyFragment, never()).afterPermissionGranted();
    }

    @Test
    public void shouldRequestPermissions_whenMissingPermissionsAndNotShowRationaleFromFragment() {
        grantPermissions(ONE_PERM);
        showRationale(false, ALL_PERMS);

        EasyPermissions.requestPermissions(spyFragment, RATIONALE, TestFragment.REQUEST_CODE, ALL_PERMS);

        verify(spyFragment, times(1))
                .requestPermissions(ALL_PERMS, TestFragment.REQUEST_CODE);
    }

    @Test
    public void shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromFragment() {
        grantPermissions(ONE_PERM);
        showRationale(true, ALL_PERMS);

        EasyPermissions.requestPermissions(spyFragment, RATIONALE, TestFragment.REQUEST_CODE, ALL_PERMS);

        androidx.fragment.app.Fragment dialogFragment = spyFragment.getChildFragmentManager()
                .findFragmentByTag(RationaleDialogFragmentCompat.TAG);
        assertThat(dialogFragment).isInstanceOf(RationaleDialogFragmentCompat.class);

        Dialog dialog = ((RationaleDialogFragmentCompat) dialogFragment).getDialog();
        assertThatHasExpectedRationale(dialog, RATIONALE);
    }

    @Test
    public void shouldShowCorrectDialogUsingRequest_whenMissingPermissionsAndShowRationaleFromFragment() {
        grantPermissions(ONE_PERM);
        showRationale(true, ALL_PERMS);

        PermissionRequest request = new PermissionRequest.Builder(spyFragment, TestFragment.REQUEST_CODE, ALL_PERMS)
                .setPositiveButtonText(POSITIVE)
                .setNegativeButtonText(NEGATIVE)
                .setRationale(RATIONALE)
                .setTheme(R.style.Theme_AppCompat)
                .build();
        EasyPermissions.requestPermissions(request);

        androidx.fragment.app.Fragment dialogFragment = spyFragment.getChildFragmentManager()
                .findFragmentByTag(RationaleDialogFragmentCompat.TAG);
        assertThat(dialogFragment).isInstanceOf(RationaleDialogFragmentCompat.class);

        Dialog dialog = ((RationaleDialogFragmentCompat) dialogFragment).getDialog();
        assertThatHasExpectedButtonsAndRationale(dialog, RATIONALE, POSITIVE, NEGATIVE);
    }

    @Test
    public void shouldHaveSomePermissionDenied_whenShowRationaleFromFragment() {
        showRationale(true, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionDenied(spyFragment, ALL_PERMS)).isTrue();
    }

    @Test
    public void shouldNotHaveSomePermissionDenied_whenNotShowRationaleFromFragment() {
        showRationale(false, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionDenied(spyFragment, ALL_PERMS)).isFalse();
    }

    @Test
    public void shouldHaveSomePermissionPermanentlyDenied_whenNotShowRationaleFromFragment() {
        showRationale(false, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyFragment, Arrays.asList(ALL_PERMS))).isTrue();
    }

    @Test
    public void shouldNotHaveSomePermissionPermanentlyDenied_whenShowRationaleFromFragment() {
        showRationale(true, ALL_PERMS);

        assertThat(EasyPermissions.somePermissionPermanentlyDenied(spyFragment, Arrays.asList(ALL_PERMS))).isFalse();
    }


    @Test
    public void shouldHavePermissionPermanentlyDenied_whenNotShowRationaleFromFragment() {
        showRationale(false, Manifest.permission.READ_SMS);

        assertThat(EasyPermissions.permissionPermanentlyDenied(spyFragment, Manifest.permission.READ_SMS)).isTrue();
    }

    @Test
    public void shouldNotHavePermissionPermanentlyDenied_whenShowRationaleFromFragment() {
        showRationale(true, Manifest.permission.READ_SMS);

        assertThat(EasyPermissions.permissionPermanentlyDenied(spyFragment, Manifest.permission.READ_SMS)).isFalse();
    }

    private void assertThatHasExpectedButtonsAndRationale(Dialog dialog, int rationale,
                                                          int positive, int negative) {
        TextView dialogMessage = dialog.findViewById(android.R.id.message);
        assertThat(dialogMessage.getText().toString()).isEqualTo(app.getString(rationale));
        TextView positiveMessage = dialog.findViewById(android.R.id.button1);
        assertThat(positiveMessage.getText().toString()).isEqualTo(app.getString(positive));
        TextView negativeMessage = dialog.findViewById(android.R.id.button2);
        assertThat(negativeMessage.getText().toString()).isEqualTo(app.getString(negative));
    }

    private void assertThatHasExpectedButtonsAndRationale(Dialog dialog, String rationale,
                                                          int positive, int negative) {
        TextView dialogMessage = dialog.findViewById(android.R.id.message);
        assertThat(dialogMessage.getText().toString()).isEqualTo(rationale);
        TextView positiveMessage = dialog.findViewById(android.R.id.button1);
        assertThat(positiveMessage.getText().toString()).isEqualTo(app.getString(positive));
        TextView negativeMessage = dialog.findViewById(android.R.id.button2);
        assertThat(negativeMessage.getText().toString()).isEqualTo(app.getString(negative));
    }

    private void assertThatHasExpectedButtonsAndRationale(Dialog dialog, String rationale,
                                                          String positive, String negative) {
        TextView dialogMessage = dialog.findViewById(android.R.id.message);
        assertThat(dialogMessage.getText().toString()).isEqualTo(rationale);
        TextView positiveMessage = dialog.findViewById(android.R.id.button1);
        assertThat(positiveMessage.getText().toString()).isEqualTo(positive);
        TextView negativeMessage = dialog.findViewById(android.R.id.button2);
        assertThat(negativeMessage.getText().toString()).isEqualTo(negative);
    }

    private void assertThatHasExpectedRationale(Dialog dialog, String rationale) {
        TextView dialogMessage = dialog.findViewById(android.R.id.message);
        assertThat(dialogMessage.getText().toString()).isEqualTo(rationale);
    }

    private void grantPermissions(String[] perms) {
        shadowApp.grantPermissions(perms);
    }

    private void showRationale(boolean show, String... perms) {
        for (String perm : perms) {
            when(spyActivity.shouldShowRequestPermissionRationale(perm)).thenReturn(show);
            when(spySupportFragmentActivity.shouldShowRequestPermissionRationale(perm)).thenReturn(show);
            when(spyAppCompatActivity.shouldShowRequestPermissionRationale(perm)).thenReturn(show);
            when(spyFragment.shouldShowRequestPermissionRationale(perm)).thenReturn(show);
        }
    }
}


================================================
FILE: easypermissions/src/test/java/pub/devrel/easypermissions/RationaleDialogClickListenerTest.java
================================================
package pub.devrel.easypermissions;

import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;

import androidx.fragment.app.Fragment;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import java.util.Arrays;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class RationaleDialogClickListenerTest {

    private static final int REQUEST_CODE = 5;
    private static final String[] PERMS = new String[]{
            Manifest.permission.READ_SMS, Manifest.permission.ACCESS_FINE_LOCATION};
    @Mock
    private RationaleDialogFragment dialogFragment;
    @Mock
    private RationaleDialogFragmentCompat dialogFragmentCompat;
    @Mock
    private RationaleDialogConfig dialogConfig;
    @Mock
    private EasyPermissions.PermissionCallbacks permissionCallbacks;
    @Mock
    private EasyPermissions.RationaleCallbacks rationaleCallbacks;
    @Mock
    private DialogInterface dialogInterface;
    @Mock
    private Activity activity;
    @Mock
    private Fragment fragment;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        when(dialogFragment.getActivity()).thenReturn(activity);
        dialogConfig.requestCode = REQUEST_CODE;
        dialogConfig.permissions = PERMS;
    }

    @Test
    public void shouldOnRationaleAccepted_whenPositiveButtonWithRationaleCallbacks() {
        RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
                permissionCallbacks, rationaleCallbacks);
        listener.onClick(dialogInterface, Dialog.BUTTON_POSITIVE);

        verify(rationaleCallbacks, times(1)).onRationaleAccepted(REQUEST_CODE);
    }

    @Test
    public void shouldNotOnRationaleAccepted_whenPositiveButtonWithoutRationaleCallbacks() {
        RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
                permissionCallbacks, null);
        listener.onClick(dialogInterface, Dialog.BUTTON_POSITIVE);

        verify(rationaleCallbacks, never()).onRationaleAccepted(anyInt());
    }

    @Test
    public void shouldRequestPermissions_whenPositiveButtonFromActivity() {
        RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
                permissionCallbacks, rationaleCallbacks);
        listener.onClick(dialogInterface, Dialog.BUTTON_POSITIVE);

        verify(activity, times(1)).requestPermissions(PERMS, REQUEST_CODE);
    }

    @Test
    public void shouldRequestPermissions_whenPositiveButtonFromFragment() {
        when(dialogFragmentCompat.getParentFragment()).thenReturn(fragment);

        RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragmentCompat, dialogConfig,
                permissionCallbacks, rationaleCallbacks);
        listener.onClick(dialogInterface, Dialog.BUTTON_POSITIVE);

        verify(fragment, times(1)).requestPermissions(PERMS, REQUEST_CODE);
    }

    @Test
    public void shouldOnRationaleDenied_whenNegativeButtonWithRationaleCallbacks() {
        RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
                permissionCallbacks, rationaleCallbacks);
        listener.onClick(dialogInterface, Dialog.BUTTON_NEGATIVE);

        verify(rationaleCallbacks, times(1)).onRationaleDenied(REQUEST_CODE);
    }

    @Test
    public void shouldNotOnRationaleDenied_whenNegativeButtonWithoutRationaleCallbacks() {
        RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
                permissionCallbacks, null);
        listener.onClick(dialogInterface, Dialog.BUTTON_NEGATIVE);

        verify(rationaleCallbacks, never()).onRationaleDenied(anyInt());
    }

    @Test
    public void shouldOnPermissionsDenied_whenNegativeButtonWithPermissionCallbacks() {
        RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
                permissionCallbacks, rationaleCallbacks);
        listener.onClick(dialogInterface, Dialog.BUTTON_NEGATIVE);

        verify(permissionCallbacks, times(1))
                .onPermissionsDenied(REQUEST_CODE, Arrays.asList(PERMS));
    }

    @Test
    public void shouldNotOnPermissionsDenied_whenNegativeButtonWithoutPermissionCallbacks() {
        RationaleDialogClickListener listener = new RationaleDialogClickListener(dialogFragment, dialogConfig,
                null, rationaleCallbacks);
        listener.onClick(dialogInterface, Dialog.BUTTON_NEGATIVE);

        verify(permissionCallbacks, never()).onPermissionsDenied(anyInt(), ArgumentMatchers.<String>anyList());
    }
}


================================================
FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/ActivityController.java
================================================
package pub.devrel.easypermissions.testhelper;

import android.app.Activity;

import androidx.annotation.NonNull;
import androidx.test.core.app.ActivityScenario;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * Helper class to allow starting Activity, similar to the Robolectric ActivityConroller.
 */
public class ActivityController<T extends Activity> {

    private ActivityScenario<T> scenario;

    public ActivityController(Class<T> clazz) {
        scenario = ActivityScenario.launch(clazz);
    }

    public synchronized T resume() {
        final CompletableFuture<T> ActivityFuture = new CompletableFuture<>();

        scenario.onActivity(new ActivityScenario.ActivityAction<T>() {
            @Override
            public void perform(@NonNull T activity) {
                ActivityFuture.complete(activity);
            }
        });

        try {
            return ActivityFuture.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    public void reset() {
        scenario.recreate();
    }

}


================================================
FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/FragmentController.java
================================================
package pub.devrel.easypermissions.testhelper;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.testing.FragmentScenario;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * Helper class to allow starting Fragments, similar to the old SupportFragmentController.
 */
public class FragmentController<T extends Fragment> {

    private FragmentScenario<T> scenario;

    public FragmentController(Class<T> clazz) {
        scenario = FragmentScenario.launch(clazz);
    }

    public synchronized T resume() {
        final CompletableFuture<T> fragmentFuture = new CompletableFuture<>();

        scenario.onFragment(new FragmentScenario.FragmentAction<T>() {
            @Override
            public void perform(@NonNull T fragment) {
                fragmentFuture.complete(fragment);
            }
        });

        try {
            return fragmentFuture.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    public void reset() {
        scenario.recreate();
    }

}


================================================
FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestActivity.java
================================================
package pub.devrel.easypermissions.testhelper;

import android.app.Activity;

import java.util.List;

import androidx.annotation.NonNull;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;

public class TestActivity extends Activity
        implements EasyPermissions.PermissionCallbacks, EasyPermissions.RationaleCallbacks {

    public static final int REQUEST_CODE = 1;

    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {

    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {

    }

    @AfterPermissionGranted(REQUEST_CODE)
    public void afterPermissionGranted() {

    }

    @Override
    public void onRationaleAccepted(int requestCode) {

    }

    @Override
    public void onRationaleDenied(int requestCode) {

    }
}


================================================
FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestAppCompatActivity.java
================================================
package pub.devrel.easypermissions.testhelper;

import android.os.Bundle;

import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;
import pub.devrel.easypermissions.R;

public class TestAppCompatActivity extends AppCompatActivity
        implements EasyPermissions.PermissionCallbacks, EasyPermissions.RationaleCallbacks {

    public static final int REQUEST_CODE = 3;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        getTheme().applyStyle(R.style.Theme_AppCompat, true);
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {

    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {

    }

    @AfterPermissionGranted(REQUEST_CODE)
    public void afterPermissionGranted() {

    }

    @Override
    public void onRationaleAccepted(int requestCode) {

    }

    @Override
    public void onRationaleDenied(int requestCode) {

    }

}


================================================
FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestFragment.java
================================================
package pub.devrel.easypermissions.testhelper;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;
import pub.devrel.easypermissions.R;

public class TestFragment extends Fragment
        implements EasyPermissions.PermissionCallbacks, EasyPermissions.RationaleCallbacks {

    public static final int REQUEST_CODE = 4;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        getContext().getTheme().applyStyle(R.style.Theme_AppCompat, true);
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {

    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {

    }

    @AfterPermissionGranted(REQUEST_CODE)
    public void afterPermissionGranted() {

    }

    @Override
    public void onRationaleAccepted(int requestCode) {

    }

    @Override
    public void onRationaleDenied(int requestCode) {

    }
}


================================================
FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestSupportFragmentActivity.java
================================================
package pub.devrel.easypermissions.testhelper;

import android.os.Bundle;

import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import pub.devrel.easypermissions.AfterPermissionGranted;
import pub.devrel.easypermissions.EasyPermissions;

public class TestSupportFragmentActivity extends FragmentActivity
        implements EasyPermissions.PermissionCallbacks, EasyPermissions.RationaleCallbacks {

    public static final int REQUEST_CODE = 5;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
    }

    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {

    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {

    }

    @AfterPermissionGranted(REQUEST_CODE)
    public void afterPermissionGranted() {

    }

    @Override
    public void onRationaleAccepted(int requestCode) {

    }

    @Override
    public void onRationaleDenied(int requestCode) {

    }
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
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


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
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


================================================
FILE: gradlew
================================================
#!/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
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

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='"-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn () {
    echo "$*"
}

die () {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
  NONSTOP* )
    nonstop=true
    ;;
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
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# 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
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        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" ;;
    esac
fi

# Escape application args
save () {
    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
    echo " "
}
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"

exec "$JAVACMD" "$@"


================================================
FILE: gradlew.bat
================================================
@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
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
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="-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 execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

: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 %*

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: settings.gradle
================================================
include ':app', ':easypermissions'
Download .txt
gitextract_zbuou_b2/

├── .github/
│   ├── ISSUE_TEMPLATE.md
│   └── workflows/
│       └── test.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── PUBLISHING.md
├── README.md
├── app/
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── pub/
│           │       └── devrel/
│           │           └── easypermissions/
│           │               └── sample/
│           │                   ├── MainActivity.java
│           │                   └── MainFragment.java
│           └── res/
│               ├── layout/
│               │   ├── activity_basic.xml
│               │   ├── activity_main.xml
│               │   └── fragment_main.xml
│               ├── values/
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               └── values-w820dp/
│                   └── dimens.xml
├── build.gradle
├── easypermissions/
│   ├── build.gradle
│   ├── gradle.properties
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── pub/
│       │   │       └── devrel/
│       │   │           └── easypermissions/
│       │   │               ├── AfterPermissionGranted.java
│       │   │               ├── AppSettingsDialog.java
│       │   │               ├── AppSettingsDialogHolderActivity.java
│       │   │               ├── EasyPermissions.java
│       │   │               ├── PermissionRequest.java
│       │   │               ├── RationaleDialogClickListener.java
│       │   │               ├── RationaleDialogConfig.java
│       │   │               ├── RationaleDialogFragment.java
│       │   │               ├── RationaleDialogFragmentCompat.java
│       │   │               └── helper/
│       │   │                   ├── ActivityPermissionHelper.java
│       │   │                   ├── AppCompatActivityPermissionsHelper.java
│       │   │                   ├── BaseSupportPermissionsHelper.java
│       │   │                   ├── LowApiPermissionsHelper.java
│       │   │                   ├── PermissionHelper.java
│       │   │                   ├── SupportFragmentPermissionHelper.java
│       │   │                   └── package-info.java
│       │   └── res/
│       │       └── values/
│       │           ├── colors.xml
│       │           ├── strings.xml
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── pub/
│                   └── devrel/
│                       └── easypermissions/
│                           ├── AppSettingsDialogTest.java
│                           ├── EasyPermissionsLowApiTest.java
│                           ├── EasyPermissionsTest.java
│                           ├── RationaleDialogClickListenerTest.java
│                           └── testhelper/
│                               ├── ActivityController.java
│                               ├── FragmentController.java
│                               ├── TestActivity.java
│                               ├── TestAppCompatActivity.java
│                               ├── TestFragment.java
│                               └── TestSupportFragmentActivity.java
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
Download .txt
SYMBOL INDEX (276 symbols across 26 files)

FILE: app/src/main/java/pub/devrel/easypermissions/sample/MainActivity.java
  class MainActivity (line 33) | public class MainActivity extends AppCompatActivity implements EasyPermi...
    method onCreate (line 43) | @Override
    method hasCameraPermission (line 55) | private boolean hasCameraPermission() {
    method hasLocationAndContactsPermissions (line 59) | private boolean hasLocationAndContactsPermissions() {
    method hasSmsPermission (line 63) | private boolean hasSmsPermission() {
    method hasStoragePermission (line 67) | private boolean hasStoragePermission() {
    method cameraTask (line 71) | @AfterPermissionGranted(RC_CAMERA_PERM)
    method locationAndContactsTask (line 86) | @AfterPermissionGranted(RC_LOCATION_CONTACTS_PERM)
    method onRequestPermissionsResult (line 101) | @Override
    method onPermissionsGranted (line 111) | @Override
    method onPermissionsDenied (line 116) | @Override
    method onActivityResult (line 127) | @Override
    method onRationaleAccepted (line 147) | @Override
    method onRationaleDenied (line 152) | @Override

FILE: app/src/main/java/pub/devrel/easypermissions/sample/MainFragment.java
  class MainFragment (line 22) | public class MainFragment extends Fragment implements EasyPermissions.Pe...
    method onCreateView (line 27) | @Override
    method onRequestPermissionsResult (line 42) | @Override
    method smsTask (line 52) | @AfterPermissionGranted(RC_SMS_PERM)
    method onPermissionsGranted (line 64) | @Override
    method onPermissionsDenied (line 69) | @Override

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialog.java
  class AppSettingsDialog (line 27) | public class AppSettingsDialog implements Parcelable {
    method createFromParcel (line 35) | @Override
    method newArray (line 40) | @Override
    method AppSettingsDialog (line 60) | private AppSettingsDialog(Parcel in) {
    method AppSettingsDialog (line 70) | private AppSettingsDialog(@NonNull final Object activityOrFragment,
    method fromIntent (line 88) | static AppSettingsDialog fromIntent(Intent intent, Activity activity) {
    method setActivityOrFragment (line 107) | private void setActivityOrFragment(Object activityOrFragment) {
    method startForResult (line 119) | private void startForResult(Intent intent) {
    method show (line 130) | public void show() {
    method showDialog (line 137) | AlertDialog showDialog(DialogInterface.OnClickListener positiveListener,
    method describeContents (line 154) | @Override
    method writeToParcel (line 159) | @Override
    method getIntentFlags (line 170) | int getIntentFlags() {
    class Builder (line 177) | public static class Builder {
      method Builder (line 195) | public Builder(@NonNull Activity activity) {
      method Builder (line 205) | public Builder(@NonNull Fragment fragment) {
      method setThemeResId (line 213) | @NonNull
      method setTitle (line 222) | @NonNull
      method setTitle (line 231) | @NonNull
      method setRationale (line 242) | @NonNull
      method setRationale (line 253) | @NonNull
      method setPositiveButton (line 262) | @NonNull
      method setPositiveButton (line 271) | @NonNull
      method setNegativeButton (line 285) | @NonNull
      method setNegativeButton (line 294) | @NonNull
      method setRequestCode (line 305) | @NonNull
      method setOpenInNewTask (line 316) | @NonNull
      method build (line 326) | @NonNull

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialogHolderActivity.java
  class AppSettingsDialogHolderActivity (line 15) | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    method createShowDialogIntent (line 22) | public static Intent createShowDialogIntent(Context context, AppSettin...
    method onCreate (line 28) | @Override
    method onDestroy (line 36) | @Override
    method onClick (line 44) | @Override
    method onActivityResult (line 59) | @Override

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java
  class EasyPermissions (line 39) | public class EasyPermissions {
    type PermissionCallbacks (line 45) | public interface PermissionCallbacks extends ActivityCompat.OnRequestP...
      method onPermissionsGranted (line 47) | void onPermissionsGranted(int requestCode, @NonNull List<String> per...
      method onPermissionsDenied (line 49) | void onPermissionsDenied(int requestCode, @NonNull List<String> perms);
    type RationaleCallbacks (line 55) | public interface RationaleCallbacks {
      method onRationaleAccepted (line 56) | void onRationaleAccepted(int requestCode);
      method onRationaleDenied (line 58) | void onRationaleDenied(int requestCode);
    method hasPermissions (line 72) | public static boolean hasPermissions(@NonNull Context context,
    method requestPermissions (line 108) | public static void requestPermissions(
    method requestPermissions (line 122) | public static void requestPermissions(
    method requestPermissions (line 137) | public static void requestPermissions(PermissionRequest request) {
    method onRequestPermissionsResult (line 171) | public static void onRequestPermissionsResult(@IntRange(from = 0, to =...
    method somePermissionPermanentlyDenied (line 225) | public static boolean somePermissionPermanentlyDenied(@NonNull Activit...
    method somePermissionPermanentlyDenied (line 234) | public static boolean somePermissionPermanentlyDenied(@NonNull Fragmen...
    method permissionPermanentlyDenied (line 247) | public static boolean permissionPermanentlyDenied(@NonNull Activity host,
    method permissionPermanentlyDenied (line 255) | public static boolean permissionPermanentlyDenied(@NonNull Fragment host,
    method somePermissionDenied (line 268) | public static boolean somePermissionDenied(@NonNull Activity host,
    method somePermissionDenied (line 276) | public static boolean somePermissionDenied(@NonNull Fragment host,
    method notifyAlreadyHasPermissions (line 289) | private static void notifyAlreadyHasPermissions(@NonNull Object object,
    method runAnnotatedMethods (line 307) | private static void runAnnotatedMethods(@NonNull Object object, int re...
    method isUsingAndroidAnnotations (line 347) | private static boolean isUsingAndroidAnnotations(@NonNull Object objec...

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java
  class PermissionRequest (line 23) | public final class PermissionRequest {
    method PermissionRequest (line 32) | private PermissionRequest(PermissionHelper helper,
    method getHelper (line 48) | @NonNull
    method getPerms (line 54) | @NonNull
    method getRequestCode (line 59) | public int getRequestCode() {
    method getRationale (line 63) | @NonNull
    method getPositiveButtonText (line 68) | @NonNull
    method getNegativeButtonText (line 73) | @NonNull
    method getTheme (line 78) | @StyleRes
    method equals (line 83) | @Override
    method hashCode (line 93) | @Override
    method toString (line 100) | @Override
    class Builder (line 118) | public static final class Builder {
      method Builder (line 136) | public Builder(@NonNull Activity activity, int requestCode,
      method Builder (line 146) | public Builder(@NonNull Fragment fragment, int requestCode,
      method setRationale (line 164) | @NonNull
      method setRationale (line 174) | @NonNull
      method setPositiveButtonText (line 185) | @NonNull
      method setPositiveButtonText (line 194) | @NonNull
      method setNegativeButtonText (line 205) | @NonNull
      method setNegativeButtonText (line 214) | @NonNull
      method setTheme (line 225) | @NonNull
      method build (line 238) | @NonNull

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogClickListener.java
  class RationaleDialogClickListener (line 15) | class RationaleDialogClickListener implements Dialog.OnClickListener {
    method RationaleDialogClickListener (line 22) | RationaleDialogClickListener(RationaleDialogFragmentCompat compatDialo...
    method RationaleDialogClickListener (line 37) | RationaleDialogClickListener(RationaleDialogFragment dialogFragment,
    method onClick (line 49) | @Override
    method notifyPermissionDenied (line 72) | private void notifyPermissionDenied() {

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogConfig.java
  class RationaleDialogConfig (line 13) | class RationaleDialogConfig {
    method RationaleDialogConfig (line 29) | RationaleDialogConfig(@NonNull String positiveButton,
    method RationaleDialogConfig (line 44) | RationaleDialogConfig(Bundle bundle) {
    method toBundle (line 53) | Bundle toBundle() {
    method createSupportDialog (line 65) | AlertDialog createSupportDialog(Context context, Dialog.OnClickListene...
    method createFrameworkDialog (line 80) | android.app.AlertDialog createFrameworkDialog(Context context, Dialog....

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragment.java
  class RationaleDialogFragment (line 17) | @RestrictTo(RestrictTo.Scope.LIBRARY)
    method newInstance (line 26) | public static RationaleDialogFragment newInstance(
    method onAttach (line 45) | @Override
    method onSaveInstanceState (line 67) | @Override
    method showAllowingStateLoss (line 77) | public void showAllowingStateLoss(FragmentManager manager, String tag) {
    method onDetach (line 92) | @Override
    method onCreateDialog (line 98) | @NonNull

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java
  class RationaleDialogFragmentCompat (line 16) | @RestrictTo(RestrictTo.Scope.LIBRARY)
    method newInstance (line 24) | public static RationaleDialogFragmentCompat newInstance(
    method showAllowingStateLoss (line 47) | public void showAllowingStateLoss(FragmentManager manager, String tag) {
    method onAttach (line 55) | @Override
    method onDetach (line 76) | @Override
    method onCreateDialog (line 83) | @NonNull

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java
  class ActivityPermissionHelper (line 17) | class ActivityPermissionHelper extends PermissionHelper<Activity> {
    method ActivityPermissionHelper (line 20) | public ActivityPermissionHelper(Activity host) {
    method directRequestPermissions (line 24) | @Override
    method shouldShowRequestPermissionRationale (line 29) | @Override
    method getContext (line 34) | @Override
    method showRequestPermissionRationale (line 39) | @Override

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/AppCompatActivityPermissionsHelper.java
  class AppCompatActivityPermissionsHelper (line 12) | class AppCompatActivityPermissionsHelper extends BaseSupportPermissionsH...
    method AppCompatActivityPermissionsHelper (line 14) | public AppCompatActivityPermissionsHelper(AppCompatActivity host) {
    method getSupportFragmentManager (line 18) | @Override
    method directRequestPermissions (line 23) | @Override
    method shouldShowRequestPermissionRationale (line 28) | @Override
    method getContext (line 33) | @Override

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/BaseSupportPermissionsHelper.java
  class BaseSupportPermissionsHelper (line 14) | public abstract class BaseSupportPermissionsHelper<T> extends Permission...
    method BaseSupportPermissionsHelper (line 18) | public BaseSupportPermissionsHelper(@NonNull T host) {
    method getSupportFragmentManager (line 22) | public abstract FragmentManager getSupportFragmentManager();
    method showRequestPermissionRationale (line 24) | @Override

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/LowApiPermissionsHelper.java
  class LowApiPermissionsHelper (line 12) | class LowApiPermissionsHelper<T> extends PermissionHelper<T> {
    method LowApiPermissionsHelper (line 13) | public LowApiPermissionsHelper(@NonNull T host) {
    method directRequestPermissions (line 17) | @Override
    method shouldShowRequestPermissionRationale (line 22) | @Override
    method showRequestPermissionRationale (line 27) | @Override
    method getContext (line 37) | @Override

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java
  class PermissionHelper (line 16) | public abstract class PermissionHelper<T> {
    method newInstance (line 20) | @NonNull
    method newInstance (line 33) | @NonNull
    method PermissionHelper (line 46) | public PermissionHelper(@NonNull T host) {
    method shouldShowRationale (line 50) | private boolean shouldShowRationale(@NonNull String... perms) {
    method requestPermissions (line 59) | public void requestPermissions(@NonNull String rationale,
    method somePermissionPermanentlyDenied (line 73) | public boolean somePermissionPermanentlyDenied(@NonNull List<String> p...
    method permissionPermanentlyDenied (line 83) | public boolean permissionPermanentlyDenied(@NonNull String perms) {
    method somePermissionDenied (line 87) | public boolean somePermissionDenied(@NonNull String... perms) {
    method getHost (line 91) | @NonNull
    method directRequestPermissions (line 100) | public abstract void directRequestPermissions(int requestCode, @NonNul...
    method shouldShowRequestPermissionRationale (line 102) | public abstract boolean shouldShowRequestPermissionRationale(@NonNull ...
    method showRequestPermissionRationale (line 104) | public abstract void showRequestPermissionRationale(@NonNull String ra...
    method getContext (line 111) | public abstract Context getContext();

FILE: easypermissions/src/main/java/pub/devrel/easypermissions/helper/SupportFragmentPermissionHelper.java
  class SupportFragmentPermissionHelper (line 11) | class SupportFragmentPermissionHelper extends BaseSupportPermissionsHelp...
    method SupportFragmentPermissionHelper (line 13) | public SupportFragmentPermissionHelper(@NonNull Fragment host) {
    method getSupportFragmentManager (line 17) | @Override
    method directRequestPermissions (line 22) | @Override
    method shouldShowRequestPermissionRationale (line 27) | @Override
    method getContext (line 32) | @Override

FILE: easypermissions/src/test/java/pub/devrel/easypermissions/AppSettingsDialogTest.java
  class AppSettingsDialogTest (line 38) | @RunWith(RobolectricTestRunner.class)
    method setUp (line 60) | @Before
    method shouldShowExpectedSettingsDialog_whenBuildingFromActivity (line 74) | @Test
    method shouldPositiveListener_whenClickingPositiveButtonFromActivity (line 96) | @Test
    method shouldNegativeListener_whenClickingPositiveButtonFromActivity (line 113) | @Test
    method shouldShowExpectedSettingsDialog_whenBuildingFromSupportFragment (line 130) | @Test
    method shouldPositiveListener_whenClickingPositiveButtonFromSupportFragment (line 152) | @Test
    method shouldNegativeListener_whenClickingPositiveButtonFromSupportFragment (line 169) | @Test

FILE: easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsLowApiTest.java
  class EasyPermissionsLowApiTest (line 32) | @RunWith(RobolectricTestRunner.class)
    method setUp (line 53) | @Before
    method shouldHavePermission_whenHasPermissionsBeforeMarshmallow (line 70) | @Test
    method shouldCallbackOnPermissionGranted_whenRequestFromActivity (line 78) | @Test
    method shouldCallbackOnPermissionGranted_whenRequestFromSupportFragmentActivity (line 90) | @Test
    method shouldCallbackOnPermissionGranted_whenRequestFromAppCompatActivity (line 101) | @Test
    method shouldCallbackOnPermissionGranted_whenRequestFromFragment (line 111) | @Test

FILE: easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsTest.java
  class EasyPermissionsTest (line 46) | @RunWith(RobolectricTestRunner.class)
    method setUp (line 74) | @Before
    method shouldNotHavePermissions_whenNoPermissionsGranted (line 93) | @Test
    method shouldNotHavePermissions_whenNotAllPermissionsGranted (line 98) | @Test
    method shouldHavePermissions_whenAllPermissionsGranted (line 104) | @Test
    method shouldThrowException_whenHasPermissionsWithNullContext (line 110) | @SuppressWarnings("ConstantConditions")
    method shouldCorrectlyCallback_whenOnRequestPermissionResultCalledFromActivity (line 124) | @Test
    method shouldCallbackOnPermissionGranted_whenRequestAlreadyGrantedPermissionsFromActivity (line 143) | @Test
    method shouldCallbackAfterPermissionGranted_whenRequestAlreadyGrantedPermissionsFromActivity (line 156) | @Test
    method shouldNotCallbackAfterPermissionGranted_whenRequestNotGrantedPermissionsFromActivity (line 166) | @Test
    method shouldRequestPermissions_whenMissingPermissionAndNotShowRationaleFromActivity (line 175) | @Test
    method shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromActivity (line 186) | @Test
    method shouldShowCorrectDialogUsingRequest_whenMissingPermissionsAndShowRationaleFromActivity (line 201) | @Test
    method shouldHaveSomePermissionDenied_whenShowRationaleFromActivity (line 223) | @Test
    method shouldNotHaveSomePermissionDenied_whenNotShowRationaleFromActivity (line 230) | @Test
    method shouldHaveSomePermissionPermanentlyDenied_whenNotShowRationaleFromActivity (line 237) | @Test
    method shouldNotHaveSomePermissionPermanentlyDenied_whenShowRationaleFromActivity (line 244) | @Test
    method shouldHavePermissionPermanentlyDenied_whenNotShowRationaleFromActivity (line 251) | @Test
    method shouldNotHavePermissionPermanentlyDenied_whenShowRationaleFromActivity (line 258) | @Test
    method shouldCorrectlyCallback_whenOnRequestPermissionResultCalledFromAppCompatActivity (line 265) | @Test
    method shouldCallbackOnPermissionGranted_whenRequestAlreadyGrantedPermissionsFromAppCompatActivity (line 284) | @Test
    method shouldCallbackAfterPermissionGranted_whenRequestAlreadyGrantedPermissionsFromAppCompatActivity (line 297) | @Test
    method shouldNotCallbackAfterPermissionGranted_whenRequestNotGrantedPermissionsFromAppCompatActivity (line 307) | @Test
    method shouldRequestPermissions_whenMissingPermissionAndNotShowRationaleFromAppCompatActivity (line 316) | @Test
    method shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromAppCompatActivity (line 327) | @Test
    method shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromSupportFragmentActivity (line 342) | @Test
    method shouldShowCorrectDialogUsingRequest_whenMissingPermissionsAndShowRationaleFromAppCompatActivity (line 357) | @Test
    method shouldHaveSomePermissionDenied_whenShowRationaleFromAppCompatActivity (line 379) | @Test
    method shouldNotHaveSomePermissionDenied_whenNotShowRationaleFromAppCompatActivity (line 386) | @Test
    method shouldHaveSomePermissionPermanentlyDenied_whenNotShowRationaleFromAppCompatActivity (line 393) | @Test
    method shouldNotHaveSomePermissionPermanentlyDenied_whenShowRationaleFromAppCompatActivity (line 400) | @Test
    method shouldHavePermissionPermanentlyDenied_whenNotShowRationaleFromAppCompatActivity (line 407) | @Test
    method shouldNotHavePermissionPermanentlyDenied_whenShowRationaleFromAppCompatActivity (line 414) | @Test
    method shouldCorrectlyCallback_whenOnRequestPermissionResultCalledFromFragment (line 421) | @Test
    method shouldCallbackOnPermissionGranted_whenRequestAlreadyGrantedPermissionsFromFragment (line 441) | @Test
    method shouldCallbackAfterPermissionGranted_whenRequestAlreadyGrantedPermissionsFragment (line 455) | @Test
    method shouldNotCallbackAfterPermissionGranted_whenRequestNotGrantedPermissionsFromFragment (line 465) | @Test
    method shouldRequestPermissions_whenMissingPermissionsAndNotShowRationaleFromFragment (line 474) | @Test
    method shouldShowCorrectDialog_whenMissingPermissionsAndShowRationaleFromFragment (line 485) | @Test
    method shouldShowCorrectDialogUsingRequest_whenMissingPermissionsAndShowRationaleFromFragment (line 500) | @Test
    method shouldHaveSomePermissionDenied_whenShowRationaleFromFragment (line 521) | @Test
    method shouldNotHaveSomePermissionDenied_whenNotShowRationaleFromFragment (line 528) | @Test
    method shouldHaveSomePermissionPermanentlyDenied_whenNotShowRationaleFromFragment (line 535) | @Test
    method shouldNotHaveSomePermissionPermanentlyDenied_whenShowRationaleFromFragment (line 542) | @Test
    method shouldHavePermissionPermanentlyDenied_whenNotShowRationaleFromFragment (line 550) | @Test
    method shouldNotHavePermissionPermanentlyDenied_whenShowRationaleFromFragment (line 557) | @Test
    method assertThatHasExpectedButtonsAndRationale (line 564) | private void assertThatHasExpectedButtonsAndRationale(Dialog dialog, i...
    method assertThatHasExpectedButtonsAndRationale (line 574) | private void assertThatHasExpectedButtonsAndRationale(Dialog dialog, S...
    method assertThatHasExpectedButtonsAndRationale (line 584) | private void assertThatHasExpectedButtonsAndRationale(Dialog dialog, S...
    method assertThatHasExpectedRationale (line 594) | private void assertThatHasExpectedRationale(Dialog dialog, String rati...
    method grantPermissions (line 599) | private void grantPermissions(String[] perms) {
    method showRationale (line 603) | private void showRationale(boolean show, String... perms) {

FILE: easypermissions/src/test/java/pub/devrel/easypermissions/RationaleDialogClickListenerTest.java
  class RationaleDialogClickListenerTest (line 27) | @RunWith(RobolectricTestRunner.class)
    method setUp (line 51) | @Before
    method shouldOnRationaleAccepted_whenPositiveButtonWithRationaleCallbacks (line 60) | @Test
    method shouldNotOnRationaleAccepted_whenPositiveButtonWithoutRationaleCallbacks (line 69) | @Test
    method shouldRequestPermissions_whenPositiveButtonFromActivity (line 78) | @Test
    method shouldRequestPermissions_whenPositiveButtonFromFragment (line 87) | @Test
    method shouldOnRationaleDenied_whenNegativeButtonWithRationaleCallbacks (line 98) | @Test
    method shouldNotOnRationaleDenied_whenNegativeButtonWithoutRationaleCallbacks (line 107) | @Test
    method shouldOnPermissionsDenied_whenNegativeButtonWithPermissionCallbacks (line 116) | @Test
    method shouldNotOnPermissionsDenied_whenNegativeButtonWithoutPermissionCallbacks (line 126) | @Test

FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/ActivityController.java
  class ActivityController (line 14) | public class ActivityController<T extends Activity> {
    method ActivityController (line 18) | public ActivityController(Class<T> clazz) {
    method resume (line 22) | public synchronized T resume() {
    method reset (line 41) | public void reset() {

FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/FragmentController.java
  class FragmentController (line 13) | public class FragmentController<T extends Fragment> {
    method FragmentController (line 17) | public FragmentController(Class<T> clazz) {
    method resume (line 21) | public synchronized T resume() {
    method reset (line 40) | public void reset() {

FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestActivity.java
  class TestActivity (line 11) | public class TestActivity extends Activity
    method onPermissionsGranted (line 16) | @Override
    method onPermissionsDenied (line 21) | @Override
    method afterPermissionGranted (line 26) | @AfterPermissionGranted(REQUEST_CODE)
    method onRationaleAccepted (line 31) | @Override
    method onRationaleDenied (line 36) | @Override

FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestAppCompatActivity.java
  class TestAppCompatActivity (line 14) | public class TestAppCompatActivity extends AppCompatActivity
    method onCreate (line 19) | @Override
    method onPermissionsGranted (line 25) | @Override
    method onPermissionsDenied (line 30) | @Override
    method afterPermissionGranted (line 35) | @AfterPermissionGranted(REQUEST_CODE)
    method onRationaleAccepted (line 40) | @Override
    method onRationaleDenied (line 45) | @Override

FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestFragment.java
  class TestFragment (line 17) | public class TestFragment extends Fragment
    method onCreateView (line 22) | @Nullable
    method onPermissionsGranted (line 31) | @Override
    method onPermissionsDenied (line 36) | @Override
    method afterPermissionGranted (line 41) | @AfterPermissionGranted(REQUEST_CODE)
    method onRationaleAccepted (line 46) | @Override
    method onRationaleDenied (line 51) | @Override

FILE: easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestSupportFragmentActivity.java
  class TestSupportFragmentActivity (line 13) | public class TestSupportFragmentActivity extends FragmentActivity
    method onCreate (line 18) | @Override
    method onPermissionsGranted (line 23) | @Override
    method onPermissionsDenied (line 28) | @Override
    method afterPermissionGranted (line 33) | @AfterPermissionGranted(REQUEST_CODE)
    method onRationaleAccepted (line 38) | @Override
    method onRationaleDenied (line 43) | @Override
Condensed preview — 60 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (178K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 291,
    "preview": "## Basic Information\n\nDevice type: ________\nOS version: ________\nEasyPermissions version: ________\n\n## Describe the prob"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 332,
    "preview": "name: test\n\non:\n  - pull_request\n  - push\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/ch"
  },
  {
    "path": ".gitignore",
    "chars": 229,
    "preview": "# Gradle files\n.gradle\nbuild\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# IntelliJ project files\n**.i"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 10,
    "preview": "Not Found\n"
  },
  {
    "path": "LICENSE",
    "chars": 10252,
    "preview": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AN"
  },
  {
    "path": "PUBLISHING.md",
    "chars": 1611,
    "preview": "## Publishing\n\n### Credentials\n\nThe library is published to Maven Central by the firebase-sonatype account, Googlers can"
  },
  {
    "path": "README.md",
    "chars": 8098,
    "preview": "# EasyPermissions [![Build Status][1]][2] [![Android Weekly][3]][4]\n\nEasyPermissions is a wrapper library to simplify ba"
  },
  {
    "path": "app/build.gradle",
    "chars": 958,
    "preview": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 30\n    testOptions.unitTests.includeAndroidReso"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 480,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 1158,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "app/src/main/java/pub/devrel/easypermissions/sample/MainActivity.java",
    "chars": 6059,
    "preview": "/*\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n "
  },
  {
    "path": "app/src/main/java/pub/devrel/easypermissions/sample/MainFragment.java",
    "chars": 2546,
    "preview": "package pub.devrel.easypermissions.sample;\n\nimport android.Manifest;\nimport android.os.Bundle;\nimport android.util.Log;\n"
  },
  {
    "path": "app/src/main/res/layout/activity_basic.xml",
    "chars": 549,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    "
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 1192,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    "
  },
  {
    "path": "app/src/main/res/layout/fragment_main.xml",
    "chars": 390,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    a"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 208,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"color"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "chars": 211,
    "preview": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 891,
    "preview": "<resources>\n    <string name=\"app_name\">Easy Permissions</string>\n    <string name=\"yes\">Yes</string>\n    <string name=\""
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 383,
    "preview": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "chars": 358,
    "preview": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as s"
  },
  {
    "path": "build.gradle",
    "chars": 391,
    "preview": "buildscript {\n    repositories {\n        jcenter()\n        google()\n        mavenCentral()\n    }\n\n    dependencies {\n   "
  },
  {
    "path": "easypermissions/build.gradle",
    "chars": 1399,
    "preview": "apply plugin: 'com.android.library'\n\n// See: https://github.com/vanniktech/gradle-maven-publish-plugin/issues/206\next {\n"
  },
  {
    "path": "easypermissions/gradle.properties",
    "chars": 554,
    "preview": "GROUP=pub.devrel\nPOM_ARTIFACT_ID=easypermissions\nVERSION_NAME=3.0.0\n\nPOM_NAME=EasyPermissions\nPOM_PACKAGING=aar\n\nPOM_DES"
  },
  {
    "path": "easypermissions/proguard-rules.pro",
    "chars": 96,
    "preview": "-keepclassmembers class * {\n    @pub.devrel.easypermissions.AfterPermissionGranted <methods>;\n}\n"
  },
  {
    "path": "easypermissions/src/main/AndroidManifest.xml",
    "chars": 440,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    pack"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/AfterPermissionGranted.java",
    "chars": 938,
    "preview": "/*\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n "
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialog.java",
    "chars": 12490,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/AppSettingsDialogHolderActivity.java",
    "chars": 2332,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.app.Activity;\nimport android.app.Dialog;\nimport android.content.Cont"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/EasyPermissions.java",
    "chars": 15038,
    "preview": "/*\n * Copyright Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n "
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/PermissionRequest.java",
    "chars": 8358,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.app.Activity;\nimport androidx.annotation.NonNull;\nimport androidx.an"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogClickListener.java",
    "chars": 2916,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.app.Activity;\nimport android.app.Dialog;\nimport android.content.Dial"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogConfig.java",
    "chars": 3495,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.os.Bundle"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragment.java",
    "chars": 3847,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.app.Dialog;\nimport android.app.DialogFragment;\nimport android.app.Fr"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/RationaleDialogFragmentCompat.java",
    "chars": 3489,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.os.Bundle"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/helper/ActivityPermissionHelper.java",
    "chars": 2100,
    "preview": "package pub.devrel.easypermissions.helper;\n\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.app"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/helper/AppCompatActivityPermissionsHelper.java",
    "chars": 1091,
    "preview": "package pub.devrel.easypermissions.helper;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport a"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/helper/BaseSupportPermissionsHelper.java",
    "chars": 1723,
    "preview": "package pub.devrel.easypermissions.helper;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StyleRes;\nimp"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/helper/LowApiPermissionsHelper.java",
    "chars": 1698,
    "preview": "package pub.devrel.easypermissions.helper;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport androidx"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/helper/PermissionHelper.java",
    "chars": 3890,
    "preview": "package pub.devrel.easypermissions.helper;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android."
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/helper/SupportFragmentPermissionHelper.java",
    "chars": 1021,
    "preview": "package pub.devrel.easypermissions.helper;\n\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport a"
  },
  {
    "path": "easypermissions/src/main/java/pub/devrel/easypermissions/helper/package-info.java",
    "chars": 127,
    "preview": "@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)\npackage pub.devrel.easypermissions.helper;\n\nimport androidx.annotation.Restr"
  },
  {
    "path": "easypermissions/src/main/res/values/colors.xml",
    "chars": 287,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- Use system defaults for devs not using AppCompat -->\n    <co"
  },
  {
    "path": "easypermissions/src/main/res/values/strings.xml",
    "chars": 398,
    "preview": "<resources>\n    <string name=\"rationale_ask\">This app may not work correctly without the requested permissions.</string>"
  },
  {
    "path": "easypermissions/src/main/res/values/styles.xml",
    "chars": 792,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"EasyPermissions\" parent=\"Theme.AppCompat.DayNight.D"
  },
  {
    "path": "easypermissions/src/test/java/pub/devrel/easypermissions/AppSettingsDialogTest.java",
    "chars": 7710,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.app.Application;\nimport android.content.DialogInterface;\nimport andr"
  },
  {
    "path": "easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsLowApiTest.java",
    "chars": 5158,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.Manifest;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport or"
  },
  {
    "path": "easypermissions/src/test/java/pub/devrel/easypermissions/EasyPermissionsTest.java",
    "chars": 27482,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.Manifest;\nimport android.app.Application;\nimport android.app.Dialog;"
  },
  {
    "path": "easypermissions/src/test/java/pub/devrel/easypermissions/RationaleDialogClickListenerTest.java",
    "chars": 5193,
    "preview": "package pub.devrel.easypermissions;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.app.Dialog;\nim"
  },
  {
    "path": "easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/ActivityController.java",
    "chars": 1210,
    "preview": "package pub.devrel.easypermissions.testhelper;\n\nimport android.app.Activity;\n\nimport androidx.annotation.NonNull;\nimport"
  },
  {
    "path": "easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/FragmentController.java",
    "chars": 1227,
    "preview": "package pub.devrel.easypermissions.testhelper;\n\nimport androidx.annotation.NonNull;\nimport androidx.fragment.app.Fragmen"
  },
  {
    "path": "easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestActivity.java",
    "chars": 891,
    "preview": "package pub.devrel.easypermissions.testhelper;\n\nimport android.app.Activity;\n\nimport java.util.List;\n\nimport androidx.an"
  },
  {
    "path": "easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestAppCompatActivity.java",
    "chars": 1224,
    "preview": "package pub.devrel.easypermissions.testhelper;\n\nimport android.os.Bundle;\n\nimport java.util.List;\n\nimport androidx.annot"
  },
  {
    "path": "easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestFragment.java",
    "chars": 1471,
    "preview": "package pub.devrel.easypermissions.testhelper;\n\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport and"
  },
  {
    "path": "easypermissions/src/test/java/pub/devrel/easypermissions/testhelper/TestSupportFragmentActivity.java",
    "chars": 1125,
    "preview": "package pub.devrel.easypermissions.testhelper;\n\nimport android.os.Bundle;\n\nimport java.util.List;\n\nimport androidx.annot"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 202,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
  },
  {
    "path": "gradle.properties",
    "chars": 959,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "gradlew",
    "chars": 5766,
    "preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "gradlew.bat",
    "chars": 2763,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "settings.gradle",
    "chars": 35,
    "preview": "include ':app', ':easypermissions'\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the googlesamples/easypermissions GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 60 files (163.6 KB), approximately 36.1k tokens, and a symbol index with 276 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!