├── aaper
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── likethesalad
│ │ │ └── android
│ │ │ └── aaper
│ │ │ ├── testutils
│ │ │ ├── RobolectricActivity.kt
│ │ │ └── BaseRobolectricTest.kt
│ │ │ ├── launcher
│ │ │ └── RequestWithCodeLauncherTest.kt
│ │ │ ├── base
│ │ │ ├── activity
│ │ │ │ ├── statusprovider
│ │ │ │ │ └── ActivityPermissionStatusProviderTest.kt
│ │ │ │ ├── launcher
│ │ │ │ │ └── ActivityRequestLauncherTest.kt
│ │ │ │ └── strategy
│ │ │ │ │ └── ActivityRequestStrategyTest.kt
│ │ │ ├── fragment
│ │ │ │ └── strategy
│ │ │ │ │ └── FragmentRequestStrategyTest.kt
│ │ │ └── common
│ │ │ │ └── strategy
│ │ │ │ └── AllRequestStrategyTest.kt
│ │ │ ├── data
│ │ │ └── RequestCodeLaunchMetadataTest.kt
│ │ │ ├── strategy
│ │ │ ├── DefaultRequestStrategyTest.kt
│ │ │ ├── DefaultRequestStrategyFactoryTest.kt
│ │ │ └── RequestWithCodeMetadataStrategyTest.kt
│ │ │ └── AaperTest.kt
│ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── likethesalad
│ │ │ └── android
│ │ │ └── aaper
│ │ │ ├── AaperInitializer.kt
│ │ │ ├── errors
│ │ │ ├── AaperNotInitializedException.kt
│ │ │ └── AaperInitializedAlreadyException.kt
│ │ │ ├── base
│ │ │ ├── fragment
│ │ │ │ ├── launcher
│ │ │ │ │ └── FragmentRequestLauncher.kt
│ │ │ │ ├── statusprovider
│ │ │ │ │ └── FragmentPermissionStatusProvider.kt
│ │ │ │ └── strategy
│ │ │ │ │ └── FragmentRequestStrategy.kt
│ │ │ ├── activity
│ │ │ │ ├── launcher
│ │ │ │ │ └── ActivityRequestLauncher.kt
│ │ │ │ ├── statusprovider
│ │ │ │ │ └── ActivityPermissionStatusProvider.kt
│ │ │ │ └── strategy
│ │ │ │ │ └── ActivityRequestStrategy.kt
│ │ │ └── common
│ │ │ │ └── strategy
│ │ │ │ └── AllRequestStrategy.kt
│ │ │ ├── data
│ │ │ └── RequestCodeLaunchMetadata.kt
│ │ │ ├── internal
│ │ │ └── PermissionRequestHandler.kt
│ │ │ ├── strategy
│ │ │ ├── RequestWithCodeMetadataStrategy.kt
│ │ │ ├── DefaultRequestStrategy.kt
│ │ │ └── DefaultRequestStrategyFactory.kt
│ │ │ ├── launcher
│ │ │ └── RequestWithCodeLauncher.kt
│ │ │ └── Aaper.kt
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── FUNDING.yml
├── tests.sh
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── app
├── gradle
│ ├── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ └── libs.versions.toml
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ ├── my_fragment.xml
│ │ │ │ └── activity_main.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── likethesalad
│ │ │ │ └── android
│ │ │ │ └── aaper
│ │ │ │ └── sample
│ │ │ │ ├── test
│ │ │ │ ├── utils
│ │ │ │ │ ├── CallMe.kt
│ │ │ │ │ └── CallMeWithArg.kt
│ │ │ │ ├── SimpleFragment.kt
│ │ │ │ ├── JavaActivity.java
│ │ │ │ ├── ActivityWithOverriddenResultMethod.kt
│ │ │ │ └── SimpleActivity.kt
│ │ │ │ ├── MyApp.kt
│ │ │ │ ├── MyFragment.kt
│ │ │ │ ├── custom
│ │ │ │ ├── FinishActivityOnDeniedStrategy.kt
│ │ │ │ └── AlertDialogStrategy.kt
│ │ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ └── test
│ │ └── java
│ │ └── com
│ │ └── likethesalad
│ │ └── android
│ │ └── aaper
│ │ └── sample
│ │ └── test
│ │ ├── testutils
│ │ ├── TestApplication.kt
│ │ ├── base
│ │ │ ├── BaseRobolectricTest.kt
│ │ │ └── BaseActivityTest.kt
│ │ ├── strategies
│ │ │ └── AlwaysSuccessfulStrategy.kt
│ │ └── launcher
│ │ │ └── TestRequestLauncher.kt
│ │ ├── JavaActivityTest.kt
│ │ ├── SimpleFragmentTest.kt
│ │ ├── ActivityWithOverriddenResultMethodTest.kt
│ │ └── SimpleActivityTest.kt
├── gradle.properties
├── proguard-rules.pro
├── build.gradle
├── settings.gradle
├── gradlew.bat
└── gradlew
├── .gitignore
├── aaper-api
├── build.gradle
└── src
│ ├── test
│ └── java
│ │ └── com
│ │ └── likethesalad
│ │ └── android
│ │ └── aaper
│ │ ├── testutils
│ │ └── StrategyTest.kt
│ │ ├── api
│ │ ├── strategy
│ │ │ └── RequestStrategyFactoryTest.kt
│ │ └── PermissionManagerTest.kt
│ │ └── internal
│ │ └── utils
│ │ └── RequestRunnerTest.kt
│ └── main
│ └── java
│ └── com
│ └── likethesalad
│ └── android
│ └── aaper
│ ├── api
│ ├── errors
│ │ ├── AaperException.kt
│ │ └── RequestExecutedAlreadyException.kt
│ ├── strategy
│ │ ├── RequestStrategyFactory.kt
│ │ ├── NoopRequestStrategy.kt
│ │ └── RequestStrategy.kt
│ ├── data
│ │ ├── LaunchMetadata.kt
│ │ ├── PermissionsResult.kt
│ │ └── PermissionsRequest.kt
│ ├── statusprovider
│ │ └── PermissionStatusProvider.kt
│ ├── launcher
│ │ └── RequestLauncher.kt
│ ├── EnsurePermissions.kt
│ └── PermissionManager.kt
│ └── internal
│ ├── strategy
│ └── RequestStrategyFactoryProvider.kt
│ ├── data
│ ├── CurrentRequest.kt
│ └── PendingRequest.kt
│ └── utils
│ └── RequestRunner.kt
├── aaper-plugin
├── src
│ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── likethesalad
│ │ │ └── android
│ │ │ └── aaper
│ │ │ └── plugin
│ │ │ ├── appender
│ │ │ ├── visitor
│ │ │ │ ├── data
│ │ │ │ │ ├── FieldInfo.kt
│ │ │ │ │ └── ClassName.kt
│ │ │ │ └── WraaperClassCreator.kt
│ │ │ ├── data
│ │ │ │ └── MethodInfo.kt
│ │ │ └── CodeAppenderTask.kt
│ │ │ ├── instrumentation
│ │ │ └── target
│ │ │ │ ├── visitor
│ │ │ │ ├── utils
│ │ │ │ │ └── AnnotatedMethodNotifier.kt
│ │ │ │ ├── annotations
│ │ │ │ │ ├── PermissionsAnnotationVisitor.kt
│ │ │ │ │ └── TargetAnnotationVisitor.kt
│ │ │ │ ├── ResultMethodWrapper.kt
│ │ │ │ ├── TargetClassVisitor.kt
│ │ │ │ └── TargetMethodVisitor.kt
│ │ │ │ └── TargetAsmClassVisitorFactory.kt
│ │ │ ├── utils
│ │ │ ├── AsmUtils.kt
│ │ │ └── NamingUtils.kt
│ │ │ └── AaperPlugin.kt
│ └── test
│ │ ├── kotlin
│ │ └── com
│ │ │ └── likethesalad
│ │ │ └── android
│ │ │ └── aaper
│ │ │ └── plugin
│ │ │ ├── appender
│ │ │ └── visitor
│ │ │ │ ├── testutils
│ │ │ │ └── GeneratedClassLoader.kt
│ │ │ │ ├── data
│ │ │ │ └── ClassNameTest.kt
│ │ │ │ └── WraaperClassCreatorTest.kt
│ │ │ └── AaperPluginTest.kt
│ │ └── assets
│ │ └── projects
│ │ ├── nonVoidMethod
│ │ └── main
│ │ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── SomeActivity.java
│ │ └── voidMethod
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── SomeActivity.java
└── build.gradle
├── CHANGELOG.md
├── settings.gradle
├── LICENSE.txt
├── gradle.properties
├── gradlew.bat
└── gradlew
/aaper/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/aaper/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ko_fi: likethesalad
2 |
--------------------------------------------------------------------------------
/tests.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | ./gradlew publishToMavenLocal
3 | ./gradlew test
4 | ./gradlew -p "app" testDebugUnitTest
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Annotated permission
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | local.properties
4 | .idea
5 | .DS_Store
6 | build
7 | captures
8 | .externalNativeBuild
9 | .cxx
10 |
--------------------------------------------------------------------------------
/app/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536m
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | kotlin.code.style=official
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/aaper-api/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.java.library)
3 | }
4 |
5 | dependencies {
6 | testImplementation libs.unitTesting
7 | }
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LikeTheSalad/aaper/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/java/com/likethesalad/android/aaper/sample/test/utils/CallMe.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test.utils
2 |
3 | interface CallMe {
4 | fun call()
5 | }
--------------------------------------------------------------------------------
/aaper/src/test/java/com/likethesalad/android/aaper/testutils/RobolectricActivity.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.testutils
2 |
3 | import android.app.Activity
4 |
5 | class RobolectricActivity : Activity()
--------------------------------------------------------------------------------
/app/src/main/java/com/likethesalad/android/aaper/sample/test/utils/CallMeWithArg.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test.utils
2 |
3 | interface CallMeWithArg {
4 |
5 | fun callMe(arg: T)
6 | }
--------------------------------------------------------------------------------
/aaper-api/src/test/java/com/likethesalad/android/aaper/testutils/StrategyTest.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.testutils
2 |
3 | import com.likethesalad.android.aaper.api.strategy.NoopRequestStrategy
4 |
5 | class StrategyTest : NoopRequestStrategy()
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/aaper-plugin/src/main/java/com/likethesalad/android/aaper/plugin/appender/visitor/data/FieldInfo.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.plugin.appender.visitor.data
2 |
3 | import org.objectweb.asm.Type
4 |
5 | data class FieldInfo(val name: String, val type: Type)
--------------------------------------------------------------------------------
/aaper-plugin/src/main/java/com/likethesalad/android/aaper/plugin/instrumentation/target/visitor/utils/AnnotatedMethodNotifier.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.plugin.instrumentation.target.visitor.utils
2 |
3 | interface AnnotatedMethodNotifier {
4 | fun foundAnnotatedMethod()
5 | }
--------------------------------------------------------------------------------
/app/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Dec 26 11:26:47 CET 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Dec 26 11:26:47 CET 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/api/errors/AaperException.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.api.errors
2 |
3 | /**
4 | * Base exception for all Aaper operations' exceptions.
5 | */
6 | open class AaperException(message: String?, cause: Throwable?) : Exception(message, cause)
--------------------------------------------------------------------------------
/aaper/src/test/java/com/likethesalad/android/aaper/testutils/BaseRobolectricTest.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.testutils
2 |
3 | import org.junit.runner.RunWith
4 | import org.robolectric.RobolectricTestRunner
5 |
6 | @RunWith(RobolectricTestRunner::class)
7 | abstract class BaseRobolectricTest {
8 | }
--------------------------------------------------------------------------------
/aaper-plugin/src/main/java/com/likethesalad/android/aaper/plugin/appender/data/MethodInfo.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.plugin.appender.data
2 |
3 | import com.likethesalad.android.aaper.plugin.appender.visitor.data.ClassName
4 |
5 | data class MethodInfo(val name: String, val descriptor: String, val className: ClassName)
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/api/strategy/RequestStrategyFactory.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.api.strategy
2 |
3 | /**
4 | * Provides instances of [RequestStrategy].
5 | */
6 | interface RequestStrategyFactory {
7 |
8 | fun > getStrategy(host: Any, type: Class): T
9 | }
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/internal/strategy/RequestStrategyFactoryProvider.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.internal.strategy
2 |
3 | import com.likethesalad.android.aaper.api.strategy.RequestStrategyFactory
4 |
5 | interface RequestStrategyFactoryProvider {
6 |
7 | fun getRequestStrategyFactory(): T
8 | }
--------------------------------------------------------------------------------
/aaper-plugin/src/main/java/com/likethesalad/android/aaper/plugin/utils/AsmUtils.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.plugin.utils
2 |
3 | import org.objectweb.asm.Type
4 |
5 | object AsmUtils {
6 |
7 | fun getCombinedSize(types: List): Int {
8 | var size = 0
9 |
10 | types.forEach {
11 | size += it.size
12 | }
13 |
14 | return size
15 | }
16 | }
--------------------------------------------------------------------------------
/aaper-plugin/src/test/kotlin/com/likethesalad/android/aaper/plugin/appender/visitor/testutils/GeneratedClassLoader.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.plugin.appender.visitor.testutils
2 |
3 | class GeneratedClassLoader(parent: ClassLoader?) : ClassLoader(parent) {
4 |
5 | fun defineClass(name: String, bytes: ByteArray): Class<*> {
6 | return defineClass(name, bytes, 0, bytes.size)
7 | }
8 | }
--------------------------------------------------------------------------------
/aaper-plugin/src/test/assets/projects/nonVoidMethod/main/java/com/example/SomeActivity.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import android.Manifest;
4 | import com.likethesalad.android.aaper.api.EnsurePermissions;
5 |
6 | public class SomeActivity extends android.app.Activity {
7 |
8 | @EnsurePermissions(permissions = {Manifest.permission.CAMERA})
9 | public int someMethod() {
10 | return 1;
11 | }
12 | }
--------------------------------------------------------------------------------
/app/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | kotlin = "1.6.20"
3 | android = "7.4.0"
4 |
5 | [libraries]
6 | androidx-appCompat = "androidx.appcompat:appcompat:1.6.1"
7 | fragmentTesting = "androidx.fragment:fragment-testing:1.5.5"
8 |
9 | [plugins]
10 | android-application = { id = "com.android.application", version.ref = "android" }
11 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/likethesalad/android/aaper/sample/MyApp.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample
2 |
3 | import android.app.Application
4 |
5 | /**
6 | * Created by César Muñoz on 03/08/20.
7 | */
8 | class MyApp : Application() {
9 |
10 | override fun onCreate() {
11 | super.onCreate()
12 |
13 | // Aaper.setDefaultStrategy(FinishActivityOnDeniedStrategy::class.java)
14 | }
15 | }
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/api/errors/RequestExecutedAlreadyException.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.api.errors
2 |
3 | /**
4 | * Exception thrown when a request was previously launched and there was an attempt
5 | * to launch it again.
6 | */
7 | class RequestExecutedAlreadyException(val permissions: List) :
8 | AaperException("Request for permissions $permissions has been executed already", null)
--------------------------------------------------------------------------------
/aaper-plugin/src/test/assets/projects/voidMethod/main/java/com/example/SomeActivity.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import android.Manifest;
4 | import com.likethesalad.android.aaper.api.EnsurePermissions;
5 |
6 | public class SomeActivity extends android.app.Activity {
7 |
8 | @EnsurePermissions(permissions = {Manifest.permission.CAMERA})
9 | public void someMethod() {
10 | System.out.println("Permissions granted.");
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/aaper-plugin/src/main/java/com/likethesalad/android/aaper/plugin/utils/NamingUtils.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.plugin.utils
2 |
3 | object NamingUtils {
4 |
5 | fun getGeneratedClassSimpleName(methodClassSimpleName: String, methodName: String): String {
6 | return "Aaper_${methodClassSimpleName}_$methodName"
7 | }
8 |
9 | fun getWraapMethodName(methodName: String): String {
10 | return "wraaper_${methodName}"
11 | }
12 | }
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/AaperInitializer.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper
2 |
3 | import android.content.Context
4 | import androidx.startup.Initializer
5 |
6 | class AaperInitializer : Initializer {
7 |
8 | override fun create(context: Context) {
9 | Aaper.initialize(context)
10 | }
11 |
12 | override fun dependencies(): List>> {
13 | return emptyList()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/internal/data/CurrentRequest.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.internal.data
2 |
3 | import com.likethesalad.android.aaper.api.strategy.RequestStrategy
4 | import com.likethesalad.android.aaper.api.data.PermissionsRequest
5 |
6 | data class CurrentRequest(
7 | val host: Any,
8 | val data: PermissionsRequest,
9 | val strategy: RequestStrategy,
10 | internal val originalMethod: Runnable
11 | )
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/internal/data/PendingRequest.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.internal.data
2 |
3 | import com.likethesalad.android.aaper.api.strategy.RequestStrategy
4 | import com.likethesalad.android.aaper.api.data.PermissionsRequest
5 |
6 | data class PendingRequest(
7 | val host: Any,
8 | val data: PermissionsRequest,
9 | val strategy: RequestStrategy,
10 | internal val originalMethod: Runnable
11 | )
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/api/data/LaunchMetadata.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.api.data
2 |
3 | /**
4 | * It represents any sort of data that might be needed for a permission request. For example,
5 | * it can hold a requestCode for common permission requests.
6 | */
7 | interface LaunchMetadata {
8 |
9 | /**
10 | * Checks if another [LaunchMetadata] is equal to self.
11 | */
12 | fun isEqualTo(other: LaunchMetadata?): Boolean
13 | }
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/errors/AaperNotInitializedException.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.errors
2 |
3 | import com.likethesalad.android.aaper.api.errors.AaperException
4 |
5 | /**
6 | * This exception is thrown when an attempt to access Aaper is made before initializing it.
7 | */
8 | class AaperNotInitializedException
9 | : AaperException(
10 | "Aaper is not initialized. You must call Aaper.initialize before performing this operation",
11 | null
12 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/likethesalad/android/aaper/sample/test/SimpleFragment.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test
2 |
3 | import android.Manifest
4 | import androidx.fragment.app.Fragment
5 | import com.likethesalad.android.aaper.api.EnsurePermissions
6 | import com.likethesalad.android.aaper.sample.test.utils.CallMe
7 |
8 | class SimpleFragment : Fragment() {
9 |
10 | @EnsurePermissions(permissions = [Manifest.permission.CAMERA])
11 | fun someMethod(callMe: CallMe) {
12 | callMe.call()
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/my_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/api/data/PermissionsResult.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.api.data
2 |
3 | /**
4 | * This data class holds the result of a request as well as the request data.
5 | *
6 | * @param request - The request data.
7 | * @param granted - The list of permissions granted, if any.
8 | * @param denied - The list of the permissions denied, if any.
9 | */
10 | data class PermissionsResult(
11 | val request: PermissionsRequest,
12 | val granted: List,
13 | val denied: List
14 | )
--------------------------------------------------------------------------------
/app/src/test/java/com/likethesalad/android/aaper/sample/test/testutils/TestApplication.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test.testutils
2 |
3 | import android.app.Application
4 | import com.likethesalad.android.aaper.Aaper
5 | import com.likethesalad.android.aaper.sample.test.testutils.strategies.AlwaysSuccessfulStrategy
6 |
7 | class TestApplication : Application() {
8 |
9 | override fun onCreate() {
10 | super.onCreate()
11 | Aaper.initialize(this)
12 | Aaper.setDefaultStrategy(AlwaysSuccessfulStrategy::class.java)
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/likethesalad/android/aaper/sample/test/JavaActivity.java:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test;
2 |
3 | import android.Manifest;
4 | import androidx.appcompat.app.AppCompatActivity;
5 | import com.likethesalad.android.aaper.api.EnsurePermissions;
6 | import com.likethesalad.android.aaper.sample.test.utils.CallMe;
7 |
8 | public class JavaActivity extends AppCompatActivity {
9 |
10 | @EnsurePermissions(permissions = {Manifest.permission.CAMERA})
11 | public void someMethod(CallMe callMe) {
12 | callMe.call();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/api/data/PermissionsRequest.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.api.data
2 |
3 | import com.likethesalad.android.aaper.api.EnsurePermissions
4 |
5 | /**
6 | * This data class holds the request parameters.
7 | *
8 | * @param permissions - The permissions provided in the [EnsurePermissions] annotation.
9 | * @param missingPermissions - The missing permissions taken from the [permissions] provided.
10 | */
11 | data class PermissionsRequest(
12 | val permissions: List,
13 | val missingPermissions: List
14 | )
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/errors/AaperInitializedAlreadyException.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.errors
2 |
3 | import com.likethesalad.android.aaper.Aaper
4 | import com.likethesalad.android.aaper.api.errors.AaperException
5 |
6 | /**
7 | * This exception is thrown whenever [Aaper.initialize] is called more than once.
8 | */
9 | class AaperInitializedAlreadyException
10 | : AaperException(
11 | "Aaper can only get initialized once, please considering doing so in your " +
12 | "Application's onCreate in order to ensure it won't get re-initialized",
13 | null
14 | )
--------------------------------------------------------------------------------
/aaper-plugin/src/main/java/com/likethesalad/android/aaper/plugin/instrumentation/target/visitor/annotations/PermissionsAnnotationVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.plugin.instrumentation.target.visitor.annotations
2 |
3 | import org.objectweb.asm.AnnotationVisitor
4 | import org.objectweb.asm.Opcodes
5 |
6 | class PermissionsAnnotationVisitor(annotationVisitor: AnnotationVisitor) :
7 | AnnotationVisitor(Opcodes.ASM9, annotationVisitor) {
8 | val permissions = mutableListOf()
9 |
10 | override fun visit(name: String?, value: Any) {
11 | super.visit(name, value)
12 | permissions.add(value.toString())
13 | }
14 | }
--------------------------------------------------------------------------------
/aaper/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
11 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/test/java/com/likethesalad/android/aaper/sample/test/JavaActivityTest.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test
2 |
3 | import com.likethesalad.android.aaper.sample.test.testutils.base.BaseActivityTest
4 | import org.junit.Test
5 |
6 | class JavaActivityTest : BaseActivityTest() {
7 |
8 | @Test
9 | fun `Check it instruments java classes`() {
10 | run { activity ->
11 | val callMe = createCallMeMock()
12 |
13 | activity.someMethod(callMe)
14 |
15 | verifyCalled(callMe)
16 | }
17 | }
18 |
19 | override fun getActivityClass(): Class {
20 | return JavaActivity::class.java
21 | }
22 | }
--------------------------------------------------------------------------------
/aaper-plugin/src/main/java/com/likethesalad/android/aaper/plugin/appender/visitor/data/ClassName.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.plugin.appender.visitor.data
2 |
3 | data class ClassName(val packageName: String, val simpleName: String) {
4 | val internalName by lazy {
5 | if (packageName.isEmpty()) {
6 | simpleName
7 | } else {
8 | packageName.replace(".", "/") + "/" + simpleName
9 | }
10 | }
11 | val fullName by lazy {
12 | if (packageName.isEmpty()) {
13 | simpleName
14 | } else {
15 | "$packageName.$simpleName"
16 | }
17 | }
18 | val fileName by lazy {
19 | "$internalName.class"
20 | }
21 | }
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/base/fragment/launcher/FragmentRequestLauncher.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.base.fragment.launcher
2 |
3 | import androidx.fragment.app.Fragment
4 | import com.likethesalad.android.aaper.launcher.RequestWithCodeLauncher
5 |
6 | /**
7 | * This class launches a request permission using a Fragment as host.
8 | */
9 | @Suppress("DEPRECATION")
10 | class FragmentRequestLauncher : RequestWithCodeLauncher() {
11 |
12 | override fun launchPermissionsRequest(
13 | host: Fragment,
14 | permissions: List,
15 | requestCode: Int
16 | ) {
17 | host.requestPermissions(permissions.toTypedArray(), requestCode)
18 | }
19 | }
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/internal/utils/RequestRunner.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.internal.utils
2 |
3 | import com.likethesalad.android.aaper.api.errors.RequestExecutedAlreadyException
4 | import com.likethesalad.android.aaper.internal.data.PendingRequest
5 |
6 | class RequestRunner(
7 | private val pendingRequest: PendingRequest,
8 | private val f: (PendingRequest) -> Unit
9 | ) : Runnable {
10 |
11 | private var executed = false
12 |
13 | override fun run() {
14 | if (executed) {
15 | throw RequestExecutedAlreadyException(pendingRequest.data.permissions)
16 | }
17 |
18 | executed = true
19 | f.invoke(pendingRequest)
20 | }
21 | }
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/base/activity/launcher/ActivityRequestLauncher.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.base.activity.launcher
2 |
3 | import android.app.Activity
4 | import androidx.core.app.ActivityCompat
5 | import com.likethesalad.android.aaper.launcher.RequestWithCodeLauncher
6 |
7 | /**
8 | * This class launches a request permission using an Activity as host.
9 | */
10 | class ActivityRequestLauncher : RequestWithCodeLauncher() {
11 |
12 | override fun launchPermissionsRequest(
13 | host: Activity,
14 | permissions: List,
15 | requestCode: Int
16 | ) {
17 | ActivityCompat.requestPermissions(host, permissions.toTypedArray(), requestCode)
18 | }
19 | }
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/data/RequestCodeLaunchMetadata.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.data
2 |
3 | import com.likethesalad.android.aaper.api.data.LaunchMetadata
4 |
5 | /**
6 | * This is the [LaunchMetadata] used for common android permissions requests in which a code
7 | * must be provided as an identifier of the request.
8 | */
9 | class RequestCodeLaunchMetadata(val code: Int) : LaunchMetadata {
10 |
11 | override fun isEqualTo(other: LaunchMetadata?): Boolean {
12 | if (other == null) {
13 | return false
14 | }
15 |
16 | if (other !is RequestCodeLaunchMetadata) {
17 | return false
18 | }
19 |
20 | return other.code == code
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/likethesalad/android/aaper/sample/test/testutils/base/BaseRobolectricTest.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test.testutils.base
2 |
3 | import com.likethesalad.android.aaper.Aaper
4 | import com.likethesalad.android.aaper.api.PermissionManager
5 | import com.likethesalad.android.aaper.sample.test.testutils.TestApplication
6 | import org.junit.After
7 | import org.junit.runner.RunWith
8 | import org.robolectric.RobolectricTestRunner
9 | import org.robolectric.annotation.Config
10 |
11 | @RunWith(RobolectricTestRunner::class)
12 | @Config(application = TestApplication::class)
13 | abstract class BaseRobolectricTest {
14 | @After
15 | fun baseTearDown() {
16 | PermissionManager.resetForTest()
17 | Aaper.resetForTest()
18 | }
19 | }
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/internal/PermissionRequestHandler.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.internal
2 |
3 | import com.likethesalad.android.aaper.Aaper
4 | import com.likethesalad.android.aaper.api.PermissionManager
5 | import com.likethesalad.android.aaper.api.strategy.RequestStrategy
6 |
7 | object PermissionRequestHandler {
8 |
9 | @JvmStatic
10 | fun processPermissionRequest(
11 | host: Any,
12 | originalMethod: Runnable,
13 | permissions: Array,
14 | strategyType: Class>?
15 | ) {
16 | PermissionManager.processPermissionRequest(
17 | host,
18 | originalMethod,
19 | permissions,
20 | strategyType ?: Aaper.getDefaultStrategy()
21 | )
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/likethesalad/android/aaper/sample/test/testutils/strategies/AlwaysSuccessfulStrategy.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test.testutils.strategies
2 |
3 | import com.likethesalad.android.aaper.api.data.PermissionsResult
4 | import com.likethesalad.android.aaper.api.launcher.RequestLauncher
5 | import com.likethesalad.android.aaper.base.common.strategy.AllRequestStrategy
6 | import com.likethesalad.android.aaper.sample.test.testutils.launcher.TestRequestLauncher
7 |
8 | class AlwaysSuccessfulStrategy : AllRequestStrategy() {
9 |
10 | override fun onPermissionsRequestResults(host: Any, data: PermissionsResult): Boolean {
11 | return true
12 | }
13 |
14 | override fun getRequestLauncher(host: Any): RequestLauncher {
15 | return TestRequestLauncher()
16 | }
17 | }
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/base/activity/statusprovider/ActivityPermissionStatusProvider.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.base.activity.statusprovider
2 |
3 | import android.app.Activity
4 | import android.content.pm.PackageManager
5 | import androidx.core.content.ContextCompat
6 | import com.likethesalad.android.aaper.api.statusprovider.PermissionStatusProvider
7 |
8 | /**
9 | * This class queries a permission granted status using Activity as host.
10 | */
11 | class ActivityPermissionStatusProvider : PermissionStatusProvider() {
12 |
13 | override fun isPermissionGranted(host: Activity, permissionName: String): Boolean {
14 | return ContextCompat.checkSelfPermission(
15 | host,
16 | permissionName
17 | ) == PackageManager.PERMISSION_GRANTED
18 | }
19 | }
--------------------------------------------------------------------------------
/aaper/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/base/fragment/statusprovider/FragmentPermissionStatusProvider.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.base.fragment.statusprovider
2 |
3 | import android.content.pm.PackageManager
4 | import androidx.core.content.ContextCompat
5 | import androidx.fragment.app.Fragment
6 | import com.likethesalad.android.aaper.api.statusprovider.PermissionStatusProvider
7 |
8 | /**
9 | * This class queries a permission granted status using Fragment as host.
10 | */
11 | class FragmentPermissionStatusProvider : PermissionStatusProvider() {
12 |
13 | override fun isPermissionGranted(host: Fragment, permissionName: String): Boolean {
14 | return ContextCompat.checkSelfPermission(
15 | host.requireContext(),
16 | permissionName
17 | ) == PackageManager.PERMISSION_GRANTED
18 | }
19 | }
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/api/statusprovider/PermissionStatusProvider.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.api.statusprovider
2 |
3 | /**
4 | * This class should tell whether a given permission is granted not.
5 | */
6 | abstract class PermissionStatusProvider {
7 |
8 | @Suppress("UNCHECKED_CAST")
9 | internal fun internalIsPermissionGranted(host: Any, permissionName: String): Boolean {
10 | return isPermissionGranted(host as T, permissionName)
11 | }
12 |
13 | /**
14 | * @param host - The class that contains the original method, e.g. Activity or Fragment.
15 | * @param permissionName - The permission being queried.
16 | *
17 | * @return - TRUE if the [permissionName] is granted, FALSE otherwise.
18 | */
19 | abstract fun isPermissionGranted(host: T, permissionName: String): Boolean
20 | }
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/strategy/RequestWithCodeMetadataStrategy.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.strategy
2 |
3 | import com.likethesalad.android.aaper.api.data.LaunchMetadata
4 | import com.likethesalad.android.aaper.api.strategy.RequestStrategy
5 | import com.likethesalad.android.aaper.data.RequestCodeLaunchMetadata
6 |
7 | /**
8 | * Base [RequestStrategy] for common Android component requests in which a numeric code
9 | * must be provided as identifier of the request.
10 | */
11 | abstract class RequestWithCodeMetadataStrategy : RequestStrategy() {
12 |
13 | companion object {
14 | private const val DEFAULT_REQUEST_CODE = 21293
15 | }
16 |
17 | override fun getLaunchMetadata(host: T): LaunchMetadata? {
18 | return RequestCodeLaunchMetadata(getRequestCode())
19 | }
20 |
21 | open fun getRequestCode(): Int {
22 | return DEFAULT_REQUEST_CODE
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/likethesalad/android/aaper/sample/test/SimpleFragmentTest.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test
2 |
3 | import androidx.fragment.app.testing.FragmentScenario
4 | import com.likethesalad.android.aaper.sample.test.testutils.base.BaseRobolectricTest
5 | import com.likethesalad.android.aaper.sample.test.utils.CallMe
6 | import io.mockk.mockk
7 | import io.mockk.verify
8 | import org.junit.Test
9 |
10 | class SimpleFragmentTest : BaseRobolectricTest() {
11 |
12 | @Test
13 | fun `Check permission granted for fragment method`() {
14 | FragmentScenario.launch(SimpleFragment::class.java).use {
15 | it.onFragment { fragment ->
16 | val callMe = mockk(relaxUnitFun = true)
17 |
18 | fragment.someMethod(callMe)
19 |
20 | verify {
21 | callMe.call()
22 | }
23 | }
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | Version 3.0.0 *(02-01-2024)*
5 | ---
6 |
7 | * Removed annotation processor module.
8 | * Creating wraapers using the AGP forScope transformation API.
9 | * BREAKING - Simplified Aaper's API by:
10 | * Allowing to pass strategies to the EnsurePermissions annotations as class types rather than
11 | with a name, hence removing the need to name every new strategy.
12 | * Avoiding having to register new strategies before using them (they will be automatically
13 | instantiated on demand).
14 | * Providing the `RequestStrategyFactory` interface to allow for custom instantiation of
15 | strategies if needed.
16 |
17 | Version 2.1.0 *(05-02-2023)*
18 | ---
19 |
20 | * Using androidx.startup to automatically initialize Aaper with its default config (thanks
21 | @msasikanth)
22 |
23 | Version 2.0.0 *(04-02-2023)*
24 | ---
25 |
26 | * Using a combination of annotation processor + the new AGP Instrumentation API
--------------------------------------------------------------------------------
/app/src/test/java/com/likethesalad/android/aaper/sample/test/ActivityWithOverriddenResultMethodTest.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test
2 |
3 | import com.likethesalad.android.aaper.sample.test.testutils.base.BaseActivityTest
4 | import org.junit.Test
5 |
6 | class ActivityWithOverriddenResultMethodTest :
7 | BaseActivityTest() {
8 |
9 | @Test
10 | fun `Check original result and generated code are called`() {
11 | run { activity ->
12 | val callMe = createCallMeMock()
13 | val callOnResult = createCallMeMock()
14 | activity.callOnResult = callOnResult
15 |
16 | activity.testCaller(callMe)
17 |
18 | verifyCalled(callMe)
19 | verifyCalled(callOnResult)
20 | }
21 | }
22 |
23 | override fun getActivityClass(): Class {
24 | return ActivityWithOverriddenResultMethod::class.java
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/likethesalad/android/aaper/sample/test/ActivityWithOverriddenResultMethod.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample.test
2 |
3 | import android.Manifest
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.likethesalad.android.aaper.api.EnsurePermissions
6 | import com.likethesalad.android.aaper.sample.test.utils.CallMe
7 |
8 | class ActivityWithOverriddenResultMethod : AppCompatActivity() {
9 | var callOnResult: CallMe? = null
10 |
11 | override fun onRequestPermissionsResult(
12 | requestCode: Int,
13 | permissions: Array,
14 | grantResults: IntArray
15 | ) {
16 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
17 | callOnResult?.call()
18 | }
19 |
20 | @EnsurePermissions(permissions = [Manifest.permission.CAMERA])
21 | private fun someMethod(callMe: CallMe) {
22 | callMe.call()
23 | }
24 |
25 | fun testCaller(callMe: CallMe) {
26 | someMethod(callMe)
27 | }
28 | }
--------------------------------------------------------------------------------
/aaper/src/main/java/com/likethesalad/android/aaper/strategy/DefaultRequestStrategy.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.strategy
2 |
3 | import com.likethesalad.android.aaper.api.data.PermissionsResult
4 | import com.likethesalad.android.aaper.base.common.strategy.AllRequestStrategy
5 |
6 | /**
7 | * This is the default strategy to handle both Activity and Fragment's permissions requests.
8 | * It extends from [AllRequestStrategy] so that it can handle both types of hosts. Regarding
9 | * the response handling, it simply checks the denied permissions list, if it's empty, it returns
10 | * true so that the annotated method gets called, false otherwise (avoiding the annotated method
11 | * to get called).
12 | */
13 | class DefaultRequestStrategy : AllRequestStrategy() {
14 |
15 | override fun onPermissionsRequestResults(host: Any, data: PermissionsResult): Boolean {
16 | // If no permissions were denied, return true so that the annotated method
17 | // gets called.
18 | return data.denied.isEmpty()
19 | }
20 | }
--------------------------------------------------------------------------------
/aaper/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | }
4 |
5 | android {
6 | compileSdk 31
7 |
8 | testOptions {
9 | unitTests {
10 | includeAndroidResources = true
11 | }
12 | }
13 |
14 | defaultConfig {
15 | minSdkVersion 14
16 | targetSdkVersion 31
17 | versionCode 1
18 | versionName "1.0"
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | consumerProguardFiles 'consumer-rules.pro'
22 | }
23 |
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 |
31 | }
32 |
33 | dependencies {
34 | api project(":aaper-api")
35 | api libs.androidx.startupRuntime
36 | implementation libs.androidx.core
37 | implementation libs.androidx.fragment
38 | testImplementation libs.unitTesting
39 | testImplementation libs.robolectric
40 | }
41 |
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/api/launcher/RequestLauncher.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.api.launcher
2 |
3 | import com.likethesalad.android.aaper.api.data.LaunchMetadata
4 |
5 | /**
6 | * This class should launch a permission request.
7 | */
8 | abstract class RequestLauncher {
9 |
10 | @Suppress("UNCHECKED_CAST")
11 | internal fun internalLaunchPermissionsRequest(
12 | host: Any,
13 | permissions: List,
14 | launchMetadata: LaunchMetadata?
15 | ) {
16 | launchPermissionsRequest(host as T, permissions, launchMetadata)
17 | }
18 |
19 | /**
20 | * @param host - The class that contains the original method, e.g. Activity or Fragment.
21 | * @param permissions - The permissions being requested.
22 | * @param launchMetadata - The metadata needed, if any, to launch the request.
23 | */
24 | abstract fun launchPermissionsRequest(
25 | host: T,
26 | permissions: List,
27 | launchMetadata: LaunchMetadata?
28 | )
29 | }
--------------------------------------------------------------------------------
/aaper-api/src/main/java/com/likethesalad/android/aaper/api/EnsurePermissions.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.api
2 |
3 | import com.likethesalad.android.aaper.api.strategy.NoopRequestStrategy
4 | import com.likethesalad.android.aaper.api.strategy.RequestStrategy
5 | import kotlin.reflect.KClass
6 |
7 | /**
8 | * This annotation is for the methods inside supported hosts (either an Activity or a Fragment)
9 | * that will be handled by Aaper in order to request (if needed) their required permissions.
10 | *
11 | * @param permissions - The array of permission that the annotated function
12 | * needs in order to be run.
13 | *
14 | * @param strategy - (Optional) The type of the [RequestStrategy] that will take care of
15 | * handling the permissions request. A default strategy will be used if not provided.
16 | */
17 |
18 | @Retention(AnnotationRetention.BINARY)
19 | @Target(AnnotationTarget.FUNCTION)
20 | annotation class EnsurePermissions(
21 | val permissions: Array,
22 | val strategy: KClass> = NoopRequestStrategy::class
23 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/likethesalad/android/aaper/sample/MyFragment.kt:
--------------------------------------------------------------------------------
1 | package com.likethesalad.android.aaper.sample
2 |
3 | import android.Manifest
4 | import android.os.Bundle
5 | import android.view.View
6 | import android.widget.Button
7 | import android.widget.Toast
8 | import androidx.fragment.app.Fragment
9 | import com.likethesalad.android.aaper.api.EnsurePermissions
10 |
11 | /**
12 | * Created by César Muñoz on 18/08/20.
13 | */
14 | class MyFragment : Fragment(R.layout.my_fragment) {
15 |
16 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
17 | super.onViewCreated(view, savedInstanceState)
18 | view.findViewById