Repository: permissions-dispatcher/PermissionsDispatcher
Branch: master
Commit: 74b532bf1fe4
Files: 185
Total size: 745.7 KB
Directory structure:
gitextract_y6ni10jf/
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ └── workflows/
│ ├── deploy_snapshot.yml
│ ├── ktx-release.yml
│ ├── pull_request_ci.yml
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── annotation/
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ └── main/
│ └── java/
│ └── permissions/
│ └── dispatcher/
│ ├── GrantableRequest.java
│ ├── NeedsPermission.java
│ ├── OnNeverAskAgain.java
│ ├── OnPermissionDenied.java
│ ├── OnShowRationale.java
│ ├── PermissionRequest.java
│ └── RuntimePermissions.java
├── build.gradle
├── buildSrc/
│ ├── build.gradle
│ └── src/
│ └── main/
│ └── groovy/
│ └── permissions/
│ └── dispatcher/
│ ├── AarToJarDependencyPlugin.groovy
│ ├── AndroidJarDependencyExtension.groovy
│ └── AndroidJarDependencyPlugin.groovy
├── doc/
│ ├── java_usage.md
│ ├── maxsdkversion.md
│ ├── migration_guide.md
│ └── special_permissions.md
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── ktx/
│ ├── README.md
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── permissions/
│ │ └── dispatcher/
│ │ └── ktx/
│ │ ├── ActivityExtensions.kt
│ │ ├── Event.kt
│ │ ├── FragmentExtensions.kt
│ │ ├── KtxPermissionRequest.kt
│ │ ├── LocationPermission.kt
│ │ ├── PermissionRequestFragment.kt
│ │ ├── PermissionRequestResult.kt
│ │ ├── PermissionRequestType.kt
│ │ ├── PermissionRequestViewModel.kt
│ │ ├── PermissionsRequester.kt
│ │ ├── PermissionsRequesterImpl.kt
│ │ └── TypeAliases.kt
│ └── test/
│ ├── java/
│ │ └── permissions/
│ │ └── dispatcher/
│ │ └── test/
│ │ ├── EventTest.kt
│ │ ├── KtxPermissionRequestTest.kt
│ │ ├── LocationPermissionTest.kt
│ │ └── PermissionRequestViewModelTest.kt
│ └── resources/
│ └── mockito-extensions/
│ └── org.mockito.plugins.MockMaker
├── ktx-sample/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── permissions/
│ │ └── dispatcher/
│ │ └── ktx/
│ │ └── sample/
│ │ ├── MainActivity.kt
│ │ └── MainFragment.kt
│ └── res/
│ ├── layout/
│ │ ├── activity_main.xml
│ │ ├── fragment_camera.xml
│ │ └── fragment_main.xml
│ └── values/
│ ├── dimens.xml
│ ├── strings.xml
│ └── template-styles.xml
├── library/
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── permissions/
│ │ └── dispatcher/
│ │ └── PermissionUtils.java
│ └── test/
│ └── java/
│ └── permissions/
│ └── dispatcher/
│ └── ApiLevelTestSuite.java
├── lint/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── permissions/
│ │ └── dispatcher/
│ │ ├── CallNeedsPermissionDetector.java
│ │ ├── CallOnRequestPermissionsResultDetector.java
│ │ ├── NoCorrespondingNeedsPermissionDetector.java
│ │ ├── NoDelegateOnResumeDetector.java
│ │ └── PermissionsDispatcherIssueRegistry.java
│ └── test/
│ └── java/
│ └── permissions/
│ └── dispatcher/
│ ├── CallNeedsPermissionDetectorKtTest.kt
│ ├── CallNeedsPermissionDetectorTest.kt
│ ├── CallOnRequestPermissionsResultDetectorKtTest.kt
│ ├── CallOnRequestPermissionsResultDetectorTest.kt
│ ├── NoCorrespondingNeedsPermissionDetectorKtTest.kt
│ ├── NoCorrespondingNeedsPermissionDetectorTest.kt
│ ├── NoDelegateOnResumeDetectorKtTest.kt
│ ├── NoDelegateOnResumeDetectorTest.kt
│ ├── PermissionsDispatcherIssueRegistryTest.kt
│ └── Utils.kt
├── processor/
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ ├── main/
│ │ ├── kotlin/
│ │ │ └── permissions/
│ │ │ └── dispatcher/
│ │ │ └── processor/
│ │ │ ├── PermissionsProcessor.kt
│ │ │ ├── ProcessorUnit.kt
│ │ │ ├── RequestCodeProvider.kt
│ │ │ ├── RuntimePermissionsElement.kt
│ │ │ ├── exception/
│ │ │ │ ├── DuplicatedMethodNameException.kt
│ │ │ │ ├── DuplicatedValueException.kt
│ │ │ │ ├── MixPermissionTypeException.kt
│ │ │ │ ├── NoAnnotatedMethodsException.kt
│ │ │ │ ├── NoParametersAllowedException.kt
│ │ │ │ ├── NoThrowsAllowedException.kt
│ │ │ │ ├── PrivateMethodException.kt
│ │ │ │ ├── SpecialPermissionsWithNeverAskAgainException.kt
│ │ │ │ ├── WrongClassException.kt
│ │ │ │ ├── WrongParametersException.kt
│ │ │ │ └── WrongReturnTypeException.kt
│ │ │ ├── impl/
│ │ │ │ ├── java/
│ │ │ │ │ ├── JavaActivityProcessorUnit.kt
│ │ │ │ │ ├── JavaBaseProcessorUnit.kt
│ │ │ │ │ ├── JavaFragmentProcessorUnit.kt
│ │ │ │ │ ├── SensitivePermissionInterface.kt
│ │ │ │ │ ├── SystemAlertWindowHelper.kt
│ │ │ │ │ └── WriteSettingsHelper.kt
│ │ │ │ └── kotlin/
│ │ │ │ ├── KotlinActivityProcessorUnit.kt
│ │ │ │ ├── KotlinBaseProcessorUnit.kt
│ │ │ │ ├── KotlinFragmentProcessorUnit.kt
│ │ │ │ ├── SensitivePermissionInterface.kt
│ │ │ │ ├── SystemAlertWindowHelper.kt
│ │ │ │ └── WriteSettingsHelper.kt
│ │ │ └── util/
│ │ │ ├── Constants.kt
│ │ │ ├── Extensions.kt
│ │ │ ├── Helpers.kt
│ │ │ └── Validators.kt
│ │ └── resources/
│ │ └── META-INF/
│ │ ├── gradle/
│ │ │ └── incremental.annotation.processors
│ │ └── services/
│ │ └── javax.annotation.processing.Processor
│ └── test/
│ ├── java/
│ │ └── permissions/
│ │ └── dispatcher/
│ │ └── processor/
│ │ ├── KtProcessorTestSuite.kt
│ │ ├── ProcessorTestSuite.java
│ │ ├── base/
│ │ │ ├── AndroidAwareClassLoader.java
│ │ │ ├── BaseTest.java
│ │ │ ├── StringEquals.java
│ │ │ └── TestSuite.java
│ │ ├── data/
│ │ │ └── Source.java
│ │ └── util/
│ │ └── ExtensionsTest.kt
│ └── resources/
│ └── mockito-extensions/
│ └── org.mockito.plugins.MockMaker
├── sample/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── kotlin/
│ │ └── permissions/
│ │ └── dispatcher/
│ │ └── sample/
│ │ ├── MainActivity.kt
│ │ ├── camera/
│ │ │ ├── CameraPreview.kt
│ │ │ └── CameraPreviewFragment.kt
│ │ └── contacts/
│ │ └── ContactsFragment.kt
│ └── res/
│ ├── layout/
│ │ ├── activity_main.xml
│ │ ├── fragment_camera.xml
│ │ └── fragment_contacts.xml
│ └── values/
│ ├── dimens.xml
│ ├── strings.xml
│ └── template-styles.xml
├── settings.gradle
└── test/
├── .gitignore
├── build.gradle
└── src/
├── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── permissions/
│ └── dispatcher/
│ └── test/
│ ├── ActivityOnlyNeedsPermission.java
│ ├── ActivityWithAllAnnotations.java
│ ├── ActivityWithAllAnnotationsKt.kt
│ ├── ActivityWithNoParameterArgumentKt.kt
│ ├── ActivityWithOnNeverAskAgain.java
│ ├── ActivityWithOnPermissionDenied.java
│ ├── ActivityWithParametersKt.kt
│ ├── ActivityWithShowRationale.java
│ ├── ActivityWithSystemAlertWindow.java
│ ├── ActivityWithSystemAlertWindowAllAnnotations.java
│ ├── ActivityWithSystemAlertWindowKt.kt
│ ├── ActivityWithSystemAlertWindowKtAllAnnotations.kt
│ ├── ActivityWithWriteSetting.java
│ ├── ActivityWithWriteSettingAllAnnotations.java
│ ├── ActivityWithWriteSettingKt.kt
│ ├── ActivityWithWriteSettingKtAllAnnotations.kt
│ ├── FragmentWithAllAnnotations.java
│ └── FragmentWithAllAnnotationsKt.kt
└── test/
└── java/
└── permissions/
└── dispatcher/
└── test/
├── ActivityOnlyNeedsPermissionPermissionsDispatcherTest.kt
├── ActivityWithAllAnnotationsKtPermissionsDispatcherTest.kt
├── ActivityWithAllAnnotationsPermissionsDispatcherTest.kt
├── ActivityWithNoParameterArgumentKtPermissionsDispatcherTest.kt
├── ActivityWithOnNeverAskAgainPermissionsDispatcherTest.kt
├── ActivityWithOnPermissionDeniedPermissionsDispatcherTest.kt
├── ActivityWithParametersKtPermissionsDispatcherTest.kt
├── ActivityWithShowRationalePermissionsDispatcherTest.kt
├── ActivityWithSystemAlertWindowAllAnnotationsPermissionsDispatcherTest.kt
├── ActivityWithSystemAlertWindowKtAllAnnotationsTest.kt
├── ActivityWithSystemAlertWindowKtTest.kt
├── ActivityWithSystemAlertWindowPermissionsDispatcherTest.kt
├── ActivityWithWriteSettingAllAnnotationsPermissionsDispatcherTest.kt
├── ActivityWithWriteSettingKtAllAnnotationsTest.kt
├── ActivityWithWriteSettingKtTest.kt
├── ActivityWithWriteSettingPermissionsDispatcherTest.kt
├── Extensions.kt
├── FragmentWithAllAnnotationsKtPermissionsDispatcherTest.kt
└── FragmentWithAllAnnotationsPermissionsDispatcherTest.kt
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
## FAQs
___
## Overview
- Describe the issue briefly
### Expected
- What is the expected behavior?
### Actual
- What is the actual behavior?
## Environment
- Which library version are you using?
- On which devices do you observe the issue?
- Note any other information that might be useful
## Reproducible steps
- While it's not required, it'd be perfect to add a link to a sample project where you encounter the issue
================================================
FILE: .github/workflows/deploy_snapshot.yml
================================================
name: Deploy snapshot
on:
pull_request:
branches:
- master
types: [closed]
jobs:
build:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Prepare local.properties for testing
run: echo "sdk.dir=$ANDROID_HOME" > local.properties
- name: Build with Gradle
run: ./gradlew clean build
- name: Deploy snapshot
run: ./gradlew uploadArchives --no-daemon --no-parallel -PSONATYPE_NEXUS_USERNAME=${{secrets.SONATYPE_NEXUS_USERNAME}} -PSONATYPE_NEXUS_PASSWORD=${{secrets.SONATYPE_NEXUS_PASSWORD}} -Psigning.keyId=${{secrets.SIGNING_KEY_ID}} -Psigning.password=${{secrets.SIGNING_PASSWORD}} -Psigning.secretKeyRingFile=$(echo ~/.gradle/secring.gpg)
================================================
FILE: .github/workflows/ktx-release.yml
================================================
name: KTX Release
on:
push:
tags:
- 'ktx-*.*.*'
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: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Prepare local.properties for testing
run: echo "sdk.dir=$ANDROID_HOME" > local.properties
- name: Build with Gradle
run: ./gradlew clean build
- name: Decode the secret key and place the file
run: |
echo "${{secrets.SIGNING_SECRET_KEY_RING_FILE}}" > ~/.gradle/secring.gpg.b64
base64 -d ~/.gradle/secring.gpg.b64 > ~/.gradle/secring.gpg
- name: Publish project
run: ./gradlew ktx:uploadArchives --no-daemon --no-parallel -PSONATYPE_NEXUS_USERNAME=${{secrets.SONATYPE_NEXUS_USERNAME}} -PSONATYPE_NEXUS_PASSWORD=${{secrets.SONATYPE_NEXUS_PASSWORD}} -Psigning.keyId=${{secrets.SIGNING_KEY_ID}} -Psigning.password=${{secrets.SIGNING_PASSWORD}} -Psigning.secretKeyRingFile=$(echo ~/.gradle/secring.gpg)
- name: Close the repository
run: ./gradlew closeAndReleaseRepository
================================================
FILE: .github/workflows/pull_request_ci.yml
================================================
name: CI for pull request
on:
pull_request:
types: [assigned, opened, synchronize, reopened]
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: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Prepare local.properties for testing
run: echo "sdk.dir=$ANDROID_HOME" > local.properties
- name: Build with Gradle
run: ./gradlew clean check --stacktrace
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
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: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Prepare local.properties for testing
run: echo "sdk.dir=$ANDROID_HOME" > local.properties
- name: Build with Gradle
run: ./gradlew clean build
- name: Decode the secret key and place the file
run: |
echo "${{secrets.SIGNING_SECRET_KEY_RING_FILE}}" > ~/.gradle/secring.gpg.b64
base64 -d ~/.gradle/secring.gpg.b64 > ~/.gradle/secring.gpg
- name: Publish project
run: ./gradlew uploadArchives --no-daemon --no-parallel -PSONATYPE_NEXUS_USERNAME=${{secrets.SONATYPE_NEXUS_USERNAME}} -PSONATYPE_NEXUS_PASSWORD=${{secrets.SONATYPE_NEXUS_PASSWORD}} -Psigning.keyId=${{secrets.SIGNING_KEY_ID}} -Psigning.password=${{secrets.SIGNING_PASSWORD}} -Psigning.secretKeyRingFile=$(echo ~/.gradle/secring.gpg)
- name: Close the repository
run: ./gradlew closeAndReleaseRepository
================================================
FILE: .gitignore
================================================
.gradle
local.properties
build/
.idea/
*.iml
================================================
FILE: CHANGELOG.md
================================================
# ChangeLog
- 4.9.2 | ktx: 1.1.4 2022/04/04
- Fix: [fix: use ContextCompat instead of PermissionChecker](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/757)
- ktx: 1.1.3 2021/11/07
- Fix: [sort permissions to secure identification](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/729)
- ktx: 1.1.2 2021/08/14
- Fix: [permissionRequest proceed doesn't work due to KtxPermissionRequest.requestPermission being null](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/742)
- ktx: 1.1.1 2021/08/09
- Fix: [Avoid memory leak with ViewModel](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/741)
- Fix: [Support device orientation again](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/738)
- Fix: [Write settings action bug fix](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/737)
- 4.9.1 2021/08/09
- Fix: [Address compile error on Kotlin 1.5](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/733)
- ktx: 1.0.5 2021/04/14
- Fix: [Use commitAllowingStateLoss() instead of commitNowAllowingStateLoss() in PermissionRequestFragment dismiss() to avoid exception when performing multiple transactions at same time](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/724)
- ktx: 1.0.4 2021/03/16
- Fix: [Wrong behaviour of ktx library when two permissions are requested in one screen](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/717)
- ktx: 1.0.3 2021/03/03
- Fix: stop depending on SNAPSHOT library module
- ktx: 1.0.2 2021/03/03
- Fix: [System dialog doesn't show up, after manually disable permission from setting](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/705)
- ktx: 1.0.1 2020/09/17
- Fix: [fix: wrap PermissionResult with Event](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/689)
- ktx: 1.0.0 2020/08/31
- fix: [observe ViewModel only when a permission has not been granted](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/681)
- feat: [Location dedicated permission methods](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/679)
- 4.8.0 2020/08/31
- Feat: [Bug fix for NeedOnRequestPermissionsResult lint derector](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/680)
- ktx: 1.0.0-beta1
- Update: [rewrite internal implementation](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/678)
- 4.7.0 2020/03/25
- Feat: [ktx module](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/665)
- Update: [Add missing dangerous permissions check in later versions](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/652)
- Update: [Use the constant from `PermissionChecker`](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/658)
- 4.6.0 2019/10/30
- Fix: lint CallNeedsPermission on same named function [#602](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/602)
- Fix: Remove Conductor support [#644](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/644)
[#620](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/620)
- Fix: Java to Kotlin collections mapping [#643](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/643)
- 4.5.0 2019/07/01
- Improve: Incremental annotation processing for Kotlin [#626](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/626)
- Fix: lint CallNeedsPermission on same named function [#602](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/602)
- Fix: java-kotlin conversion issue [#603](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/603) [#620](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/620)
- Fix: Remove redundant BuildConfig file [#607](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/607)
- 4.3.1 2019/04/08
- Add: Add support for internal classes [#574](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/606)
- 4.3.0 2018/12/31
- Add: Conductor support [#574](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/574)
- 4.2.0 2018/12/21
- Add: OnShowRationale API change with keeping backward compatibility [#569](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/569)
- Update: Change maven groupId from `com.github.hotchemi` to `org.permissionsdispatcher` [#560](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/560)
- Doc: Publish document site! https://permissions-dispatcher.github.io
- Fix: Any is translated to java.lang.Object [#545](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/545)
- Add: Support Java incremental compilation [#473](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/473)
- Update: Drop Xiaomi support [#548](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/548)
- 4.1.0 2018/12/07
- Fix: compile time validation for detecting OnNeverAskAgain with special permissions [#549](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/549)
- Fix: Fix CallNeedsPermissionDetector to scan only annotated classes [#536](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/536)
- 4.0.0 2018/10/20
- Update: [Update androidx ver to 1.0.0](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/530)
- Fix: [Add NonNull annotation and use requireActivity](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/526)
- Fix: [workaround to convert java String to kotlin String in argument parameter](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/509)
- 4.0.0-alpha1 2018/07/12
- Remove: Remove native Fragment support[#498](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/498)
- Add: Jetpack support [#494](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/494)
- 3.3.2 2018/12/07
- Update: Drop Xiaomi support [#548](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/548)
- 3.3.1 2018/06/25
- Internal: Update using Kotlin ver to 1.2.50 [#489](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/489)
- Add: Add a lint rule for not call WithPermissionCheck inside onResume [#486](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/486)
- Fix: Fix compile error when request SYSTEM_ALERT_WINDOW on SupportFragment [#482](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/482)
- Fix: Fix the problem with order matter "Useless @OnShowRationale declaration [#479](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/479)
- 3.2.0 2018/04/17
- Update: Address lint for Kotlin project [#460](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/460)
- Add: Add JvmName annotation to generated file [#458](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/458)
- Update: Deprecate android.app.Fragment [#454](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/454)
- Fix: Kotlin file problem with CallOnRequestPermissionsResultDetector [#449](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/449)
- 3.1.0 2017/12/18
- Fix: nullable params with Kotlin [#397](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/397)
- Fix: PD does not generate kt version of SYSTEM_ALERT_WINDOW [#406](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/406)
- 3.0.1 2017/09/17
- Fix: NeedsPermission annotated method with parameter doesn't work in Kotlin [#376](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/376)
- Fix: CallNeedsPermission check incorrectly flags calls to methods of different class [#377](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/377)
- 3.0.0 2017/09/16
- Add fully [Kotlin support](https://github.com/hotchemi/PermissionsDispatcher/blob/master/doc/kotlin_support.md)!
- Allow for Deterministic, Reproducible Builds with sorted inputs [#342](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/342)
- Internal: Migrate Lint Rules to UAST [363](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/363)
- Rename withCheck to withPermissionCheck [#365](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/365)
- Fix CallNeedsPermissionDetector to work correctly [#368](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/368)
- 2.4.0 2017/05/01
- Fix `SupportV13MissingException` with newer Gradle [#279](https://github.com/hotchemi/PermissionsDispatcher/issues/279).
- Now we bundle support v13 library in the library, you don't have to add v13 dependency by yourself.
- If you don't need v13 remove it rxpressly. ref: [README](https://github.com/hotchemi/PermissionsDispatcher#download)
- Remove a workaround in the case `targetSdkVersion < 23` [#305](https://github.com/hotchemi/PermissionsDispatcher/issues/305).
- If you need the workaround please use older version.
- 2.3.2 2017/03/10
- Update minSdkVersion to API level 9 [#286](https://github.com/hotchemi/PermissionsDispatcher/pull/286).
- Add new compile-time check [#284](https://github.com/hotchemi/PermissionsDispatcher/pull/284).
- Fix the problem with receiver of startActivityForResult [#280](https://github.com/hotchemi/PermissionsDispatcher/pull/280)
- Support nested annotated class [#263](https://github.com/hotchemi/PermissionsDispatcher/pull/263)
- 2.3.1 2016/12/26
- Fix a bug related to Xiaomi. [#240](https://github.com/hotchemi/PermissionsDispatcher/issues/240).
- Address [#244](https://github.com/hotchemi/PermissionsDispatcher/issues/244) just in case.
- 2.3.0 2016/12/14
- Start Xiaomi support. [#235](https://github.com/hotchemi/PermissionsDispatcher/pull/235).
- Fix slight bug for lint check. [#230](https://github.com/hotchemi/PermissionsDispatcher/pull/230).
- 2.2.1 2016/12/08
- Lint update: Migrate to Psi APIs [#227](https://github.com/hotchemi/PermissionsDispatcher/pull/227).
- Make sure to support Java 1.6 [#222](https://github.com/hotchemi/PermissionsDispatcher/pull/222).
- 2.2.0 2016/09/25
- Support maxSdkVersion [#204](https://github.com/hotchemi/PermissionsDispatcher/pull/204).
- Add ProGuard support [#175](https://github.com/hotchemi/PermissionsDispatcher/pull/175).
- Some internal cleanup.
- 2.1.3 2016/05/12
- Fix [#147](https://github.com/hotchemi/PermissionsDispatcher/pull/147).
- 2.1.2 2016/04/11
- Fix [#131](https://github.com/hotchemi/PermissionsDispatcher/pull/131).
- Add [#122](https://github.com/hotchemi/PermissionsDispatcher/pull/122).
- 2.1.1 2016/03/30
- Fix [#124](https://github.com/hotchemi/PermissionsDispatcher/issues/124).
- 2.1.0 2016/03/20
- Fix [#114](https://github.com/hotchemi/PermissionsDispatcher/issues/114).
- 2.0.9 2016/03/19
- Fix [#112](https://github.com/hotchemi/PermissionsDispatcher/issues/112).
- 2.0.8 2016/03/09
- Fix [#107](https://github.com/hotchemi/PermissionsDispatcher/issues/107).
- Fix [#109](https://github.com/hotchemi/PermissionsDispatcher/issues/109).
- 2.0.7 2016/02/16
- Kotlin 1.0 support [#98](https://github.com/hotchemi/PermissionsDispatcher/pull/98).
- 2.0.6 2016/02/15
- Add lint support [#75](https://github.com/hotchemi/PermissionsDispatcher/pull/75).
- Update kotlin version [#91](https://github.com/hotchemi/PermissionsDispatcher/pull/91).
- Performance improvement [#93](https://github.com/hotchemi/PermissionsDispatcher/pull/93).
- 2.0.5 2016/01/29
- Back to support JDK 1.7.
- 2.0.4 2016/01/22
- Fix [Issue #78](https://github.com/hotchemi/PermissionsDispatcher/issues/78)
- 2.0.3 2016/01/18
- **This version has a bug which @OnNeverAskAgain is never called. Please don't use it.**
- Add [#65](https://github.com/hotchemi/PermissionsDispatcher/pull/65).
- Add [#72](https://github.com/hotchemi/PermissionsDispatcher/pull/72).
- 2.0.2 2016/01/13
- Fix [#63](https://github.com/hotchemi/PermissionsDispatcher/issues/63).
- 2.0.1 2015/12/04
- Add `@NeverAskAgain`.
- 1.2.1 2015/09/14
- Fix #14 and #16.
- 1.2.0 2015/09/07
- Add `@DeniedPermission` and `@DeniedPermissions`.
- 1.1.2 2015/08/26
- Downgrade processor jdk version to 1.7.
- 1.1.1 2015/08/25
- Fix some bugs.
- 1.1.0 2015/08/24
- Add `@NeedsPermissions` and `@ShowRationales`.
- 1.0.1 2015/08/20
- Stop calling rationale methods in the OnRequestPermissionsResult.
- 1.0.0 2015/08/19
- Using support v4 compat classes.
- 0.9.0 2015/08/18
- Prepare for preview 3(final release).
- 0.5.0 2015/08/16
- Initial release.
================================================
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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 {yyyy} {name of copyright owner}
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: README.md
================================================
# PermissionsDispatcher  [](https://www.appbrain.com/stats/libraries/details/permissions_dispatcher/permissionsdispatcher)
- **Fully Kotlin/Java support**
- [**Special permissions support**](https://github.com/hotchemi/PermissionsDispatcher/blob/master/doc/special_permissions.md)
- **100% reflection-free**
PermissionsDispatcher provides a simple annotation-based API to handle runtime permissions.
This library lifts the burden that comes with writing a bunch of check statements whether a permission has been granted or not from you, in order to keep your code clean and safe.
## Usage
- Kotlin: You can pick either of [ktx](https://github.com/permissions-dispatcher/PermissionsDispatcher/tree/master/ktx) or [kapt](https://github.com/permissions-dispatcher/PermissionsDispatcher#0-prepare-androidmanifest).
- Java: [apt](https://github.com/hotchemi/PermissionsDispatcher/blob/master/doc/java_usage.md)
Here's a minimum example, in which you register a `MainActivity` which requires `Manifest.permission.CAMERA`.
### 0. Prepare AndroidManifest
Add the following line to `AndroidManifest.xml`:
``
### 1. Attach annotations
PermissionsDispatcher introduces only a few annotations, keeping its general API concise:
> NOTE: Annotated methods must not be `private`.
|Annotation|Required|Description|
|---|---|---|
|`@RuntimePermissions`|**✓**|Register an `Activity` or `Fragment` to handle permissions|
|`@NeedsPermission`|**✓**|Annotate a method which performs the action that requires one or more permissions|
|`@OnShowRationale`||Annotate a method which explains why the permissions are needed. It passes in a `PermissionRequest` object which can be used to continue or abort the current permission request upon user input. If you don't specify any argument for the method compiler will generate `process${NeedsPermissionMethodName}ProcessRequest` and `cancel${NeedsPermissionMethodName}ProcessRequest`. You can use those methods in place of `PermissionRequest`(ex: with `DialogFragment`)|
|`@OnPermissionDenied`||Annotate a method which is invoked if the user doesn't grant the permissions|
|`@OnNeverAskAgain`||Annotate a method which is invoked if the user chose to have the device "never ask again" about a permission|
```kotlin
@RuntimePermissions
class MainActivity : AppCompatActivity(), View.OnClickListener {
@NeedsPermission(Manifest.permission.CAMERA)
fun showCamera() {
supportFragmentManager.beginTransaction()
.replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance())
.addToBackStack("camera")
.commitAllowingStateLoss()
}
@OnShowRationale(Manifest.permission.CAMERA)
fun showRationaleForCamera(request: PermissionRequest) {
showRationaleDialog(R.string.permission_camera_rationale, request)
}
@OnPermissionDenied(Manifest.permission.CAMERA)
fun onCameraDenied() {
Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show()
}
@OnNeverAskAgain(Manifest.permission.CAMERA)
fun onCameraNeverAskAgain() {
Toast.makeText(this, R.string.permission_camera_never_askagain, Toast.LENGTH_SHORT).show()
}
}
```
### 2. Delegate to generated functions
Now generated functions become much more concise and intuitive than Java version!
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById(R.id.button_camera).setOnClickListener {
// NOTE: delegate the permission handling to generated function
showCameraWithPermissionCheck()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// NOTE: delegate the permission handling to generated function
onRequestPermissionsResult(requestCode, grantResults)
}
```
Check out the [sample](https://github.com/hotchemi/PermissionsDispatcher/tree/master/sample) for more details.
## Other features/plugins
- [Getting Special Permissions](https://github.com/hotchemi/PermissionsDispatcher/blob/master/doc/special_permissions.md)
- [maxSdkVersion](https://github.com/hotchemi/PermissionsDispatcher/blob/master/doc/maxsdkversion.md)
- [IntelliJ plugin](https://github.com/shiraji/permissions-dispatcher-plugin)
- [AndroidAnnotations plugin](https://github.com/AleksanderMielczarek/AndroidAnnotationsPermissionsDispatcherPlugin)
## Installation
**NOTE:**
- If you're using jCenter we've moved on to MavenCentral, see [migration guide](https://github.com/hotchemi/PermissionsDispatcher/blob/master/doc/migration_guide.md).
- 4.x only supports [Jetpack](https://developer.android.com/jetpack/). If you still use appcompat 3.x is the way to go.
To add PermissionsDispatcher to your project, include the following in your **app module** `build.gradle` file:
`${latest.version}` is [](https://search.maven.org/search?q=g:com.github.permissions-dispatcher)
```groovy
dependencies {
implementation "com.github.permissions-dispatcher:permissionsdispatcher:${latest.version}"
annotationProcessor "com.github.permissions-dispatcher:permissionsdispatcher-processor:${latest.version}"
}
```
With Kotlin:
```groovy
apply plugin: 'kotlin-kapt'
dependencies {
implementation "com.github.permissions-dispatcher:permissionsdispatcher:${latest.version}"
kapt "com.github.permissions-dispatcher:permissionsdispatcher-processor:${latest.version}"
}
```
## License
```
Copyright 2016 Shintaro Katafuchi, Marcel Schnelle, Yoshinori Isogai
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: annotation/.gitignore
================================================
/build
================================================
FILE: annotation/build.gradle
================================================
apply plugin: 'java-library'
apply plugin: "com.vanniktech.maven.publish"
================================================
FILE: annotation/gradle.properties
================================================
POM_NAME = permissionsdispatcher-annotation
POM_ARTIFACT_ID = permissionsdispatcher-annotation
POM_PACKAGING = jar
================================================
FILE: annotation/src/main/java/permissions/dispatcher/GrantableRequest.java
================================================
package permissions.dispatcher;
public interface GrantableRequest extends PermissionRequest {
void grant();
}
================================================
FILE: annotation/src/main/java/permissions/dispatcher/NeedsPermission.java
================================================
package permissions.dispatcher;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Register some methods which permissions are needed.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface NeedsPermission {
String[] value();
int maxSdkVersion() default 0;
}
================================================
FILE: annotation/src/main/java/permissions/dispatcher/OnNeverAskAgain.java
================================================
package permissions.dispatcher;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Register some methods handling the user's choice to permanently deny permissions.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnNeverAskAgain {
String[] value();
}
================================================
FILE: annotation/src/main/java/permissions/dispatcher/OnPermissionDenied.java
================================================
package permissions.dispatcher;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Register some methods which permissions are needed.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnPermissionDenied {
String[] value();
}
================================================
FILE: annotation/src/main/java/permissions/dispatcher/OnShowRationale.java
================================================
package permissions.dispatcher;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Register some methods which explain why permissions are needed.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnShowRationale {
String[] value();
}
================================================
FILE: annotation/src/main/java/permissions/dispatcher/PermissionRequest.java
================================================
package permissions.dispatcher;
/**
* Interface used by {@link OnShowRationale} methods to allow for continuation
* or cancellation of a permission request.
*/
public interface PermissionRequest {
void proceed();
void cancel();
}
================================================
FILE: annotation/src/main/java/permissions/dispatcher/RuntimePermissions.java
================================================
package permissions.dispatcher;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Register an Activity or Fragment to handle permissions.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface RuntimePermissions {
}
================================================
FILE: build.gradle
================================================
buildscript {
repositories {
mavenCentral()
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:$GRADLE_PLUGIN_VERSION"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$DOKKA_VERSION"
classpath "com.vanniktech:gradle-maven-publish-plugin:$PUBLISH_PLUGIN_VERSION"
}
}
allprojects {
repositories {
mavenCentral()
google()
jcenter()
// TODO: remove after publishing kompile-testing to Maven Central
maven { url 'https://jitpack.io' }
}
}
================================================
FILE: buildSrc/build.gradle
================================================
apply plugin: "groovy"
repositories {
mavenCentral()
}
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation "commons-io:commons-io:2.6"
}
================================================
FILE: buildSrc/src/main/groovy/permissions/dispatcher/AarToJarDependencyPlugin.groovy
================================================
package permissions.dispatcher
import org.apache.commons.io.FileUtils
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import java.util.zip.ZipFile
/**
* Custom plugin for creating "broken" JARs from AAR files.
*
* It allows an annotation processor's code generation test
* to resolve Android-specific classes on the classpath.
*
* Of course, the generated JARs aren't able to be loaded in by a ClassLoader,
* since they reference Android classes absent in a pure-JVM environment.
* However the generated code will compile and serve as the cornerstone
* for assertions of an annotation processor that interfaces with Android APIs.
*
* To do this, a special configuration is created when the plugin is applied.
* Any dependency specified in this configuration will be written into
* a specific resource file, then loaded into a custom ClassLoader when
* the unit tests are executed. Since AARs are not readable by a pure Java
* runtime environment, their classes need to be extracted, and converted
* into the JAR format.
*/
class AarToJarConversionPlugin implements Plugin {
private static final CONFIGURATION_NAME = "testCompileAar"
private static final TASK_NAME = "convertAarsToJar"
private static final CLASSPATH_FILE_NAME = "additional-test-classpath.txt"
@Override
void apply(Project project) {
// Custom Configuration
def configuration = project.configurations.create(CONFIGURATION_NAME)
// Conversion Task
def conversionTask = project.tasks.create(TASK_NAME, ConvertAarToJarTask) {
inputFiles = configuration
jarOutputDir = project.file("$project.buildDir/tmp/converted-aars")
classpathOutputDir = project.file("$project.buildDir/resources/test")
}
// Hook into the task dependency chain
project.tasks.getByName("classes").dependsOn conversionTask
}
static class ConvertAarToJarTask extends DefaultTask {
@Input
FileCollection inputFiles
@OutputDirectory
File jarOutputDir
@OutputDirectory
File classpathOutputDir
@TaskAction
def convert() {
def paths = []
inputFiles.each { file ->
// AARs need to be converted to JAR,
// other files are piped through to the output folder
def destinationFile
if (file.name.endsWith(".aar")) {
// Classes are contained inside a file named classes.jar,
// which resides in an AAR.
// Extract & rename it according to the artifact in question,
// then copy it over to the output
def zipFile = new ZipFile(file)
def classesEntry = zipFile.getEntry("classes.jar")
def newFileName = file.name.replace(".aar", ".jar")
def sourceInputStream = zipFile.getInputStream(classesEntry)
destinationFile = new File(jarOutputDir, newFileName)
FileUtils.copyInputStreamToFile(sourceInputStream, destinationFile)
project.logger.info("Converted AAR '$file.name' to JAR")
} else {
destinationFile = new File(jarOutputDir, file.name)
FileUtils.copyFile(file, destinationFile)
}
paths += destinationFile.toURI().toURL().toString()
}
def classpathFile = new File(classpathOutputDir, CLASSPATH_FILE_NAME)
classpathFile.withWriter { it.write(paths.join("\n")) }
}
}
}
================================================
FILE: buildSrc/src/main/groovy/permissions/dispatcher/AndroidJarDependencyExtension.groovy
================================================
package permissions.dispatcher
import javax.annotation.Nullable
class AndroidJarDependencyExtension {
@Nullable
String directory
}
================================================
FILE: buildSrc/src/main/groovy/permissions/dispatcher/AndroidJarDependencyPlugin.groovy
================================================
package permissions.dispatcher
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* This plugin creates a convenient dependency handler
* that allows a shorthand way to access an "android.jar" file
* of the environment's platform.
*
* It also adds an extension named "androidJar" to the project,
* with which the default directory to search can be overridden.
* If no value is specified explicitly, the plugin performs a
* lookup of the "sdk.dir" property in the user's local.properties
* file, and derives the Android version to include from the
* compile SDK version specified in the root-level gradle.properties.
*/
@SuppressWarnings("GroovyUnusedDeclaration")
class AndroidJarDependencyPlugin implements Plugin {
private static final EXTENSION_NAME = "androidJar"
private static final SDK_DIR_PROPERTY = "sdk.dir"
private static final PLATFORM_DIR_PROPERTY = "COMPILE_SDK_VERSION"
private static final ANDROID_JAR_FILENAME = "android.jar"
@Override
void apply(Project project) {
def extension = project.extensions.create(EXTENSION_NAME, AndroidJarDependencyExtension)
project.dependencies.ext.androidJar = {
def directory = extension.directory
if (!directory) {
// By default, concatenate android.jar path from build environment variables
def sdkDir = loadProperties(project.rootProject.file("local.properties"))
.getProperty(SDK_DIR_PROPERTY)
def platformDir = loadProperties(project.rootProject.file("gradle.properties"))
.getProperty(PLATFORM_DIR_PROPERTY)
directory = "$sdkDir/platforms/$platformDir"
}
if (!new File(directory, ANDROID_JAR_FILENAME).exists()) {
throw new RuntimeException("can't find android.jar in folder '$directory'!")
}
def dependency = project.fileTree(dir: directory, includes: [ANDROID_JAR_FILENAME])
return project.dependencies.create(dependency)
}
}
private static Properties loadProperties(File file) {
def properties = new Properties()
file.withReader { properties.load(it) }
return properties
}
}
================================================
FILE: doc/java_usage.md
================================================
## Usage with Java
Here's a minimum example, in which you register a `MainActivity` which requires `Manifest.permission.CAMERA`.
### 0. Prepare AndroidManifest
Add the following line to `AndroidManifest.xml`:
``
### 1. Attach annotations
PermissionsDispatcher introduces only a few annotations, keeping its general API concise:
> NOTE: Annotated methods must not be `private`.
|Annotation|Required|Description|
|---|---|---|
|`@RuntimePermissions`|**✓**|Register an `Activity` or `Fragment` to handle permissions|
|`@NeedsPermission`|**✓**|Annotate a method which performs the action that requires one or more permissions|
|`@OnShowRationale`||Annotate a method which explains why the permissions are needed. It passes in a `PermissionRequest` object which can be used to continue or abort the current permission request upon user input. If you don't specify any argument for the method compiler will generate `process${NeedsPermissionMethodName}ProcessRequest` and `cancel${NeedsPermissionMethodName}ProcessRequest`. You can use those methods in place of `PermissionRequest`(ex: with `DialogFragment`)|
|`@OnPermissionDenied`||Annotate a method which is invoked if the user doesn't grant the permissions|
|`@OnNeverAskAgain`||Annotate a method which is invoked if the user chose to have the device "never ask again" about a permission|
```java
@RuntimePermissions
public class MainActivity extends AppCompatActivity {
@NeedsPermission(Manifest.permission.CAMERA)
void showCamera() {
getSupportFragmentManager().beginTransaction()
.replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance())
.addToBackStack("camera")
.commitAllowingStateLoss();
}
@OnShowRationale(Manifest.permission.CAMERA)
void showRationaleForCamera(final PermissionRequest request) {
new AlertDialog.Builder(this)
.setMessage(R.string.permission_camera_rationale)
.setPositiveButton(R.string.button_allow, (dialog, button) -> request.proceed())
.setNegativeButton(R.string.button_deny, (dialog, button) -> request.cancel())
.show();
}
@OnPermissionDenied(Manifest.permission.CAMERA)
void showDeniedForCamera() {
Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show();
}
@OnNeverAskAgain(Manifest.permission.CAMERA)
void showNeverAskForCamera() {
Toast.makeText(this, R.string.permission_camera_neverask, Toast.LENGTH_SHORT).show();
}
}
```
### 2. Delegate to generated class
Upon compilation, PermissionsDispatcher generates a class for `MainActivityPermissionsDispatcher`([Activity Name] + PermissionsDispatcher), which you can use to safely access these permission-protected methods.
The only step you have to do is delegating the work to this helper class:
```java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button_camera).setOnClickListener(v -> {
// NOTE: delegate the permission handling to generated method
MainActivityPermissionsDispatcher.showCameraWithPermissionCheck(this);
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// NOTE: delegate the permission handling to generated method
MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
```
Check out the [sample](https://github.com/hotchemi/PermissionsDispatcher/tree/master/sample) for more details.
================================================
FILE: doc/maxsdkversion.md
================================================
## maxSdkVersion
[<uses-permission>](https://developer.android.com/guide/topics/manifest/uses-permission-element.html) has an attribute call `maxSdkVersion`. PermissionsDispatcher support the feature as well.
The following sample is for declaring `Manifest.permisison.WRITE_EXTERNAL_STORAGE` up to API level 18.
### 0. AndroidManifest
Declare the permission with `maxSdkVersion` attribute
```xml
```
### 1. Attach annotations with `maxSdkVersion`
```java
@RuntimePermissions
public class MainActivity extends AppCompatActivity {
@NeedsPermission(value = Manifest.permission.WRITE_EXTERNAL_STORAGE, maxSdkVersion = 18)
void getStorage() {
}
}
```
================================================
FILE: doc/migration_guide.md
================================================
# Migration guide
- [Migrating to Maven Central](#migrating-to-maven-central)
- [Migrating to 4.x](#migrating-to-4x)
- [Migrating to 3.x](#migrating-to-3x)
- [Migrating to 2.x](#migrating-to-2x)
## Migrating to Maven Central
Since Maven Central only accepts valid URL, we had no other choices but to change groupId again.
```diff
dependencies {
- implementation "org.permissionsdispatcher:permissionsdispatcher:${latest.version}"
+ implementation "com.github.permissions-dispatcher:permissionsdispatcher:${latest.version}"
- annotationProcessor "org.permissionsdispatcher:permissionsdispatcher-processor:${latest.version}"
+ annotationProcessor "com.github.permissions-dispatcher:permissionsdispatcher-processor:${latest.version}"
}
```
#### KTX
NOTE: Due to accidental mistake, artifact id `permissionsdispatcher-ktx` also exists on maven central but `ktx` is the correct one.
```diff
dependencies {
- implementation "org.permissionsdispatcher:permissionsdispatcher-ktx:${latest.version}"
+ implementation "com.github.permissions-dispatcher:ktx:${latest.version}"
}
```
## Migrating to 4.x
### Change Maven groupId
Issue: [#560](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/560)
```diff
dependencies {
- implementation "com.github.hotchemi:permissionsdispatcher:${latest.version}"
+ implementation "org.permissionsdispatcher:permissionsdispatcher:${latest.version}"
- annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:${latest.version}"
+ annotationProcessor "org.permissionsdispatcher:permissionsdispatcher-processor:${latest.version}"
}
```
### Migrate to AndroidX from AppCompat
Issue: [#488](https://github.com/permissions-dispatcher/PermissionsDispatcher/pull/488)
From 4.x we only support [Jetpack](https://developer.android.com/jetpack/).
Be sure you've gotten AndroidX migration done before upgrading the library version to 4.x.
## Migrating to 3.x
### Method name changing
Issue: [#355](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/355)
PermissionsDispatcher used to generate `***WithCheck` methods.
Starting with 3.0, the suffix of these methods becomes `***WithPermissionCheck`.
```diff
- MainActivityPermissionsDispatcher.showCameraWithCheck(this);
+ MainActivityPermissionsDispatcher.showCameraWithPermissionCheck(this);
```
The motivation of this change is to make generated code more declarative and easy to figure out what'd be going on under the hood.
This change is especially beneficial in Kotlin, because the receiver of the generated method is a class annotated with `@RuntimePermissions`, instead of a helper class named `***PermissionsDispatcher`.
### Kotlin support
Issue: [#320](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/320)
Actually it's been possible to use PermissionsDispatcher with Kotlin already, because of its interoperability with Java. But to give you a more concise and idiomatic experience, we added full Kotlin support which is described in [here](kotlin_support.md).
If you're already using PermissionsDispatcher with Kotlin, be aware of the following 2 changes:
#### `***WithPermissionCheck`
```diff
button.setOnClickListener {
- MainActivityPermissionsDispatcher.showCameraWithCheck(this)
+ showCameraWithPermissionCheck()
}
```
#### `onRequestPermissionsResult`
```diff
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- MainActivityPermissionsDispatcher.onRequestPermissionsResult(requestCode, grantResults)
+ onRequestPermissionsResult(requestCode, grantResults)
}
```
Those methods are defined as extension functions so now you don't have to call `***MainActivityPermissionsDispatcher` class!
## Migrating to 2.x
Since the internals of PermissionsDispatcher 2 have undergone a fundamental refactoring, most notably in the switch of languages to Kotlin for our annotation processor, the exposed APIs to users of the library have been tweaked as well. This guide will help you migrate to the latest version in just a few minutes!
### Update the artifacts
First of all, make sure you're pulling in the correct version of the library:
#### Before
```groovy
dependencies {
compile "com.github.hotchemi:permissionsdispatcher:1.2.1"
apt "com.github.hotchemi:permissionsdispatcher-processor:1.2.1"
}
```
#### After
```groovy
dependencies {
compile "com.github.hotchemi:permissionsdispatcher:2.0.0"
apt "com.github.hotchemi:permissionsdispatcher-processor:2.0.0"
}
```
*Make sure to change both the library and processor artifacts!*
### @RuntimePermissions
Your `Activity` or `Fragment` classes utilizing PermissionsDispatcher still need to be annotated with `@RuntimePermissions`, so nothing has changed there.
### @NeedsPermission
While version 1.x of the library had two flavors of the `@NeedsPermission` annotation (both the singular and plural form `@NeedsPermissions`), from now on there is only one. Make sure to change your previous usage of `@NeedsPermissions` to the unified one:
#### Before
```java
@NeedsPermissions({ CAMERA, WRITE_EXTERNAL_STORAGE })
void setupCamera() {
}
```
#### After
```java
@NeedsPermission({ CAMERA, WRITE_EXTERNAL_STORAGE })
void setupCamera() {
}
```
### @OnShowRationale
Both `@ShowsRationale` and its plural form `@ShowsRationales` have been removed in version 2. They have been unified in the new annotation `@OnShowRationale`, which is used to display a reasoning to your users about why you need to request permissions for specific actions. The signature of methods annotated with this new annotation require a single parameter of type `PermissionRequest`: When PermissionsDispatcher calls this method on your class, the `PermissionRequest` will provide an interface for you to either `cancel()` or `proceed()` the ongoing runtime permission process. This allows you to defer showing a system dialog, for instance if you would like to display your own explanation in a dialog beforehand. And don't worry: If you forget to follow this signature pattern, PermissionsDispatcher will remind you at compile time! This is one of the most exciting new features of PermissionsDispatcher 2:
#### Before
```java
@ShowsRationale({ CAMERA, WRITE_EXTERNAL_STORAGE })
void showCameraRationale() {
// Can't really do much here, since the system dialog is shown immediately afterwards...
Toast.makeText(...).show();
}
```
#### After
```java
@OnShowRationale({ CAMERA, WRITE_EXTERNAL_STORAGE })
void showCameraRationale(final PermissionRequest request) {
// E.g. show a dialog explaining why you need the permission.
// Call proceed() or cancel() on the incoming request to continue or abort the current permissions process
new AlertDialog.Builder(...)
.setPositiveButton("OK", (dialog, which) -> request.proceed())
.setNegativeButton("Abort", (dialog, which) -> request.cancel())
.show();
}
```
### @OnPermissionDenied
The old annotations `@DeniedPermission` and `@DeniedPermissions` have been unified into the new annotation `@OnPermissionDenied`. A method annotated with this annotation will be invoked if the user declines an ongoing permission request. For developers, it's as simple as switching the annotation's name:
#### Before
```java
@DeniedPermission({ CAMERA, WRITE_EXTERNAL_STORAGE })
void cameraDenied() {
}
```
#### After
```java
@OnPermissionDenied({ CAMERA, WRITE_EXTERNAL_STORAGE })
void cameraDenied() {
}
```
### That's it!
You're good to go and have successfully migrated to PermissionsDispatcher 2.x! We are striving to continue improving this library, so that the hassle of runtime permission handling becomes as convenient as possible for you.
================================================
FILE: doc/special_permissions.md
================================================
## Special Permissions
PermissionsDispatcher takes care of special permissions `Manifest.permission.SYSTEM_ALERT_WINDOW` and `Manifest.permission.WRITE_SETTINGS`.
The following sample is to grant `SYSTEM_ALERT_WINDOW`.
### 0. Prepare AndroidManifest
Add the following line to `AndroidManifest.xml`:
``
### 1. Attach annotations
It's the same as other permissions:
```java
@RuntimePermissions
public class MainActivity extends AppCompatActivity {
@NeedsPermission(Manifest.permission.SYSTEM_ALERT_WINDOW)
void systemAlertWindow() {
}
@OnShowRationale(Manifest.permission.SYSTEM_ALERT_WINDOW)
void systemAlertWindowOnShowRationale(final PermissionRequest request) {
}
@OnPermissionDenied(Manifest.permission.SYSTEM_ALERT_WINDOW)
void systemAlertWindowOnPermissionDenied() {
}
@OnNeverAskAgain(Manifest.permission.SYSTEM_ALERT_WINDOW)
void systemAlertWindowOnNeverAskAgain() {
}
}
```
### 2. Delegate to generated class
Unlike other permissions, special permissions require to call the delegation method at `onActivityResult`:
```java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button_system_alert_window).setOnClickListener(v -> {
// NOTE: delegate the permission handling to generated method
MainActivityPermissionsDispatcher.systemAlertWindowWithPermissionCheck(this);
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
MainActivityPermissionsDispatcher.onActivityResult(this, requestCode);
}
```
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: gradle.properties
================================================
# Upload configuration
USER = hotchemi
GROUP = com.github.permissions-dispatcher
VERSION_NAME = 4.10.0-SNAPSHOT
POM_NAME = PermissionsDispatcher
POM_DESCRIPTION = A declarative API to handle Android runtime permissions.
POM_INCEPTION_YEAR = 2015
POM_URL = https://github.com/permissions-dispatcher/PermissionsDispatcher
POM_SCM_URL = https://github.com/permissions-dispatcher/PermissionsDispatcher
POM_SCM_CONNECTION = https://github.com/permissions-dispatcher/PermissionsDispatcher.git
POM_LICENCE_NAME = The Apache Software License, Version 2.0
POM_LICENCE_URL = https://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST = repo
POM_DEVELOPER_ID = hotchemi
POM_DEVELOPER_NAME = Shintaro Katafuchi
POM_DEVELOPER_URL = https://github.com/hotchemi
# Plugin versions
DOKKA_VERSION = 1.4.20
PUBLISH_PLUGIN_VERSION = 0.18.0
GRADLE_PLUGIN_VERSION = 4.2.0
KOTLIN_VERSION = 1.5.20
KOTLIN_METADATA_VERSION = 0.3.0
ANDROIDX_LIBRARY_VERSION= 1.0.0
LIFECYCLE_VERSION = 2.2.0
ARC_TESTING_VERSION = 1.1.1
JAVAPOET_VERSION = 1.9.0
KOTLINPOET_VERSION = 1.3.0
JUNIT_VERSION = 4.12
MOCKITO_VERSION = 2.28.2
MOCKITO_KOTLIN_VERSION = 2.2.0
POWERMOCK_VERSION = 2.0.2
COMPILE_TESTING_VERSION = 0.12
LINT_VERSION = 26.3.2
ROBOLECTRIC_VERSION = 3.3.2
COMMONS_IO_VERSION = 2.6
KOMPILE_TESTING_VERSION = 0.1.4
# Android configuration
COMPILE_SDK_VERSION = android-29
TARGET_SDK_VERSION = 29
MIN_SDK_VERSION = 14
SAMPLE_MIN_SDK_VERSION = 16
# Gradle parameters
org.gradle.daemon = true
org.gradle.jvmargs = -XX:MaxPermSize=1024m -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m
# AndroidX
android.useAndroidX = true
android.enableJetifier = true
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
##############################################################################
##
## 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=""
# 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, switch paths to Windows format before running java
if $cygwin ; 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=$((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"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@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 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=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
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 init
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
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
: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: ktx/README.md
================================================
## permissionsdispatcher-ktx
`permissionsdispatcher-ktx` aims to let developers cope with runtime permissions handling in a declarative style without using annotation processing([kapt](https://kotlinlang.org/docs/reference/kapt.html)).
Let's see a minimum example, in which you register a `MainActivity` which requires `Manifest.permission.CAMERA`.
### 0. Declare a permission in AndroidManifest.xml
Add the following line to `AndroidManifest.xml`:
``
### 1. Define a requester with `constructPermissionsRequest`
The library provides `constructPermissionsRequest` which you can construct a requester object with the given several callback functions to be called in an appropriate situation.
```kotlin
/**
* @param permissions the permissions [requiresPermission] requires.
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param onNeverAskAgain the method invoked if the user does not deny the permissions with
* "never ask again" option.
* @param requiresPermission the action requires [permissions].
*/
fun FragmentActivity/*(or Fragment)*/.constructPermissionsRequest(
vararg permissions: String,
onShowRationale: ShowRationaleFunc? = null,
onPermissionDenied: Func? = null,
onNeverAskAgain: Func? = null,
requiresPermission: Func): PermissionsRequester
```
Here you just define `showCamera` and basically that's it! With the library you don't need to manually override `onRequestPermissionsResult`.
NOTE: Be sure to construct a requester every time an activity is created to capture the callbacks appropriately.
```kotlin
class MainActivity: AppCompatActivity {
// constructPermissionsRequest must be invoked every time an activity is created
private val showCamera = constructPermissionsRequest(Manifest.permission.CAMERA,
onShowRationale = ::onCameraShowRationale,
onPermissionDenied = ::onCameraDenied,
onNeverAskAgain = ::onCameraNeverAskAgain) {
// do something here
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById(R.id.button_camera).setOnClickListener {
showCamera.launch()
}
}
private fun onCameraDenied() {
Toast.makeText(requireContext(), R.string.permission_camera_denied, Toast.LENGTH_SHORT).show()
}
private fun onCameraShowRationale(request: PermissionRequest) {
request.proceed()
}
private fun onCameraNeverAskAgain() {
Toast.makeText(requireContext(), R.string.permission_camera_never_ask_again, Toast.LENGTH_SHORT).show()
}
}
```
Check out the [sample](https://github.com/hotchemi/PermissionsDispatcher/tree/master/ktx-sample) for more details.
#### Location Permissions
Since the location permissions have been one of the most sensitive permission group to deal with, we provide a dedicated method `constructLocationPermissionRequest`.
With the method you don't have to think of which API version you can ask [ACCESS_BACKGROUND_LOCATION](https://developer.android.com/about/versions/10/privacy/changes#app-access-device-location)(see the [issue](https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/646) for more detail).
```kotlin
/**
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param requiresPermission the action requires [permissions].
*/
fun FragmentActivity/*(or Fragment)*/.constructLocationPermissionRequest(
vararg permissions: LocationPermission,
onShowRationale: ShowRationaleFun? = null,
onPermissionDenied: Fun? = null,
onNeverAskAgain: Fun? = null,
requiresPermission: Fun
): PermissionsRequester
```
#### Special Permissions
The library also provides `constructWriteSettingsPermissionRequest` and
`constructSystemAlertWindowPermissionRequest` to support `WRITE_SETTINGS` and `SYSTEM_ALERT_WINDOW` that
requires exceptional handling.
```kotlin
/**
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param requiresPermission the action requires [permissions].
*/
fun FragmentActivity/*(or Fragment)*/.constructWriteSettingsPermissionRequest(
onShowRationale: ShowRationaleFunc? = null,
onPermissionDenied: Func? = null,
requiresPermission: Func): PermissionsRequester
/**
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param requiresPermission the action requires [permissions].
*/
fun FragmentActivity/*(or Fragment)*/.constructSystemAlertWindowPermissionRequest(
onShowRationale: ShowRationaleFunc? = null,
onPermissionDenied: Func? = null,
requiresPermission: Func): PermissionsRequester
```
## Installation
`${latest.version}` is [](https://search.maven.org/artifact/com.github.permissions-dispatcher/ktx/1.0.2/aar)
```groovy
dependencies {
implementation "com.github.permissions-dispatcher:ktx:${latest.version}"
}
```
================================================
FILE: ktx/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'org.jetbrains.dokka'
apply plugin: "com.vanniktech.maven.publish"
android {
compileSdkVersion COMPILE_SDK_VERSION
defaultConfig {
minSdkVersion MIN_SDK_VERSION
targetSdkVersion TARGET_SDK_VERSION
}
libraryVariants.all {
it.generateBuildConfigProvider.configure { enabled = false }
}
}
dependencies {
api "com.github.permissions-dispatcher:permissionsdispatcher:4.8.0"
implementation "androidx.core:core:$ANDROIDX_LIBRARY_VERSION"
implementation "androidx.appcompat:appcompat:$ANDROIDX_LIBRARY_VERSION"
implementation "androidx.fragment:fragment:$ANDROIDX_LIBRARY_VERSION"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION"
implementation "androidx.collection:collection:$ANDROIDX_LIBRARY_VERSION"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
testImplementation "junit:junit:$JUNIT_VERSION"
testImplementation "androidx.test:core:1.3.0-rc01"
testImplementation "androidx.test.ext:junit:1.1.2-rc01"
testImplementation "androidx.test:runner:1.3.0-rc01"
testImplementation "android.arch.core:core-testing:$ARC_TESTING_VERSION"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$MOCKITO_KOTLIN_VERSION"
}
================================================
FILE: ktx/gradle.properties
================================================
POM_NAME = ktx
POM_ARTIFACT_ID = ktx
POM_PACKAGING = aar
VERSION_NAME = 1.2.0-SNAPSHOT
================================================
FILE: ktx/src/main/AndroidManifest.xml
================================================
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/ActivityExtensions.kt
================================================
package permissions.dispatcher.ktx
import android.annotation.SuppressLint
import android.provider.Settings
import androidx.fragment.app.FragmentActivity
/**
* Constructs a request for ordinary permissions that require a grant from the user.
* Be sure to invoke the method when an activity is created to capture the valid callbacks.
*
* @param permissions the permissions [requiresPermission] requires.
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param onNeverAskAgain the method invoked if the user does not deny the permissions with
* "never ask again" option.
* @param requiresPermission the action requires [permissions].
* @see PermissionsRequester
*/
fun FragmentActivity.constructPermissionsRequest(
vararg permissions: String,
onShowRationale: ShowRationaleFun? = null,
onPermissionDenied: Fun? = null,
onNeverAskAgain: Fun? = null,
requiresPermission: Fun
): PermissionsRequester = PermissionsRequesterImpl(
permissions = permissions,
activity = this,
onShowRationale = onShowRationale,
onPermissionDenied = onPermissionDenied,
onNeverAskAgain = onNeverAskAgain,
requiresPermission = requiresPermission,
permissionRequestType = PermissionRequestType.Normal
)
/**
* Constructs a request for location permissions that require a grant from the user.
* Be sure to invoke the method when an activity is created to capture the valid callbacks.
*
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param requiresPermission the action requires [permissions].
* @see PermissionsRequester
*/
fun FragmentActivity.constructLocationPermissionRequest(
vararg permissions: LocationPermission,
onShowRationale: ShowRationaleFun? = null,
onPermissionDenied: Fun? = null,
onNeverAskAgain: Fun? = null,
requiresPermission: Fun
): PermissionsRequester = PermissionsRequesterImpl(
permissions = permissions.filterByApiLevel(),
activity = this,
onShowRationale = onShowRationale,
onPermissionDenied = onPermissionDenied,
onNeverAskAgain = onNeverAskAgain,
requiresPermission = requiresPermission,
permissionRequestType = PermissionRequestType.Normal
)
/**
* Constructs a request for [android.Manifest.permission.WRITE_SETTINGS].
* Be sure to invoke the method when an activity is created to capture the valid callbacks.
*
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param requiresPermission the action requires [permissions].
* @see PermissionsRequester
*/
@SuppressLint("InlinedApi")
fun FragmentActivity.constructWriteSettingsPermissionRequest(
onShowRationale: ShowRationaleFun? = null,
onPermissionDenied: Fun? = null,
requiresPermission: Fun
): PermissionsRequester = PermissionsRequesterImpl(
permissions = arrayOf(Settings.ACTION_MANAGE_WRITE_SETTINGS),
activity = this,
onShowRationale = onShowRationale,
onPermissionDenied = onPermissionDenied,
onNeverAskAgain = null,
requiresPermission = requiresPermission,
permissionRequestType = PermissionRequestType.WriteSettings
)
/**
* Constructs a request for [android.Manifest.permission.SYSTEM_ALERT_WINDOW].
* Be sure to invoke the method when an activity is created to capture the valid callbacks.
*
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param requiresPermission the action requires [permissions].
* @see PermissionsRequester
*/
@SuppressLint("InlinedApi")
fun FragmentActivity.constructSystemAlertWindowPermissionRequest(
onShowRationale: ShowRationaleFun? = null,
onPermissionDenied: Fun? = null,
requiresPermission: Fun
): PermissionsRequester = PermissionsRequesterImpl(
permissions = arrayOf(Settings.ACTION_MANAGE_OVERLAY_PERMISSION),
activity = this,
onShowRationale = onShowRationale,
onPermissionDenied = onPermissionDenied,
onNeverAskAgain = null,
requiresPermission = requiresPermission,
permissionRequestType = PermissionRequestType.SystemAlertWindow
)
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/Event.kt
================================================
package permissions.dispatcher.ktx
import androidx.annotation.AnyThread
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
* ref: https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
*/
@AnyThread
class Event(private val content: T) {
@Volatile
private var hasBeenHandled = false
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? = if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/FragmentExtensions.kt
================================================
package permissions.dispatcher.ktx
import android.annotation.SuppressLint
import android.provider.Settings
import androidx.fragment.app.Fragment
/**
* Constructs a request for ordinary permissions that require a grant from the user.
* Be sure to invoke the method when an activity is created to capture the valid callbacks.
*
* @param permissions the permissions [requiresPermission] requires.
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param onNeverAskAgain the method invoked if the user does not deny the permissions with
* "never ask again" option.
* @param requiresPermission the action requires [permissions].
* @see PermissionsRequester
*/
fun Fragment.constructPermissionsRequest(
vararg permissions: String,
onShowRationale: ShowRationaleFun? = null,
onPermissionDenied: Fun? = null,
onNeverAskAgain: Fun? = null,
requiresPermission: Fun
): PermissionsRequester = PermissionsRequesterImpl(
permissions = permissions,
activity = requireActivity(),
onShowRationale = onShowRationale,
onPermissionDenied = onPermissionDenied,
onNeverAskAgain = onNeverAskAgain,
requiresPermission = requiresPermission,
permissionRequestType = PermissionRequestType.Normal
)
/**
* Constructs a request for location permissions that require a grant from the user.
* Be sure to invoke the method when an activity is created to capture the valid callbacks.
*
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param requiresPermission the action requires [permissions].
* @see PermissionsRequester
*/
fun Fragment.constructLocationPermissionRequest(
vararg permissions: LocationPermission,
onShowRationale: ShowRationaleFun? = null,
onPermissionDenied: Fun? = null,
onNeverAskAgain: Fun? = null,
requiresPermission: Fun
): PermissionsRequester = PermissionsRequesterImpl(
permissions = permissions.filterByApiLevel(),
activity = requireActivity(),
onShowRationale = onShowRationale,
onPermissionDenied = onPermissionDenied,
onNeverAskAgain = onNeverAskAgain,
requiresPermission = requiresPermission,
permissionRequestType = PermissionRequestType.Normal
)
/**
* Constructs a request for [android.Manifest.permission.WRITE_SETTINGS].
* Be sure to invoke the method when an activity is created to capture the valid callbacks.
*
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param requiresPermission the action requires [permissions].
* @see PermissionsRequester
*/
@SuppressLint("InlinedApi")
fun Fragment.constructWriteSettingsPermissionRequest(
onShowRationale: ShowRationaleFun? = null,
onPermissionDenied: Fun? = null,
requiresPermission: Fun
): PermissionsRequester = PermissionsRequesterImpl(
permissions = arrayOf(Settings.ACTION_MANAGE_WRITE_SETTINGS),
activity = requireActivity(),
onShowRationale = onShowRationale,
onPermissionDenied = onPermissionDenied,
onNeverAskAgain = null,
requiresPermission = requiresPermission,
permissionRequestType = PermissionRequestType.WriteSettings
)
/**
* Constructs a request for [android.Manifest.permission.SYSTEM_ALERT_WINDOW].
* Be sure to invoke the method when an activity is created to capture the valid callbacks.
*
* @param onShowRationale the method explains why the permissions are required.
* @param onPermissionDenied the method invoked if the user doesn't grant the permissions.
* @param requiresPermission the action requires [permissions].
* @see PermissionsRequester
*/
@SuppressLint("InlinedApi")
fun Fragment.constructSystemAlertWindowPermissionRequest(
onShowRationale: ShowRationaleFun? = null,
onPermissionDenied: Fun? = null,
requiresPermission: Fun
): PermissionsRequester = PermissionsRequesterImpl(
permissions = arrayOf(Settings.ACTION_MANAGE_OVERLAY_PERMISSION),
activity = requireActivity(),
onShowRationale = onShowRationale,
onPermissionDenied = onPermissionDenied,
onNeverAskAgain = null,
requiresPermission = requiresPermission,
permissionRequestType = PermissionRequestType.SystemAlertWindow
)
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/KtxPermissionRequest.kt
================================================
package permissions.dispatcher.ktx
import permissions.dispatcher.PermissionRequest
import java.lang.ref.WeakReference
internal class KtxPermissionRequest(
private val requestPermission: WeakReference,
private val permissionDenied: WeakReference?
) : PermissionRequest {
override fun proceed() {
requestPermission.get()?.invoke()
}
override fun cancel() {
permissionDenied?.get()?.invoke()
}
companion object {
fun create(onPermissionDenied: Fun?, requestPermission: Fun) = KtxPermissionRequest(
requestPermission = WeakReference(requestPermission),
permissionDenied = onPermissionDenied?.let { WeakReference(it) }
)
}
}
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/LocationPermission.kt
================================================
package permissions.dispatcher.ktx
import android.Manifest
import android.annotation.SuppressLint
import android.os.Build
/**
* An enum which represents location permission types.
*
* **See:** [Android Developers](https://developer.android.com/training/location/permissions)
*/
enum class LocationPermission(internal val permission: String, internal val apiLevel: Int) {
FINE(Manifest.permission.ACCESS_FINE_LOCATION, 1),
COARSE(Manifest.permission.ACCESS_COARSE_LOCATION, 1),
@SuppressLint("InlinedApi")
BACKGROUND(Manifest.permission.ACCESS_BACKGROUND_LOCATION, 29)
}
internal fun Array.filterByApiLevel(sdkVer: Int = Build.VERSION.SDK_INT) =
asSequence().filter { it.apiLevel <= sdkVer }.map { it.permission }.toList().toTypedArray()
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/PermissionRequestFragment.kt
================================================
package permissions.dispatcher.ktx
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import permissions.dispatcher.PermissionUtils
import permissions.dispatcher.PermissionUtils.verifyPermissions
import java.util.*
internal sealed class PermissionRequestFragment : Fragment() {
protected val requestCode = Random().nextInt(1000)
protected lateinit var viewModel: PermissionRequestViewModel
override fun onAttach(context: Context?) {
super.onAttach(context)
retainInstance = true
viewModel = ViewModelProvider(requireActivity()).get(PermissionRequestViewModel::class.java)
}
protected fun dismiss() =
fragmentManager?.beginTransaction()?.remove(this)?.commitAllowingStateLoss()
internal class NormalRequestPermissionFragment : PermissionRequestFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val permissions = arguments?.getStringArray(BUNDLE_PERMISSIONS_KEY) ?: return
requestPermissions(permissions, requestCode)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == this.requestCode) {
// https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/729
val key = permissions.sortedArray().contentToString()
if (verifyPermissions(*grantResults)) {
viewModel.postPermissionRequestResult(key, PermissionResult.GRANTED)
} else {
if (!PermissionUtils.shouldShowRequestPermissionRationale(this, *permissions)) {
viewModel.postPermissionRequestResult(
key,
PermissionResult.DENIED_AND_DISABLED
)
} else {
viewModel.postPermissionRequestResult(key, PermissionResult.DENIED)
}
}
}
dismiss()
}
companion object {
const val BUNDLE_PERMISSIONS_KEY = "key:permissions"
fun newInstance(permissions: Array) =
NormalRequestPermissionFragment().apply {
arguments = Bundle().apply {
putStringArray(BUNDLE_PERMISSIONS_KEY, permissions)
}
}
}
}
internal class SpecialRequestPermissionFragment : PermissionRequestFragment() {
private lateinit var action: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
action = arguments?.getString(BUNDLE_ACTION_KEY) ?: return
val packageName = context?.packageName ?: return
val uri = Uri.parse("package:$packageName")
startActivityForResult(Intent(action, uri), requestCode)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == this.requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& Settings.canDrawOverlays(activity)
) {
viewModel.postPermissionRequestResult(action, PermissionResult.GRANTED)
} else {
viewModel.postPermissionRequestResult(action, PermissionResult.DENIED)
}
}
dismiss()
}
companion object {
const val BUNDLE_ACTION_KEY = "key:action"
fun newInstance(action: String) = SpecialRequestPermissionFragment().apply {
arguments = Bundle().apply {
putString(BUNDLE_ACTION_KEY, action)
}
}
}
}
}
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/PermissionRequestResult.kt
================================================
package permissions.dispatcher.ktx
internal enum class PermissionResult {
GRANTED,
DENIED,
DENIED_AND_DISABLED
}
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/PermissionRequestType.kt
================================================
package permissions.dispatcher.ktx
import android.content.Context
import android.os.Build
import android.provider.Settings
import androidx.annotation.RequiresApi
import permissions.dispatcher.PermissionUtils.hasSelfPermissions
internal sealed class PermissionRequestType {
object Normal : PermissionRequestType() {
override fun checkPermissions(context: Context, permissions: Array): Boolean =
hasSelfPermissions(context, *permissions)
override fun fragment(permissions: Array): PermissionRequestFragment =
PermissionRequestFragment.NormalRequestPermissionFragment.newInstance(
permissions
)
}
object SystemAlertWindow : PermissionRequestType() {
override fun checkPermissions(context: Context, permissions: Array): Boolean =
Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context)
@RequiresApi(Build.VERSION_CODES.M)
override fun fragment(permissions: Array): PermissionRequestFragment =
PermissionRequestFragment.SpecialRequestPermissionFragment.newInstance(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION
)
}
object WriteSettings : PermissionRequestType() {
override fun checkPermissions(context: Context, permissions: Array): Boolean =
Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.System.canWrite(context)
@RequiresApi(Build.VERSION_CODES.M)
override fun fragment(permissions: Array): PermissionRequestFragment =
PermissionRequestFragment.SpecialRequestPermissionFragment.newInstance(
Settings.ACTION_MANAGE_WRITE_SETTINGS
)
}
abstract fun checkPermissions(context: Context, permissions: Array): Boolean
abstract fun fragment(permissions: Array): PermissionRequestFragment
}
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/PermissionRequestViewModel.kt
================================================
package permissions.dispatcher.ktx
import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.lang.ref.WeakReference
@MainThread
internal class PermissionRequestViewModel : ViewModel() {
private val permissionRequestResult =
MutableLiveData>>()
get() {
field.value ?: run { field.value = mutableMapOf() }
return field
}
fun postPermissionRequestResult(key: String, value: PermissionResult) {
permissionRequestResult.value?.set(key, Event(value))
permissionRequestResult.notifyObserver()
}
inline fun observe(
owner: LifecycleOwner,
key: String,
requiresPermission: WeakReference,
onPermissionDenied: WeakReference?,
onNeverAskAgain: WeakReference?
) {
permissionRequestResult.observe(owner, {
when (it[key]?.getContentIfNotHandled()) {
PermissionResult.GRANTED -> requiresPermission.get()?.invoke()
PermissionResult.DENIED -> onPermissionDenied?.get()?.invoke()
PermissionResult.DENIED_AND_DISABLED -> onNeverAskAgain?.get()?.invoke()
else -> Unit
}
})
}
fun removeObservers(owner: LifecycleOwner) = permissionRequestResult.removeObservers(owner)
private fun MutableLiveData.notifyObserver() {
this.value = this.value
}
}
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/PermissionsRequester.kt
================================================
package permissions.dispatcher.ktx
/**
* An intermediate class that is able to launch permissions request process as appropriate.
* [launch] method kicks off the actual process.
*/
interface PermissionsRequester {
fun launch()
}
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/PermissionsRequesterImpl.kt
================================================
package permissions.dispatcher.ktx
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModelProvider
import permissions.dispatcher.PermissionUtils.shouldShowRequestPermissionRationale
import java.lang.ref.WeakReference
internal class PermissionsRequesterImpl(
private val permissions: Array,
private val activity: FragmentActivity,
private val onShowRationale: ShowRationaleFun?,
private val onPermissionDenied: Fun?,
private val requiresPermission: Fun,
onNeverAskAgain: Fun?,
private val permissionRequestType: PermissionRequestType
) : PermissionsRequester {
private val viewModel = ViewModelProvider(activity).get(PermissionRequestViewModel::class.java)
private val requestFun: Fun = {
activity.supportFragmentManager
.beginTransaction()
.replace(android.R.id.content, permissionRequestType.fragment(permissions))
.commitAllowingStateLoss()
}
init {
viewModel.observe(
activity,
// https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/729
permissions.sortedArray().contentToString(),
WeakReference(requiresPermission),
WeakReference(onPermissionDenied),
WeakReference(onNeverAskAgain)
)
}
override fun launch() {
if (permissionRequestType.checkPermissions(activity, permissions)) {
viewModel.removeObservers(activity)
requiresPermission()
} else {
if (shouldShowRequestPermissionRationale(activity, *permissions) && onShowRationale != null) {
onShowRationale.invoke(KtxPermissionRequest.create(onPermissionDenied, requestFun))
} else {
requestFun.invoke()
}
}
}
}
================================================
FILE: ktx/src/main/java/permissions/dispatcher/ktx/TypeAliases.kt
================================================
package permissions.dispatcher.ktx
import permissions.dispatcher.PermissionRequest
internal typealias Fun = () -> Unit
internal typealias ShowRationaleFun = (PermissionRequest) -> Unit
================================================
FILE: ktx/src/test/java/permissions/dispatcher/test/EventTest.kt
================================================
package permissions.dispatcher.test
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNull
import org.junit.Test
import permissions.dispatcher.ktx.Event
class EventTest {
@Test
fun `getContentIfNotHandled returns null from the second access`() {
val testInstance = Event(1)
assertEquals(1, testInstance.getContentIfNotHandled())
assertNull(testInstance.getContentIfNotHandled())
assertNull(testInstance.getContentIfNotHandled())
}
}
================================================
FILE: ktx/src/test/java/permissions/dispatcher/test/KtxPermissionRequestTest.kt
================================================
package permissions.dispatcher.test
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.never
import permissions.dispatcher.ktx.*
class KtxPermissionRequestTest {
private lateinit var onPermissionDenied: Fun
private lateinit var requiresPermission: Fun
@Before
fun setUp() {
onPermissionDenied = mock()
requiresPermission = mock()
}
@Test
fun `PermissionRequest#proceed invokes requiresPermission`() {
val request = KtxPermissionRequest.create(onPermissionDenied, requiresPermission)
request.proceed()
verify(onPermissionDenied, never()).invoke()
verify(requiresPermission).invoke()
}
@Test
fun `PermissionRequest#cancel invokes onPermissionDenied`() {
val request = KtxPermissionRequest.create(onPermissionDenied, requiresPermission)
request.cancel()
verify(onPermissionDenied).invoke()
verify(requiresPermission, never()).invoke()
}
}
================================================
FILE: ktx/src/test/java/permissions/dispatcher/test/LocationPermissionTest.kt
================================================
package permissions.dispatcher.test
import org.junit.Assert.*
import org.junit.Test
import permissions.dispatcher.ktx.LocationPermission
import permissions.dispatcher.ktx.filterByApiLevel
class LocationPermissionTest {
@Test
fun `BACKGRGROUND should be excluded when sdkVer is less than 29`() {
val permissions = LocationPermission.values()
.filterByApiLevel(sdkVer = LocationPermission.BACKGROUND.apiLevel - 1)
assertEquals(2, permissions.size)
assertFalse(permissions.contains(LocationPermission.BACKGROUND.permission))
}
@Test
fun `BACKGRGROUND should be included when sdkVer equals to 29`() {
val permissions = LocationPermission.values()
.filterByApiLevel(sdkVer = LocationPermission.BACKGROUND.apiLevel)
assertEquals(3, permissions.size)
assertTrue(permissions.contains(LocationPermission.BACKGROUND.permission))
}
@Test
fun `BACKGRGROUND should be included when sdkVer is more than 29`() {
val permissions = LocationPermission.values()
.filterByApiLevel(sdkVer = LocationPermission.BACKGROUND.apiLevel + 1)
assertEquals(3, permissions.size)
assertTrue(permissions.contains(LocationPermission.BACKGROUND.permission))
}
}
================================================
FILE: ktx/src/test/java/permissions/dispatcher/test/PermissionRequestViewModelTest.kt
================================================
package permissions.dispatcher.test
import android.Manifest
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import permissions.dispatcher.ktx.Fun
import permissions.dispatcher.ktx.PermissionRequestViewModel
import permissions.dispatcher.ktx.PermissionResult
import java.lang.ref.WeakReference
class PermissionRequestViewModelTest {
@Rule
@JvmField
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val permission = arrayOf(Manifest.permission.CAMERA).contentToString()
private lateinit var viewModel: PermissionRequestViewModel
private lateinit var lifecycleOwner: LifecycleOwner
private lateinit var requiresPermission: Fun
private lateinit var onPermissionDenied: Fun
private lateinit var onNeverAskAgain: Fun
@Before
fun setup() {
viewModel = PermissionRequestViewModel()
lifecycleOwner = mock()
val lifecycle = LifecycleRegistry(mock())
lifecycle.markState(Lifecycle.State.RESUMED)
whenever(lifecycleOwner.lifecycle).thenReturn(lifecycle)
onPermissionDenied = mock()
requiresPermission = mock()
onNeverAskAgain = mock()
}
@After
fun cleanup() {
viewModel.removeObservers(lifecycleOwner)
}
@Test
fun `GRANTED emits requiresPermission`() {
viewModel.observe(
lifecycleOwner,
permission,
WeakReference(requiresPermission),
WeakReference(onPermissionDenied),
WeakReference(onNeverAskAgain)
)
viewModel.postPermissionRequestResult(permission, PermissionResult.GRANTED)
verify(requiresPermission).invoke()
verify(onPermissionDenied, never()).invoke()
verify(onNeverAskAgain, never()).invoke()
}
@Test
fun `DENIED emits onPermissionDenied`() {
viewModel.observe(
lifecycleOwner,
permission,
WeakReference(requiresPermission),
WeakReference(onPermissionDenied),
WeakReference(onNeverAskAgain)
)
viewModel.postPermissionRequestResult(permission, PermissionResult.DENIED)
verify(requiresPermission, never()).invoke()
verify(onPermissionDenied).invoke()
verify(onNeverAskAgain, never()).invoke()
}
@Test
fun `DENIED_AND_DISABLED emits onNeverAskAgain`() {
viewModel.observe(
lifecycleOwner,
permission,
WeakReference(requiresPermission),
WeakReference(onPermissionDenied),
WeakReference(onNeverAskAgain)
)
viewModel.postPermissionRequestResult(permission, PermissionResult.DENIED_AND_DISABLED)
verify(requiresPermission, never()).invoke()
verify(onPermissionDenied, never()).invoke()
verify(onNeverAskAgain).invoke()
}
}
================================================
FILE: ktx/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
================================================
mock-maker-inline
================================================
FILE: ktx-sample/.gitignore
================================================
/build
================================================
FILE: ktx-sample/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion COMPILE_SDK_VERSION
defaultConfig {
applicationId "permissions.dispatcher.sample"
targetSdkVersion TARGET_SDK_VERSION
minSdkVersion SAMPLE_MIN_SDK_VERSION
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
implementation "androidx.appcompat:appcompat:$ANDROIDX_LIBRARY_VERSION"
implementation project(':ktx')
}
================================================
FILE: ktx-sample/src/main/AndroidManifest.xml
================================================
================================================
FILE: ktx-sample/src/main/java/permissions/dispatcher/ktx/sample/MainActivity.kt
================================================
package permissions.dispatcher.ktx.sample
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
val fragment = MainFragment()
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.commitNowAllowingStateLoss()
}
}
}
================================================
FILE: ktx-sample/src/main/java/permissions/dispatcher/ktx/sample/MainFragment.kt
================================================
package permissions.dispatcher.ktx.sample
import android.Manifest
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import permissions.dispatcher.PermissionRequest
import permissions.dispatcher.ktx.PermissionsRequester
import permissions.dispatcher.ktx.constructPermissionsRequest
class MainFragment : Fragment() {
private lateinit var permissionsRequester: PermissionsRequester
private lateinit var fileManagerRequester: PermissionsRequester
override fun onAttach(context: Context?) {
super.onAttach(context)
permissionsRequester = constructPermissionsRequest(
Manifest.permission.CAMERA,
onShowRationale = ::onCameraShowRationale,
onPermissionDenied = ::onCameraDenied,
onNeverAskAgain = ::onCameraNeverAskAgain,
requiresPermission = ::openCamera
)
fileManagerRequester = constructPermissionsRequest(
Manifest.permission.READ_EXTERNAL_STORAGE,
requiresPermission = ::openFileManager
)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_main, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById