├── .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 │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.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.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Basic Information 2 | 3 | Device type: ________ 4 | OS version: ________ 5 | EasyPermissions version: ________ 6 | 7 | ## Describe the problem 8 | 9 | What happened? What did you expect to happen? 10 | 11 | ## Code and logs 12 | 13 | ``` 14 | // TODO(you): show the code that produces the problem, 15 | // and any relevant logs. 16 | ``` 17 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | - pull_request 5 | - push 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Gradle 17 | run: | 18 | ./gradlew build :easypermissions:test 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle 3 | build 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # IntelliJ project files 9 | **.iml 10 | .idea 11 | 12 | # Android Studio captures folder 13 | captures/ 14 | 15 | # Misc 16 | .DS_Store 17 | .classpath 18 | .project 19 | .settings 20 | .vscode 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Not Found 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright 2017 Google 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /PUBLISHING.md: -------------------------------------------------------------------------------- 1 | ## Publishing 2 | 3 | ### Credentials 4 | 5 | The library is published to Maven Central by the firebase-sonatype account, Googlers can find the 6 | password for this account in [Valentine](http://valentine/) 7 | 8 | ### GPG Key 9 | 10 | You will need to create a private GPG keyring on your machine, if you don't have one do the 11 | following steps: 12 | 13 | 1. Run `gpg --full-generate-key` 14 | 1. Choose `RSA and RSA` for the key type 15 | 1. Use `4096` for the key size 16 | 1. Use `0` for the expiration (never) 17 | 1. Use any name, email address, and password 18 | 19 | This creates your key in `~/.gnupg/openpgp-revocs.d/` with `.rev` format. The last 8 characters 20 | before the `.rev` extension are your **Key ID**. 21 | 22 | To export the key, run: 23 | 24 | ``` 25 | gpg --export-secret-keys -o $HOME/sonatype.gpg 26 | ``` 27 | 28 | Finally upload your key to the keyserver: 29 | 30 | ``` 31 | gpg --keyserver hkp://keys.openpgp.org --send-keys 32 | ``` 33 | 34 | ### Local Properties 35 | 36 | Open your `$HOME/.gradle/gradle.properties` file at and fill in the values: 37 | 38 | ``` 39 | signing.keyId= 40 | signing.password= 41 | signing.secretKeyRingFile= 42 | mavenCentralRepositoryUsername=firebase-sonatype 43 | mavenCentralRepositoryUsername= 44 | ``` 45 | 46 | ### Publish 47 | 48 | To publish, run: 49 | 50 | ``` 51 | ./gradlew publish 52 | ``` 53 | 54 | ### Release 55 | 56 | Follow [the instructions here](https://central.sonatype.org/pages/releasing-the-deployment.html): 57 | 58 | 1. Navigate to https://s01.oss.sonatype.org/ and **Log In** 59 | 1. On the left side click **Build Promotion** and look for the `com.firebase` repo 60 | 1. Click **Close** ... wait a few minutes (you can check status with **Refresh**) 61 | 1. Click **Release** 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyPermissions [![Build Status][1]][2] [![Android Weekly][3]][4] 2 | 3 | EasyPermissions is a wrapper library to simplify basic system permissions logic when targeting 4 | Android M or higher. 5 | 6 | **Note:** If your app is written in Kotlin consider the [easypermissions-ktx](https://github.com/VMadalin/easypermissions-ktx) 7 | library which adds Kotlin extensions to the core EasyPermissions library. 8 | 9 | ## Installation 10 | 11 | EasyPermissions is installed by adding the following dependency to your `build.gradle` file: 12 | 13 | ```groovy 14 | dependencies { 15 | // For developers using AndroidX in their applications 16 |    implementation 'pub.devrel:easypermissions:3.0.0' 17 | 18 | // For developers using the Android Support Library 19 | implementation 'pub.devrel:easypermissions:2.0.1' 20 | } 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### Basic 26 | 27 | To begin using EasyPermissions, have your `Activity` (or `Fragment`) override the `onRequestPermissionsResult` method: 28 | 29 | ```java 30 | public class MainActivity extends AppCompatActivity { 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_main); 35 | } 36 | 37 | @Override 38 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 39 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 40 | 41 | // Forward results to EasyPermissions 42 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); 43 | } 44 | } 45 | ``` 46 | 47 | ### Request Permissions 48 | 49 | The example below shows how to request permissions for a method that requires both 50 | `CAMERA` and `ACCESS_FINE_LOCATION` permissions. There are a few things to note: 51 | 52 | * Using `EasyPermissions#hasPermissions(...)` to check if the app already has the 53 | required permissions. This method can take any number of permissions as its final 54 | argument. 55 | * Requesting permissions with `EasyPermissions#requestPermissions`. This method 56 | will request the system permissions and show the rationale string provided if 57 | necessary. The request code provided should be unique to this request, and the method 58 | can take any number of permissions as its final argument. 59 | * Use of the `AfterPermissionGranted` annotation. This is optional, but provided for 60 | convenience. If all of the permissions in a given request are granted, *all* methods 61 | 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 62 | flow of needing to run the requesting method after all of its permissions have been granted. 63 | This can also be achieved by adding logic on the `onPermissionsGranted` callback. 64 | 65 | ```java 66 | @AfterPermissionGranted(RC_CAMERA_AND_LOCATION) 67 | private void methodRequiresTwoPermission() { 68 | String[] perms = {Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION}; 69 | if (EasyPermissions.hasPermissions(this, perms)) { 70 | // Already have permission, do the thing 71 | // ... 72 | } else { 73 | // Do not have permissions, request them now 74 | EasyPermissions.requestPermissions(this, getString(R.string.camera_and_location_rationale), 75 | RC_CAMERA_AND_LOCATION, perms); 76 | } 77 | } 78 | ``` 79 | 80 | Or for finer control over the rationale dialog, use a `PermissionRequest`: 81 | 82 | ```java 83 | EasyPermissions.requestPermissions( 84 | new PermissionRequest.Builder(this, RC_CAMERA_AND_LOCATION, perms) 85 | .setRationale(R.string.camera_and_location_rationale) 86 | .setPositiveButtonText(R.string.rationale_ask_ok) 87 | .setNegativeButtonText(R.string.rationale_ask_cancel) 88 | .setTheme(R.style.my_fancy_style) 89 | .build()); 90 | ``` 91 | 92 | Optionally, for a finer control, you can have your `Activity` / `Fragment` implement 93 | the `PermissionCallbacks` interface. 94 | 95 | ```java 96 | public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks { 97 | 98 | @Override 99 | protected void onCreate(Bundle savedInstanceState) { 100 | super.onCreate(savedInstanceState); 101 | setContentView(R.layout.activity_main); 102 | } 103 | 104 | @Override 105 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 106 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 107 | 108 | // Forward results to EasyPermissions 109 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); 110 | } 111 | 112 | @Override 113 | public void onPermissionsGranted(int requestCode, List list) { 114 | // Some permissions have been granted 115 | // ... 116 | } 117 | 118 | @Override 119 | public void onPermissionsDenied(int requestCode, List list) { 120 | // Some permissions have been denied 121 | // ... 122 | } 123 | } 124 | ``` 125 | 126 | ### Required Permissions 127 | 128 | In some cases your app will not function properly without certain permissions. If the user 129 | denies these permissions with the "Never Ask Again" option, you will be unable to request 130 | these permissions from the user and they must be changed in app settings. You can use the 131 | method `EasyPermissions.somePermissionPermanentlyDenied(...)` to display a dialog to the 132 | user in this situation and direct them to the system setting screen for your app: 133 | 134 | **Note**: Due to a limitation in the information provided by the Android 135 | framework permissions API, the `somePermissionPermanentlyDenied` method only 136 | works after the permission has been denied and your app has received 137 | the `onPermissionsDenied` callback. Otherwise the library cannot distinguish 138 | permanent denial from the "not yet denied" case. 139 | 140 | ```java 141 | @Override 142 | public void onPermissionsDenied(int requestCode, List perms) { 143 | Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size()); 144 | 145 | // (Optional) Check whether the user denied any permissions and checked "NEVER ASK AGAIN." 146 | // This will display a dialog directing them to enable the permission in app settings. 147 | if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { 148 | new AppSettingsDialog.Builder(this).build().show(); 149 | } 150 | } 151 | 152 | @Override 153 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 154 | super.onActivityResult(requestCode, resultCode, data); 155 | 156 | if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) { 157 | // Do something after user returned from app settings screen, like showing a Toast. 158 | Toast.makeText(this, R.string.returned_from_app_settings_to_activity, Toast.LENGTH_SHORT) 159 | .show(); 160 | } 161 | } 162 | ``` 163 | 164 | ### Interacting with the rationale dialog 165 | 166 | Implement the `EasyPermissions.RationaleCallbacks` if you want to interact with the rationale dialog. 167 | 168 | ```java 169 | @Override 170 | public void onRationaleAccepted(int requestCode) { 171 | // Rationale accepted to request some permissions 172 | // ... 173 | } 174 | 175 | @Override 176 | public void onRationaleDenied(int requestCode) { 177 | // Rationale denied to request some permissions 178 | // ... 179 | } 180 | ``` 181 | 182 | Rationale callbacks don't necessarily imply permission changes. To check for those, see the `EasyPermissions.PermissionCallbacks`. 183 | 184 | ## LICENSE 185 | 186 | ``` 187 | Copyright 2017 Google 188 | 189 | Licensed under the Apache License, Version 2.0 (the "License"); 190 | you may not use this file except in compliance with the License. 191 | You may obtain a copy of the License at 192 | 193 | http://www.apache.org/licenses/LICENSE-2.0 194 | 195 | Unless required by applicable law or agreed to in writing, software 196 | distributed under the License is distributed on an "AS IS" BASIS, 197 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 198 | See the License for the specific language governing permissions and 199 | limitations under the License. 200 | 201 | ``` 202 | 203 | [1]: https://github.com/googlesamples/easypermissions/workflows/test/badge.svg 204 | [2]: https://github.com/googlesamples/easypermissions/actions 205 | [3]: https://img.shields.io/badge/Android%20Weekly-%23185-2CB3E5.svg?style=flat 206 | [4]: http://androidweekly.net/issues/issue-185 207 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 30 5 | testOptions.unitTests.includeAndroidResources = true 6 | 7 | defaultConfig { 8 | applicationId "pub.devrel.easypermissions.sample" 9 | minSdkVersion 14 10 | targetSdkVersion 30 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled true 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | 22 | compileOptions { 23 | // Flag to enable support for the new language APIs 24 | coreLibraryDesugaringEnabled false 25 | // Sets Java compatibility to Java 8 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation 'androidx.appcompat:appcompat:1.1.0' 33 | implementation "androidx.annotation:annotation:1.1.0" 34 | implementation project(':easypermissions') 35 | } 36 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/pub/devrel/easypermissions/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package pub.devrel.easypermissions.sample; 17 | 18 | import android.Manifest; 19 | import android.content.Intent; 20 | import android.os.Bundle; 21 | import android.util.Log; 22 | import android.widget.Toast; 23 | 24 | import androidx.annotation.NonNull; 25 | import androidx.appcompat.app.AppCompatActivity; 26 | 27 | import java.util.List; 28 | 29 | import pub.devrel.easypermissions.AfterPermissionGranted; 30 | import pub.devrel.easypermissions.AppSettingsDialog; 31 | import pub.devrel.easypermissions.EasyPermissions; 32 | 33 | public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks, 34 | EasyPermissions.RationaleCallbacks{ 35 | 36 | private static final String TAG = "MainActivity"; 37 | private static final String[] LOCATION_AND_CONTACTS = 38 | {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CONTACTS}; 39 | 40 | private static final int RC_CAMERA_PERM = 123; 41 | private static final int RC_LOCATION_CONTACTS_PERM = 124; 42 | 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.activity_main); 47 | 48 | // Button click listener that will request one permission. 49 | findViewById(R.id.button_camera).setOnClickListener(v -> cameraTask()); 50 | 51 | // Button click listener that will request two permissions. 52 | findViewById(R.id.button_location_and_contacts).setOnClickListener(v -> locationAndContactsTask()); 53 | } 54 | 55 | private boolean hasCameraPermission() { 56 | return EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA); 57 | } 58 | 59 | private boolean hasLocationAndContactsPermissions() { 60 | return EasyPermissions.hasPermissions(this, LOCATION_AND_CONTACTS); 61 | } 62 | 63 | private boolean hasSmsPermission() { 64 | return EasyPermissions.hasPermissions(this, Manifest.permission.READ_SMS); 65 | } 66 | 67 | private boolean hasStoragePermission() { 68 | return EasyPermissions.hasPermissions(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); 69 | } 70 | 71 | @AfterPermissionGranted(RC_CAMERA_PERM) 72 | public void cameraTask() { 73 | if (hasCameraPermission()) { 74 | // Have permission, do the thing! 75 | Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show(); 76 | } else { 77 | // Ask for one permission 78 | EasyPermissions.requestPermissions( 79 | this, 80 | getString(R.string.rationale_camera), 81 | RC_CAMERA_PERM, 82 | Manifest.permission.CAMERA); 83 | } 84 | } 85 | 86 | @AfterPermissionGranted(RC_LOCATION_CONTACTS_PERM) 87 | public void locationAndContactsTask() { 88 | if (hasLocationAndContactsPermissions()) { 89 | // Have permissions, do the thing! 90 | Toast.makeText(this, "TODO: Location and Contacts things", Toast.LENGTH_LONG).show(); 91 | } else { 92 | // Ask for both permissions 93 | EasyPermissions.requestPermissions( 94 | this, 95 | getString(R.string.rationale_location_contacts), 96 | RC_LOCATION_CONTACTS_PERM, 97 | LOCATION_AND_CONTACTS); 98 | } 99 | } 100 | 101 | @Override 102 | public void onRequestPermissionsResult(int requestCode, 103 | @NonNull String[] permissions, 104 | @NonNull int[] grantResults) { 105 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 106 | 107 | // EasyPermissions handles the request result. 108 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); 109 | } 110 | 111 | @Override 112 | public void onPermissionsGranted(int requestCode, @NonNull List perms) { 113 | Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size()); 114 | } 115 | 116 | @Override 117 | public void onPermissionsDenied(int requestCode, @NonNull List perms) { 118 | Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size()); 119 | 120 | // (Optional) Check whether the user denied any permissions and checked "NEVER ASK AGAIN." 121 | // This will display a dialog directing them to enable the permission in app settings. 122 | if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { 123 | new AppSettingsDialog.Builder(this).build().show(); 124 | } 125 | } 126 | 127 | @Override 128 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 129 | super.onActivityResult(requestCode, resultCode, data); 130 | 131 | if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) { 132 | String yes = getString(R.string.yes); 133 | String no = getString(R.string.no); 134 | 135 | // Do something after user returned from app settings screen, like showing a Toast. 136 | Toast.makeText( 137 | this, 138 | getString(R.string.returned_from_app_settings_to_activity, 139 | hasCameraPermission() ? yes : no, 140 | hasLocationAndContactsPermissions() ? yes : no, 141 | hasSmsPermission() ? yes : no), 142 | Toast.LENGTH_LONG) 143 | .show(); 144 | } 145 | } 146 | 147 | @Override 148 | public void onRationaleAccepted(int requestCode) { 149 | Log.d(TAG, "onRationaleAccepted:" + requestCode); 150 | } 151 | 152 | @Override 153 | public void onRationaleDenied(int requestCode) { 154 | Log.d(TAG, "onRationaleDenied:" + requestCode); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /app/src/main/java/pub/devrel/easypermissions/sample/MainFragment.java: -------------------------------------------------------------------------------- 1 | package pub.devrel.easypermissions.sample; 2 | 3 | import android.Manifest; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Toast; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.fragment.app.Fragment; 13 | 14 | import java.util.List; 15 | 16 | import pub.devrel.easypermissions.AfterPermissionGranted; 17 | import pub.devrel.easypermissions.EasyPermissions; 18 | 19 | /** 20 | * Created in {@link R.layout#activity_main} 21 | */ 22 | public class MainFragment extends Fragment implements EasyPermissions.PermissionCallbacks { 23 | 24 | private static final String TAG = "MainFragment"; 25 | private static final int RC_SMS_PERM = 122; 26 | 27 | @Override 28 | public View onCreateView(@NonNull LayoutInflater inflater, 29 | ViewGroup container, 30 | Bundle savedInstanceState) { 31 | super.onCreateView(inflater, container, savedInstanceState); 32 | 33 | // Create view 34 | View v = inflater.inflate(R.layout.fragment_main, container); 35 | 36 | // Button click listener 37 | v.findViewById(R.id.button_sms).setOnClickListener(v1 -> smsTask()); 38 | 39 | return v; 40 | } 41 | 42 | @Override 43 | public void onRequestPermissionsResult(int requestCode, 44 | @NonNull String[] permissions, 45 | @NonNull int[] grantResults) { 46 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 47 | 48 | // EasyPermissions handles the request result. 49 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); 50 | } 51 | 52 | @AfterPermissionGranted(RC_SMS_PERM) 53 | private void smsTask() { 54 | if (EasyPermissions.hasPermissions(requireContext(), Manifest.permission.READ_SMS)) { 55 | // Have permission, do the thing! 56 | Toast.makeText(getActivity(), "TODO: SMS things", Toast.LENGTH_LONG).show(); 57 | } else { 58 | // Request one permission 59 | EasyPermissions.requestPermissions(this, getString(R.string.rationale_sms), 60 | RC_SMS_PERM, Manifest.permission.READ_SMS); 61 | } 62 | } 63 | 64 | @Override 65 | public void onPermissionsGranted(int requestCode, @NonNull List perms) { 66 | Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size()); 67 | } 68 | 69 | @Override 70 | public void onPermissionsDenied(int requestCode, @NonNull List perms) { 71 | Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_basic.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |