Activity or Fragment to handle permissions.
10 | */
11 | @Target(ElementType.TYPE)
12 | @Retention(RetentionPolicy.CLASS)
13 | public @interface RuntimePermissions {
14 | }
--------------------------------------------------------------------------------
/annotation/src/main/java/permissions/dispatcher/OnShowRationale.java:
--------------------------------------------------------------------------------
1 | package permissions.dispatcher;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Register some methods which explain why permissions are needed.
10 | */
11 | @Target(ElementType.METHOD)
12 | @Retention(RetentionPolicy.CLASS)
13 | public @interface OnShowRationale {
14 | String[] value();
15 | }
--------------------------------------------------------------------------------
/processor/src/main/kotlin/permissions/dispatcher/processor/impl/kotlin/SensitivePermissionInterface.kt:
--------------------------------------------------------------------------------
1 | package permissions.dispatcher.processor.impl.kotlin
2 |
3 | import com.squareup.kotlinpoet.FunSpec
4 |
5 | interface SensitivePermissionInterface {
6 | fun addHasSelfPermissionsCondition(builder: FunSpec.Builder, activity: String, permissionField: String)
7 |
8 | fun addRequestPermissionsStatement(builder: FunSpec.Builder, targetParam: String = "this", activityVar: String, requestCodeField: String)
9 | }
10 |
--------------------------------------------------------------------------------
/annotation/src/main/java/permissions/dispatcher/OnNeverAskAgain.java:
--------------------------------------------------------------------------------
1 | package permissions.dispatcher;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Register some methods handling the user's choice to permanently deny permissions.
10 | */
11 | @Target(ElementType.METHOD)
12 | @Retention(RetentionPolicy.CLASS)
13 | public @interface OnNeverAskAgain {
14 | String[] value();
15 | }
16 |
--------------------------------------------------------------------------------
/annotation/src/main/java/permissions/dispatcher/NeedsPermission.java:
--------------------------------------------------------------------------------
1 | package permissions.dispatcher;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Register some methods which permissions are needed.
10 | */
11 | @Target(ElementType.METHOD)
12 | @Retention(RetentionPolicy.CLASS)
13 | public @interface NeedsPermission {
14 | String[] value();
15 |
16 | int maxSdkVersion() default 0;
17 | }
--------------------------------------------------------------------------------
/processor/src/main/kotlin/permissions/dispatcher/processor/util/Constants.kt:
--------------------------------------------------------------------------------
1 | package permissions.dispatcher.processor.util
2 |
3 | const val FILE_COMMENT = "This file was generated by PermissionsDispatcher. Do not modify!"
4 | const val GEN_CLASS_SUFFIX = "PermissionsDispatcher"
5 | const val GEN_REQUEST_CODE_PREFIX = "REQUEST_"
6 | const val GEN_PERMISSION_PREFIX = "PERMISSION_"
7 | const val GEN_PENDING_PREFIX = "PENDING_"
8 | const val GEN_WITH_PERMISSION_CHECK_SUFFIX = "WithPermissionCheck"
9 | const val GEN_PERMISSION_REQUEST_SUFFIX = "PermissionRequest"
10 |
--------------------------------------------------------------------------------
/processor/src/main/kotlin/permissions/dispatcher/processor/exception/WrongParametersException.kt:
--------------------------------------------------------------------------------
1 | package permissions.dispatcher.processor.exception
2 |
3 | import permissions.dispatcher.processor.util.simpleString
4 | import javax.lang.model.element.ExecutableElement
5 | import javax.lang.model.type.TypeMirror
6 |
7 | class WrongParametersException(e: ExecutableElement, numParams: Int, requiredType: TypeMirror) : RuntimeException("Method '${e.simpleString()}()' must has less than or equal to $numParams size parameter and type of it is supposed to be '${requiredType.simpleString()}'")
--------------------------------------------------------------------------------
/lint/src/test/java/permissions/dispatcher/PermissionsDispatcherIssueRegistryTest.kt:
--------------------------------------------------------------------------------
1 | package permissions.dispatcher
2 |
3 | import org.junit.Test
4 |
5 | import com.google.common.truth.Truth.assertThat
6 |
7 | class PermissionsDispatcherIssueRegistryTest {
8 | @Test
9 | fun issues() {
10 | assertThat(PermissionsDispatcherIssueRegistry().issues).containsAllOf(
11 | CallNeedsPermissionDetector.ISSUE,
12 | CallOnRequestPermissionsResultDetector.ISSUE,
13 | NoCorrespondingNeedsPermissionDetector.ISSUE)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ktx/src/test/java/permissions/dispatcher/test/EventTest.kt:
--------------------------------------------------------------------------------
1 | package permissions.dispatcher.test
2 |
3 | import junit.framework.Assert.assertEquals
4 | import junit.framework.Assert.assertNull
5 | import org.junit.Test
6 | import permissions.dispatcher.ktx.Event
7 |
8 | class EventTest {
9 | @Test
10 | fun `getContentIfNotHandled returns null from the second access`() {
11 | val testInstance = Event(1)
12 | assertEquals(1, testInstance.getContentIfNotHandled())
13 | assertNull(testInstance.getContentIfNotHandled())
14 | assertNull(testInstance.getContentIfNotHandled())
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/processor/src/main/kotlin/permissions/dispatcher/processor/RequestCodeProvider.kt:
--------------------------------------------------------------------------------
1 | package permissions.dispatcher.processor
2 |
3 | import java.util.concurrent.atomic.AtomicInteger
4 |
5 | /**
6 | * Helper class providing app-level unique request codes
7 | * for a round trip of the annotation processor.
8 | */
9 | class RequestCodeProvider {
10 |
11 | private val currentCode = AtomicInteger(0)
12 |
13 | /**
14 | * Obtains the next unique request code.
15 | * This method atomically increments the value
16 | * returned upon the next invocation.
17 | */
18 | fun nextRequestCode(): Int = currentCode.andIncrement
19 | }
--------------------------------------------------------------------------------
/.github/workflows/pull_request_ci.yml:
--------------------------------------------------------------------------------
1 | name: CI for pull request
2 |
3 | on:
4 | pull_request:
5 | types: [assigned, opened, synchronize, reopened]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: set up JDK 1.8
13 | uses: actions/setup-java@v1
14 | with:
15 | java-version: 1.8
16 | - name: Grant execute permission for gradlew
17 | run: chmod +x gradlew
18 | - name: Prepare local.properties for testing
19 | run: echo "sdk.dir=$ANDROID_HOME" > local.properties
20 | - name: Build with Gradle
21 | run: ./gradlew clean check --stacktrace
22 |
--------------------------------------------------------------------------------
/ktx-sample/src/main/java/permissions/dispatcher/ktx/sample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package permissions.dispatcher.ktx.sample
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 |
6 | class MainActivity : AppCompatActivity() {
7 | override fun onCreate(savedInstanceState: Bundle?) {
8 | super.onCreate(savedInstanceState)
9 | setContentView(R.layout.activity_main)
10 | if (savedInstanceState == null) {
11 | val fragment = MainFragment()
12 | supportFragmentManager.beginTransaction()
13 | .replace(R.id.container, fragment)
14 | .commitNowAllowingStateLoss()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/fragment_camera.xml:
--------------------------------------------------------------------------------
1 |
12 | * This will delegate to other methods that compose generated code.
13 | */
14 | fun createFile(rpe: RuntimePermissionsElement, requestCodeProvider: RequestCodeProvider): K
15 | }
16 |
17 | interface JavaProcessorUnit : ProcessorUnit
18 | * It works closely with our AarToJarDependencyPlugin
19 | * and consumes the resource file generated by that plugin
20 | * to inject Android-specific code into the code generation tests,
21 | * so that they will compile properly.
22 | */
23 | final class AndroidAwareClassLoader {
24 |
25 | private static final String TEST_CLASSPATH_FILE_NAME = "/additional-test-classpath.txt";
26 |
27 | private AndroidAwareClassLoader() {
28 | throw new AssertionError();
29 | }
30 |
31 | static ClassLoader create() {
32 | try {
33 | InputStream stream = AndroidAwareClassLoader.class.getResourceAsStream(TEST_CLASSPATH_FILE_NAME);
34 | URL[] urls = IOUtils.readLines(stream, Charset.forName("UTF-8"))
35 | .stream()
36 | .map(AndroidAwareClassLoader::unsafeToURL)
37 | .toArray(URL[]::new);
38 |
39 | return new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
40 |
41 | } catch (IOException e) {
42 | throw new RuntimeException(e);
43 | }
44 | }
45 |
46 | private static URL unsafeToURL(String spec) {
47 | try {
48 | return new URL(spec);
49 | } catch (MalformedURLException e) {
50 | throw new RuntimeException(e);
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/doc/special_permissions.md:
--------------------------------------------------------------------------------
1 | ## Special Permissions
2 |
3 | PermissionsDispatcher takes care of special permissions `Manifest.permission.SYSTEM_ALERT_WINDOW` and `Manifest.permission.WRITE_SETTINGS`.
4 |
5 | The following sample is to grant `SYSTEM_ALERT_WINDOW`.
6 |
7 | ### 0. Prepare AndroidManifest
8 |
9 | Add the following line to `AndroidManifest.xml`:
10 |
11 | `