├── .gitignore
├── transformer-app
├── app
│ ├── .gitignore
│ ├── 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
│ │ │ │ └── activity_main.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── com
│ │ │ └── hyperaware
│ │ │ └── transformers
│ │ │ └── MainActivity.kt
│ ├── proguard-rules.pro
│ └── build.gradle.kts
├── settings.gradle
├── .gitignore
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── build.gradle.kts
├── gradle.properties
├── gradlew.bat
└── gradlew
├── transformer-libs
├── transformer-module
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── com
│ │ │ └── hyperaware
│ │ │ └── transformer
│ │ │ └── module
│ │ │ ├── InstrumentedURLConnection.kt
│ │ │ └── UrlConnectionInstrumentation.kt
│ └── build.gradle.kts
├── transformer-plugin
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ ├── resources
│ │ │ └── META-INF
│ │ │ │ └── gradle-plugins
│ │ │ │ └── com.hyperaware.transformer.plugin.properties
│ │ │ └── java
│ │ │ └── com
│ │ │ └── hyperaware
│ │ │ └── transformer
│ │ │ └── plugin
│ │ │ ├── MyExtension.kt
│ │ │ ├── InstrumentationConfig.kt
│ │ │ ├── TransformConfig.kt
│ │ │ ├── MyPlugin.kt
│ │ │ ├── asm
│ │ │ ├── ClassInstrumenter.kt
│ │ │ └── InstrumentationVisitor.kt
│ │ │ ├── MyTransform.kt
│ │ │ └── MyTransformImpl.kt
│ └── build.gradle.kts
├── buildSrc
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ └── ProjectConfig.kt
├── .gitignore
├── settings.gradle.kts
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── local.properties
├── build.gradle.kts
├── gradle.properties
└── gradlew
├── CONTRIBUTING.md
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/transformer-app/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/transformer-app/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-module/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/transformer-libs/buildSrc/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /.gradle
3 |
--------------------------------------------------------------------------------
/transformer-libs/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /.idea
3 | /.gradle
4 | *.iml
5 |
--------------------------------------------------------------------------------
/transformer-libs/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include(
2 | ":transformer-plugin",
3 | ":transformer-module"
4 | )
5 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Transformers
3 |
4 |
--------------------------------------------------------------------------------
/transformer-app/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/transformer-libs/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | repositories {
6 | mavenCentral()
7 | jcenter()
8 | }
9 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-module/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | transformer-module
3 |
4 |
--------------------------------------------------------------------------------
/transformer-app/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/transformer-libs/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-libs/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/src/main/resources/META-INF/gradle-plugins/com.hyperaware.transformer.plugin.properties:
--------------------------------------------------------------------------------
1 | implementation-class=com.hyperaware.transformer.plugin.MyPlugin
2 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-module/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingDoug/android-transformers/HEAD/transformer-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/transformer-app/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/transformer-libs/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/transformer-libs/local.properties:
--------------------------------------------------------------------------------
1 | ## This file must *NOT* be checked into Version Control Systems,
2 | # as it contains information specific to your local configuration.
3 | #
4 | # Location of the SDK. This is only used by Gradle.
5 | # For customization when using a Version Control System, please read the
6 | # header note.
7 | #Fri Jan 18 15:36:01 PST 2019
8 | sdk.dir=/Users/dougstevenson/Library/Android/sdk
9 |
--------------------------------------------------------------------------------
/transformer-libs/buildSrc/src/main/kotlin/ProjectConfig.kt:
--------------------------------------------------------------------------------
1 | object ProjectConfig {
2 |
3 | object Versions {
4 | const val kotlin = "1.4.30"
5 | // 4.1.0 currently won't work in IntelliJ 2020.3.4
6 | const val androidPlugin = "4.0.2"
7 | }
8 |
9 | object Maven {
10 | const val group = "com.hyperaware.transformer"
11 | const val version = "0.1"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/transformer-app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | jcenter()
6 | mavenLocal()
7 | }
8 | dependencies {
9 | classpath("com.android.tools.build:gradle:4.1.2")
10 | classpath(kotlin("gradle-plugin", version = "1.4.30"))
11 | classpath("com.hyperaware.transformer:transformer-plugin:0.1")
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | mavenCentral()
19 | jcenter()
20 | mavenLocal()
21 | }
22 | }
23 |
24 | tasks.register("clean", Delete::class) {
25 | delete(rootProject.buildDir)
26 | }
27 |
--------------------------------------------------------------------------------
/transformer-libs/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath(group = "com.android.tools.build", name = "gradle", version = ProjectConfig.Versions.androidPlugin)
9 | classpath(group = "com.github.dcendents", name = "android-maven-gradle-plugin", version = "2.1")
10 | classpath(kotlin("gradle-plugin", version = ProjectConfig.Versions.kotlin))
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | jcenter()
19 | }
20 |
21 | group = ProjectConfig.Maven.group
22 | version = ProjectConfig.Maven.version
23 | }
24 |
25 | tasks.register("clean", Delete::class) {
26 | delete(rootProject.buildDir)
27 | }
28 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2 |
3 | plugins {
4 | kotlin("jvm")
5 | id("maven")
6 | }
7 |
8 | repositories {
9 | google()
10 | mavenCentral()
11 | jcenter()
12 | }
13 |
14 | dependencies {
15 | compileOnly(gradleApi())
16 | compileOnly(group = "com.android.tools.build", name = "gradle", version = ProjectConfig.Versions.androidPlugin)
17 |
18 | implementation(kotlin("stdlib"))
19 |
20 | implementation(group = "commons-io", name = "commons-io", version = "2.8.0")
21 | implementation(group = "org.ow2.asm", name = "asm", version = "9.1")
22 | }
23 |
24 | listOf("compileKotlin", "compileTestKotlin").forEach {
25 | tasks.getByName(it) {
26 | kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/transformer-app/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 |
--------------------------------------------------------------------------------
/transformer-libs/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # Kotlin code style for this project: "official" or "obsolete":
15 | kotlin.code.style=official
16 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/src/main/java/com/hyperaware/transformer/plugin/MyExtension.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformer.plugin
18 |
19 | open class MyExtension {
20 | var logVisits = false
21 | var logInstrumentation = false
22 | }
23 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/transformer-app/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # Kotlin code style for this project: "official" or "obsolete":
15 | kotlin.code.style=official
16 |
17 | android.useAndroidX=true
18 | android.enableJetifier=true
19 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/src/main/java/com/hyperaware/transformer/plugin/InstrumentationConfig.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformer.plugin
18 |
19 | import java.net.URL
20 |
21 | data class InstrumentationConfig(
22 | val runtimeClasspath: List,
23 | val logVisits: Boolean,
24 | val logInstrumentation: Boolean
25 | )
26 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-module/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.config.KotlinCompilerVersion
2 |
3 | plugins {
4 | id("com.android.library")
5 | id("com.github.dcendents.android-maven")
6 | kotlin("android")
7 | }
8 |
9 | android {
10 | compileSdkVersion(30)
11 |
12 | defaultConfig {
13 | minSdkVersion(16)
14 | targetSdkVersion(30)
15 | versionCode = 1
16 | versionName = "1.0"
17 | testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | getByName("release") {
22 | isMinifyEnabled = false
23 | }
24 | }
25 |
26 | compileOptions {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 | }
30 |
31 | kotlinOptions {
32 | jvmTarget = JavaVersion.VERSION_1_8.toString()
33 | }
34 | }
35 |
36 | dependencies {
37 | implementation(kotlin("stdlib", KotlinCompilerVersion.VERSION))
38 | }
39 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/src/main/java/com/hyperaware/transformer/plugin/TransformConfig.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformer.plugin
18 |
19 | import com.android.build.api.transform.TransformInvocation
20 | import java.io.File
21 |
22 | data class TransformConfig(
23 | val transformInvocation: TransformInvocation,
24 | val androidClasspath: List,
25 | val ignorePaths: List,
26 | val pluginConfig: MyExtension
27 | )
28 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google.com/conduct/).
29 |
--------------------------------------------------------------------------------
/transformer-app/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | kotlin("android")
4 | kotlin("android.extensions")
5 | id("com.hyperaware.transformer.plugin")
6 | }
7 |
8 | android {
9 | compileSdkVersion(30)
10 |
11 | defaultConfig {
12 | applicationId = "com.hyperaware.transformers"
13 | minSdkVersion(16)
14 | targetSdkVersion(30)
15 | versionCode = 1
16 | versionName = "1.0"
17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | buildTypes {
22 | getByName("release") {
23 | isMinifyEnabled = false
24 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
25 | }
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility = JavaVersion.VERSION_1_8
30 | targetCompatibility = JavaVersion.VERSION_1_8
31 | }
32 |
33 | kotlinOptions {
34 | jvmTarget = JavaVersion.VERSION_1_8.toString()
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation(kotlin("stdlib"))
40 |
41 | implementation(group = "androidx.appcompat", name = "appcompat", version = "1.2.0")
42 | implementation(group = "androidx.constraintlayout", name = "constraintlayout", version = "2.0.4")
43 |
44 | implementation(group = "com.hyperaware.transformer", name = "transformer-module", version = "0.1")
45 |
46 | implementation(group = "commons-io", name = "commons-io", version = "2.8.0")
47 | }
48 |
49 | // The MyExtension extension added by the transform can be configured here.
50 | //
51 | //transform {
52 | // logVisits = true
53 | // logInstrumentation = true
54 | //}
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android Transform API Demo
2 |
3 | This is sample code for my talk "(Android) Transformers - bytecode in disguise!".
4 |
5 | There are three distinct modules here.
6 |
7 | 1. `transformer-libs/transformer-plugin` is a Gradle plugin that uses the
8 | Android Gradle Transform API to perform bytecode manipulation.
9 | 1. `transformer-libs/transformer-module` is an Android library project.
10 | 1. `tranformer-app` is an Android app that has the plugin applied to it,
11 | and also uses the library project.
12 |
13 | The first two modules can be installed in a local Maven repo with:
14 |
15 | ```bash
16 | cd transformer-libs
17 | ./gradlew install
18 | ```
19 |
20 | After these are installed, they can participate in the build of the Android
21 | app.
22 |
23 | When launched, you will observe in logcat that the app is printing more
24 | log lines than you see in the code, and the lines of code are coming
25 | from the library project as part of build time instrumentation.
26 |
27 | ## License
28 |
29 | The code in this project is licensed under the Apache License 2.0.
30 |
31 | ```text
32 | Copyright 2019 Google LLC
33 |
34 | Licensed under the Apache License, Version 2.0 (the "License");
35 | you may not use this file except in compliance with the License.
36 | You may obtain a copy of the License at
37 |
38 | https://www.apache.org/licenses/LICENSE-2.0
39 |
40 | Unless required by applicable law or agreed to in writing, software
41 | distributed under the License is distributed on an "AS IS" BASIS,
42 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
43 | See the License for the specific language governing permissions and
44 | limitations under the License.
45 | ```
46 |
47 | ## Disclaimer
48 |
49 | This is not an officially supported Google product.
50 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/src/main/java/com/hyperaware/transformer/plugin/MyPlugin.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformer.plugin
18 |
19 | import com.android.build.gradle.AppExtension
20 | import org.gradle.api.Plugin
21 | import org.gradle.api.Project
22 | import org.gradle.api.logging.LogLevel
23 |
24 | class MyPlugin : Plugin {
25 |
26 | override fun apply(project: Project) {
27 | project.logger.log(LogLevel.INFO, "MyPlugin applied")
28 | // Check to see if this is an android project
29 | val ext = project.extensions.findByName("android")
30 | if (ext != null && ext is AppExtension) {
31 | project.logger.log(LogLevel.INFO, "Registering transform")
32 | // Register our class transform
33 | ext.registerTransform(MyTransform(project))
34 | // Add an extension for gradle configuration
35 | project.extensions.create("transform", MyExtension::class.java)
36 | }
37 | else {
38 | throw Exception("${MyPlugin::class.java.name} plugin may only be applied to Android app projects")
39 | }
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/src/main/java/com/hyperaware/transformer/plugin/asm/ClassInstrumenter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformer.plugin.asm
18 |
19 | import com.hyperaware.transformer.plugin.InstrumentationConfig
20 | import org.objectweb.asm.ClassReader
21 | import org.objectweb.asm.ClassWriter
22 | import java.net.URLClassLoader
23 |
24 | class ClassInstrumenter(private val config: InstrumentationConfig) {
25 |
26 | private val cl = URLClassLoader(config.runtimeClasspath.toTypedArray())
27 |
28 | fun instrument(input: ByteArray): ByteArray {
29 | val cr = ClassReader(input)
30 |
31 | // Custom ClassWriter needs to specify a ClassLoader that knows
32 | // about all classes in the app.
33 | val cw = object : ClassWriter(ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES) {
34 | override fun getClassLoader(): ClassLoader = cl
35 | }
36 |
37 | // Our InstrumentationVisitor wraps the ClassWriter to intercept and
38 | // change bytecode as class elements are being visited.
39 | val cv = InstrumentationVisitor(cw, config)
40 | cr.accept(cv, ClassReader.SKIP_FRAMES)
41 |
42 | return cw.toByteArray()
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/java/com/hyperaware/transformers/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformers
18 |
19 | import android.os.Bundle
20 | import android.util.Log
21 | import androidx.appcompat.app.AppCompatActivity
22 | import java.net.URL
23 | import java.nio.charset.Charset
24 | import javax.net.ssl.HttpsURLConnection
25 |
26 | class MainActivity : AppCompatActivity() {
27 |
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | super.onCreate(savedInstanceState)
30 | setContentView(R.layout.activity_main)
31 | Thread(MyRunnable()).start()
32 | }
33 |
34 | }
35 |
36 | class MyRunnable : Runnable {
37 |
38 | override fun run() {
39 | val url = URL("https://www.google.com")
40 | val array = ByteArray(15)
41 | val conn = url.openConnection()
42 |
43 | // When the connection is instrumented, we'll see the decorator class
44 | // here.
45 | //
46 | Log.d("@@@@@", "URLConnection: $conn")
47 |
48 | // The decorated class also returns a decorated InputStream, which logs
49 | // a message to make itself known here.
50 | //
51 | conn.getInputStream().use {
52 | // Since the decorator subclasses the appropriate expected type, it
53 | // can still be cast as expected in order to call special methods
54 | // for HTTPS connections.
55 | //
56 | if (conn is HttpsURLConnection) {
57 | Log.d("@@@@@", conn.cipherSuite.toString())
58 | }
59 |
60 | it.read(array)
61 | Log.d("@@@@@", array.toString(Charset.defaultCharset()))
62 | }
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/transformer-app/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/src/main/java/com/hyperaware/transformer/plugin/MyTransform.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformer.plugin
18 |
19 | import com.android.build.api.transform.QualifiedContent
20 | import com.android.build.api.transform.Transform
21 | import com.android.build.api.transform.TransformInvocation
22 | import com.android.build.api.variant.VariantInfo
23 | import com.android.build.gradle.AppExtension
24 | import org.gradle.api.Project
25 | import org.gradle.api.logging.Logging
26 |
27 | class MyTransform(private val project: Project) : Transform() {
28 |
29 | private val logger = Logging.getLogger(MyTransform::class.java)
30 |
31 | override fun getName(): String {
32 | return MyTransform::class.java.simpleName
33 | }
34 |
35 | // This transform is interested in classes only (and not resources)
36 | private val typeClasses = setOf(QualifiedContent.DefaultContentType.CLASSES)
37 |
38 | override fun getInputTypes(): Set {
39 | return typeClasses
40 | }
41 |
42 |
43 | // This transform is interested in classes from all parts of the app
44 | private val scopes = setOf(
45 | QualifiedContent.Scope.PROJECT,
46 | QualifiedContent.Scope.SUB_PROJECTS,
47 | QualifiedContent.Scope.EXTERNAL_LIBRARIES
48 | )
49 |
50 | override fun getScopes(): MutableSet {
51 | return scopes.toMutableSet()
52 | }
53 |
54 |
55 | // This transform can handle incremental builds
56 | override fun isIncremental(): Boolean {
57 | return true
58 | }
59 |
60 |
61 | override fun transform(transformInvocation: TransformInvocation) {
62 | val start = System.currentTimeMillis()
63 |
64 | // Find the Gradle extension that contains configuration for this Transform
65 | val ext = project.extensions.findByType(MyExtension::class.java) ?: MyExtension()
66 | logger.debug("config logVisits ${ext.logVisits}")
67 | logger.debug("config logInstrumentation ${ext.logInstrumentation}")
68 |
69 | val appExtension = project.extensions.findByName("android") as AppExtension
70 | val ignores = listOf(
71 | // Don't instrument the companion library
72 | Regex("com/hyperaware/transformer/.*")
73 | )
74 | val config = TransformConfig(transformInvocation, appExtension.bootClasspath, ignores, ext)
75 |
76 | MyTransformImpl(config).doIt()
77 | val end = System.currentTimeMillis()
78 | logger.info("Transform took ${end-start}ms")
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-module/src/main/java/com/hyperaware/transformer/module/InstrumentedURLConnection.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformer.module
18 |
19 | import java.io.InputStream
20 | import java.io.OutputStream
21 | import java.net.URL
22 | import java.security.Permission
23 | import java.security.Principal
24 | import java.security.cert.Certificate
25 | import javax.net.ssl.HostnameVerifier
26 | import javax.net.ssl.SSLSocketFactory
27 |
28 | /**
29 | * Combined API of Http and HttpsURLConnection. Used for automatic delegation
30 | * in Kotlin.
31 | */
32 |
33 | interface InstrumentedURLConnection {
34 |
35 | fun getContentEncoding(): String
36 |
37 | fun getHeaderField(name: String?): String
38 |
39 | fun getReadTimeout(): Int
40 |
41 | fun connect()
42 |
43 | fun getUseCaches(): Boolean
44 |
45 | fun setConnectTimeout(timeout: Int)
46 |
47 | fun getDate(): Long
48 |
49 | fun getExpiration(): Long
50 |
51 | fun getContent(): Any
52 |
53 | fun getContent(classes: Array>?): Any
54 |
55 | fun getContentLengthLong(): Long
56 |
57 | fun getHeaderFieldInt(name: String?, Default: Int): Int
58 |
59 | fun setUseCaches(usecaches: Boolean)
60 |
61 | fun getIfModifiedSince(): Long
62 |
63 | fun setIfModifiedSince(ifmodifiedsince: Long)
64 |
65 | fun getDoInput(): Boolean
66 |
67 | fun getLastModified(): Long
68 |
69 | fun setDefaultUseCaches(defaultusecaches: Boolean)
70 |
71 | fun setDoOutput(dooutput: Boolean)
72 |
73 | fun getDefaultUseCaches(): Boolean
74 |
75 | fun getRequestProperties(): MutableMap>
76 |
77 | fun setReadTimeout(timeout: Int)
78 |
79 | fun getDoOutput(): Boolean
80 |
81 | fun addRequestProperty(key: String?, value: String?)
82 |
83 | fun getConnectTimeout(): Int
84 |
85 | fun setDoInput(doinput: Boolean)
86 |
87 | fun getHeaderFields(): MutableMap>
88 |
89 | fun getInputStream(): InputStream
90 |
91 | fun getAllowUserInteraction(): Boolean
92 |
93 | fun getURL(): URL
94 |
95 | fun setRequestProperty(key: String?, value: String?)
96 |
97 | fun setAllowUserInteraction(allowuserinteraction: Boolean)
98 |
99 | fun getContentLength(): Int
100 |
101 | fun getContentType(): String
102 |
103 | fun getRequestProperty(key: String?): String
104 |
105 | fun getOutputStream(): OutputStream
106 |
107 | fun getHeaderFieldLong(name: String?, Default: Long): Long
108 |
109 | fun getHeaderField(n: Int): String
110 |
111 | fun usingProxy(): Boolean
112 |
113 | fun getHeaderFieldKey(n: Int): String
114 |
115 | fun setInstanceFollowRedirects(followRedirects: Boolean)
116 |
117 | fun getHeaderFieldDate(name: String?, Default: Long): Long
118 |
119 | fun setChunkedStreamingMode(chunklen: Int)
120 |
121 | fun getPermission(): Permission
122 |
123 | fun getInstanceFollowRedirects(): Boolean
124 |
125 | fun getRequestMethod(): String
126 |
127 | fun getErrorStream(): InputStream
128 |
129 | fun getResponseMessage(): String
130 |
131 | fun setFixedLengthStreamingMode(contentLength: Int)
132 |
133 | fun setFixedLengthStreamingMode(contentLength: Long)
134 |
135 | fun disconnect()
136 |
137 | fun setRequestMethod(method: String?)
138 |
139 | fun getResponseCode(): Int
140 |
141 | // For HTTPS
142 |
143 | fun getLocalPrincipal(): Principal
144 |
145 | fun getHostnameVerifier(): HostnameVerifier
146 |
147 | fun getServerCertificates(): Array
148 |
149 | fun setHostnameVerifier(v: HostnameVerifier?)
150 |
151 | fun setSSLSocketFactory(sf: SSLSocketFactory?)
152 |
153 | fun getPeerPrincipal(): Principal
154 |
155 | fun getCipherSuite(): String
156 |
157 | fun getLocalCertificates(): Array
158 |
159 | fun getSSLSocketFactory(): SSLSocketFactory
160 |
161 | }
162 |
--------------------------------------------------------------------------------
/transformer-app/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/src/main/java/com/hyperaware/transformer/plugin/asm/InstrumentationVisitor.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformer.plugin.asm
18 |
19 | import com.hyperaware.transformer.plugin.InstrumentationConfig
20 | import org.gradle.api.logging.Logging
21 | import org.objectweb.asm.ClassVisitor
22 | import org.objectweb.asm.MethodVisitor
23 | import org.objectweb.asm.Opcodes
24 | import org.objectweb.asm.commons.AdviceAdapter
25 |
26 | class InstrumentationVisitor(
27 | private val classVisitor: ClassVisitor,
28 | private val config: InstrumentationConfig
29 | ) : ClassVisitor(ASM_API_VERSION, classVisitor) {
30 |
31 | companion object {
32 | private const val ASM_API_VERSION = Opcodes.ASM7
33 | }
34 |
35 | private val logger = Logging.getLogger(InstrumentationVisitor::class.java)
36 |
37 | override fun visit(
38 | version: Int,
39 | access: Int,
40 | className: String,
41 | signature: String?,
42 | superName: String,
43 | interfaces: Array?
44 | ) {
45 | // The only thing we're doing here is logging that we visited this class.
46 | //
47 | if (config.logVisits) {
48 | logger.debug("VISITING CLASS $className")
49 | logger.debug(" signature: $signature superName: $superName interfaces: ${interfaces?.joinToString()}")
50 | }
51 |
52 | super.visit(version, access, className, signature, superName, interfaces)
53 | }
54 |
55 | override fun visitMethod(
56 | access: Int, // public / private / final / etc
57 | methodName: String, // e.g. "openConnection"
58 | methodDesc: String, // e.g. "()Ljava/net/URLConnection;
59 | signature: String?, // for any generics
60 | exceptions: Array? // declared exceptions thrown
61 | ): MethodVisitor {
62 | if (config.logVisits) {
63 | logger.debug("Visit method: $methodName desc: $methodDesc signature: $signature exceptions: ${exceptions?.joinToString()}")
64 | }
65 |
66 | // Get a MethodVisitor using the ClassVisitor we're decorating
67 | val mv = super.visitMethod(access, methodName, methodDesc, signature, exceptions)
68 | // Wrap it in a custom MethodVisitor
69 | return MyMethodVisitor(ASM_API_VERSION, mv, access, methodName, methodDesc)
70 | }
71 |
72 |
73 | private inner class MyMethodVisitor(
74 | api: Int,
75 | mv: MethodVisitor,
76 | access: Int,
77 | methodName: String,
78 | methodDesc: String
79 | ): AdviceAdapter(api, mv, access, methodName, methodDesc) {
80 |
81 | override fun visitMethodInsn(
82 | opcode: Int, // type of method call this is (e.g. invokevirtual, invokestatic)
83 | owner: String, // containing object
84 | name: String, // name of the method
85 | desc: String, // signature
86 | itf: Boolean) { // is this from an interface?
87 |
88 | if (config.logVisits) {
89 | logger.debug("visitMethodInsn opcode: $opcode owner: $owner name: $name desc: $desc")
90 | }
91 |
92 | if (owner == "java/net/URL" && name == "openStream" && desc == "()Ljava/io/InputStream;") {
93 | // Replace this instruction with a new instruction that calls out to
94 | // the companion SDK.
95 | super.visitMethodInsn(
96 | Opcodes.INVOKESTATIC,
97 | "com/hyperaware/transformer/module/UrlConnectionInstrumentation",
98 | "openStream",
99 | "(Ljava/net/URL;)Ljava/io/InputStream;",
100 | false
101 | )
102 | if (config.logInstrumentation) {
103 | logger.debug("@@@@@@@@@@ I instrumented a call to URL.openStream")
104 | }
105 | }
106 | else if (owner == "java/net/URL" && name == "openConnection" && desc == "()Ljava/net/URLConnection;") {
107 | super.visitMethodInsn(
108 | Opcodes.INVOKESTATIC,
109 | "com/hyperaware/transformer/module/UrlConnectionInstrumentation",
110 | "openConnection",
111 | "(Ljava/net/URL;)Ljava/net/URLConnection;",
112 | false
113 | )
114 | if (config.logInstrumentation) {
115 | logger.debug("@@@@@@@@@@ I instrumented a call to URL.openConnection")
116 | }
117 | }
118 | else {
119 | super.visitMethodInsn(opcode, owner, name, desc, itf)
120 | }
121 | }
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/transformer-app/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/transformer-libs/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-plugin/src/main/java/com/hyperaware/transformer/plugin/MyTransformImpl.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformer.plugin
18 |
19 | import com.android.build.api.transform.*
20 | import com.hyperaware.transformer.plugin.asm.ClassInstrumenter
21 | import org.apache.commons.io.FileUtils
22 | import org.apache.commons.io.IOUtils
23 | import org.gradle.api.logging.Logging
24 | import java.io.*
25 | import java.net.URL
26 | import java.util.jar.JarFile
27 |
28 | class MyTransformImpl(config: TransformConfig) {
29 |
30 | private val logger = Logging.getLogger(MyTransformImpl::class.java)
31 |
32 | private val transformInvocation = config.transformInvocation
33 | private val androidClasspath = config.androidClasspath
34 | private val ignorePaths = config.ignorePaths
35 | private val outputProvider = transformInvocation.outputProvider
36 | private val instrumentationConfig = InstrumentationConfig(
37 | buildRuntimeClasspath(transformInvocation),
38 | config.pluginConfig.logVisits,
39 | config.pluginConfig.logInstrumentation
40 | )
41 | private val instrumenter = ClassInstrumenter(instrumentationConfig)
42 |
43 | fun doIt() {
44 | logger.debug(instrumentationConfig.toString())
45 | logger.debug("isIncremental: ${transformInvocation.isIncremental}")
46 | for (ti in transformInvocation.inputs) {
47 | instrumentDirectoryInputs(ti.directoryInputs)
48 | instrumentJarInputs(ti.jarInputs)
49 | }
50 | }
51 |
52 | /**
53 | * Builds the runtime classpath of the project. This combines all the
54 | * various TransformInput file locations in addition to the targeted
55 | * Android platform jar into a single collection that's suitable to be a
56 | * classpath for the entire app.
57 | */
58 | private fun buildRuntimeClasspath(transformInvocation: TransformInvocation): List {
59 | val allTransformInputs = transformInvocation.inputs + transformInvocation.referencedInputs
60 | val allJarsAndDirs = allTransformInputs.map { ti ->
61 | (ti.directoryInputs + ti.jarInputs).map { i -> i.file }
62 | }
63 | val allClassesAtRuntime = androidClasspath + allJarsAndDirs.flatten()
64 | return allClassesAtRuntime.map { file -> file.toURI().toURL() }
65 | }
66 |
67 |
68 | private fun instrumentDirectoryInputs(directoryInputs: Collection) {
69 | // A DirectoryInput is a tree of class files that simply gets
70 | // copied to the output directory.
71 | //
72 | for (di in directoryInputs) {
73 | // Build a unique name for the output dir based on the path
74 | // of the input dir.
75 | //
76 | logger.debug("TransformInput dir $di")
77 | val outDir = outputProvider.getContentLocation(di.name, di.contentTypes, di.scopes, Format.DIRECTORY)
78 | logger.debug(" Directory input ${di.file}")
79 | logger.debug(" Directory output $outDir")
80 | if (transformInvocation.isIncremental) {
81 | // Incremental builds will specify which individual class files changed.
82 | for (changedFile in di.changedFiles) {
83 | when (changedFile.value) {
84 | Status.ADDED, Status.CHANGED -> {
85 | val relativeFile = normalizedRelativeFilePath(di.file, changedFile.key)
86 | val destFile = File(outDir, relativeFile)
87 | changedFile.key.inputStream().use { inputStream ->
88 | destFile.outputStream().use { outputStream ->
89 | if (isInstrumentableClassFile(relativeFile)) {
90 | processClassStream(relativeFile, inputStream, outputStream)
91 | }
92 | else {
93 | copyStream(inputStream, outputStream)
94 | }
95 | }
96 | }
97 | }
98 | Status.REMOVED -> {
99 | val relativeFile = normalizedRelativeFilePath(di.file, changedFile.key)
100 | val destFile = File(outDir, relativeFile)
101 | FileUtils.forceDelete(destFile)
102 | }
103 | Status.NOTCHANGED, null -> {
104 | }
105 | }
106 | }
107 | logger.debug(" Files processed: ${di.changedFiles.size}")
108 | }
109 | else {
110 | ensureDirectoryExists(outDir)
111 | FileUtils.cleanDirectory(outDir)
112 | logger.debug(" Copying ${di.file} to $outDir")
113 | var count = 0
114 | for (file in FileUtils.iterateFiles(di.file, null, true)) {
115 | val relativeFile = normalizedRelativeFilePath(di.file, file)
116 | val destFile = File(outDir, relativeFile)
117 | ensureDirectoryExists(destFile.parentFile)
118 | IOUtils.buffer(file.inputStream()).use { inputStream ->
119 | IOUtils.buffer(destFile.outputStream()).use { outputStream ->
120 | if (isInstrumentableClassFile(relativeFile)) {
121 | try {
122 | processClassStream(relativeFile, inputStream, outputStream)
123 | }
124 | catch (e: Exception) {
125 | logger.error("Can't process class $file", e)
126 | throw e
127 | }
128 | }
129 | else {
130 | copyStream(inputStream, outputStream)
131 | }
132 | }
133 | }
134 | count++
135 | }
136 | logger.debug(" Files processed: $count")
137 | }
138 | }
139 | }
140 |
141 | private fun instrumentJarInputs(jarInputs: Collection) {
142 | // A JarInput is a jar file that just gets copied to a destination
143 | // output jar.
144 | //
145 | for (ji in jarInputs) {
146 | // Build a unique name for the output file based on the path
147 | // of the input jar.
148 | //
149 | logger.debug("TransformInput jar $ji")
150 | val outDir = outputProvider.getContentLocation(ji.name, ji.contentTypes, ji.scopes, Format.DIRECTORY)
151 | logger.debug(" Jar input ${ji.file}")
152 | logger.debug(" Dir output $outDir")
153 |
154 | val doTransform = !transformInvocation.isIncremental || ji.status == Status.ADDED || ji.status == Status.CHANGED
155 | if (doTransform) {
156 | ensureDirectoryExists(outDir)
157 | FileUtils.cleanDirectory(outDir)
158 | val inJar = JarFile(ji.file)
159 | var count = 0
160 | for (entry in inJar.entries()) {
161 | val outFile = File(outDir, entry.name)
162 | if (!entry.isDirectory) {
163 | ensureDirectoryExists(outFile.parentFile)
164 | inJar.getInputStream(entry).use { inputStream ->
165 | IOUtils.buffer(FileOutputStream(outFile)).use { outputStream ->
166 | if (isInstrumentableClassFile(entry.name)) {
167 | try {
168 | processClassStream(entry.name, inputStream, outputStream)
169 | }
170 | catch (e: Exception) {
171 | logger.error("Can't process class ${entry.name}", e)
172 | throw e
173 | }
174 | }
175 | else {
176 | copyStream(inputStream, outputStream)
177 | }
178 | }
179 | }
180 | count++
181 | }
182 | }
183 | logger.debug(" Entries copied: $count")
184 | }
185 | else if (ji.status == Status.REMOVED) {
186 | logger.debug(" REMOVED")
187 | if (outDir.exists()) {
188 | FileUtils.forceDelete(outDir)
189 | }
190 | }
191 | }
192 | }
193 |
194 | private fun ensureDirectoryExists(dir: File) {
195 | if (! ((dir.isDirectory && dir.canWrite()) || dir.mkdirs())) {
196 | throw IOException("Can't write or create ${dir.path}")
197 | }
198 | }
199 |
200 | // Builds a relative path from a given file and its parent.
201 | // For file /a/b/c and parent /a, returns "b/c".
202 | private fun normalizedRelativeFilePath(parent: File, file: File): String {
203 | val parts = mutableListOf()
204 | var current = file
205 | while (current != parent) {
206 | parts.add(current.name)
207 | current = current.parentFile
208 | }
209 | return parts.asReversed().joinToString("/")
210 | }
211 |
212 | // Checks the (relative) path of a given class file and returns true if
213 | // it's assumed to be instrumentable. The path must end with .class and
214 | // also not match any of the regular expressions in ignorePaths.
215 | private fun isInstrumentableClassFile(path: String): Boolean {
216 | return if (ignorePaths.any { it.matches(path) }) {
217 | logger.debug("Ignoring class $path")
218 | false
219 | }
220 | else {
221 | path.toLowerCase().endsWith(".class")
222 | }
223 | }
224 |
225 | private fun copyStream(inputStream: InputStream, outputStream: OutputStream) {
226 | IOUtils.copy(inputStream, outputStream)
227 | }
228 |
229 | private fun processClassStream(name: String, inputStream: InputStream, outputStream: OutputStream) {
230 | val classBytes = IOUtils.toByteArray(inputStream)
231 | val bytesToWrite = try {
232 | val instrBytes = instrumenter.instrument(classBytes)
233 | instrBytes
234 | }
235 | catch (e: Exception) {
236 | // If instrumentation fails, just write the original bytes
237 | logger.error("Failed to instrument $name, using original contents", e)
238 | classBytes
239 | }
240 | IOUtils.write(bytesToWrite, outputStream)
241 | }
242 |
243 | }
244 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/transformer-libs/transformer-module/src/main/java/com/hyperaware/transformer/module/UrlConnectionInstrumentation.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.hyperaware.transformer.module
18 |
19 | import android.annotation.TargetApi
20 | import android.os.Build
21 | import android.util.Log
22 | import java.io.InputStream
23 | import java.io.OutputStream
24 | import java.lang.RuntimeException
25 | import java.net.HttpURLConnection
26 | import java.net.URL
27 | import java.net.URLConnection
28 | import java.security.Permission
29 | import java.security.Principal
30 | import java.security.cert.Certificate
31 | import javax.net.ssl.HostnameVerifier
32 | import javax.net.ssl.HttpsURLConnection
33 | import javax.net.ssl.SSLSocketFactory
34 |
35 | @Suppress("unused")
36 | class UrlConnectionInstrumentation {
37 |
38 | companion object {
39 |
40 | @JvmStatic
41 | fun openConnection(url: URL): URLConnection {
42 | Log.d("@@@@@", "Fetching $url via openConnection")
43 | return when (val conn = url.openConnection()) {
44 | is HttpsURLConnection ->
45 | InstrumentedHttpsURLConnection(url, conn)
46 | is HttpURLConnection ->
47 | InstrumentedHttpURLConnection(url, conn)
48 | else ->
49 | conn
50 | }
51 | }
52 |
53 | // Should also provide these methods to cover all possible uses of
54 | // java.net.URL to kick off an HTTP request.
55 |
56 | // @JvmStatic
57 | // fun openStream(url: URL): InputStream
58 | // @JvmStatic
59 | // fun openConnection(url: URL, proxy: Proxy): URLConnection
60 | // @JvmStatic
61 | // fun getContent(url: URL): IOException
62 | // @JvmStatic
63 | // fun getContent(url: URL, classes: Array>): IOException
64 |
65 | }
66 |
67 | }
68 |
69 | // The following two classes:
70 | // - InstrumentedHttpURLConnection
71 | // - InstrumentedHttpsURLConnection
72 | //
73 | // Do the following:
74 | // - Decorate a Http(s)URLConnection
75 | // - Delegate all API methods to an implementation class that measures
76 | // and records the transaction, and further delegates the decorated object
77 | // - Subclass Http(s)URLConnection so they gain private implementation details
78 | // and be downcast as an opaque replacement for the decorated object
79 |
80 | @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
81 | private open class InstrumentedHttpURLConnection(url: URL, urlc: HttpURLConnection)
82 | : InstrumentedURLConnection by InstrumentedHttpURLConnectionImpl(urlc), HttpURLConnection(url)
83 |
84 | @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
85 | private class InstrumentedHttpsURLConnection(url: URL, private val urlc: HttpsURLConnection)
86 | : InstrumentedURLConnection by InstrumentedHttpsURLConnectionImpl(urlc), HttpsURLConnection(url)
87 |
88 |
89 | // The following two classes:
90 | // - InstrumentedHttpURLConnectionImpl
91 | // - InstrumentedHttpsURLConnectionImpl
92 | // implement the universal interface for all HTTPS and HTTP requests.
93 | // They override all exposed API methods, measures and records the transaction, and
94 | // delegate to an actual Http(s)URLConnection object that does the real work.
95 |
96 | private open class InstrumentedHttpURLConnectionImpl(private val urlConn: HttpURLConnection)
97 | : InstrumentedURLConnection {
98 |
99 | override fun getContentEncoding(): String {
100 | return urlConn.contentEncoding
101 | }
102 |
103 | override fun getHeaderField(name: String?): String {
104 | return urlConn.getHeaderField(name)
105 | }
106 |
107 | override fun getReadTimeout(): Int {
108 | return urlConn.readTimeout
109 | }
110 |
111 | override fun connect() {
112 | urlConn.connect()
113 | }
114 |
115 | override fun getUseCaches(): Boolean {
116 | return urlConn.useCaches
117 | }
118 |
119 | override fun setConnectTimeout(timeout: Int) {
120 | urlConn.connectTimeout = timeout
121 | }
122 |
123 | override fun getDate(): Long {
124 | return urlConn.date
125 | }
126 |
127 | override fun getExpiration(): Long {
128 | return urlConn.expiration
129 | }
130 |
131 | override fun getContent(): Any {
132 | return urlConn.content
133 | }
134 |
135 | override fun getContent(classes: Array>?): Any {
136 | return urlConn.getContent(classes)
137 | }
138 |
139 | @TargetApi(Build.VERSION_CODES.N)
140 | override fun getContentLengthLong(): Long {
141 | return urlConn.contentLengthLong
142 | }
143 |
144 | override fun getHeaderFieldInt(name: String?, Default: Int): Int {
145 | return urlConn.getHeaderFieldInt(name, Default)
146 | }
147 |
148 | override fun setUseCaches(usecaches: Boolean) {
149 | urlConn.useCaches = usecaches
150 | }
151 |
152 | override fun getIfModifiedSince(): Long {
153 | return urlConn.ifModifiedSince
154 | }
155 |
156 | override fun setIfModifiedSince(ifmodifiedsince: Long) {
157 | return setIfModifiedSince(ifmodifiedsince)
158 | }
159 |
160 | override fun getDoInput(): Boolean {
161 | return urlConn.doInput
162 | }
163 |
164 | override fun getLastModified(): Long {
165 | return urlConn.lastModified
166 | }
167 |
168 | override fun setDefaultUseCaches(defaultusecaches: Boolean) {
169 | urlConn.defaultUseCaches = defaultusecaches
170 | }
171 |
172 | override fun setDoOutput(dooutput: Boolean) {
173 | urlConn.doOutput = dooutput
174 | }
175 |
176 | override fun getDefaultUseCaches(): Boolean {
177 | return urlConn.defaultUseCaches
178 | }
179 |
180 | override fun getRequestProperties(): MutableMap> {
181 | return urlConn.requestProperties
182 | }
183 |
184 | override fun setReadTimeout(timeout: Int) {
185 | urlConn.readTimeout = timeout
186 | }
187 |
188 | override fun getDoOutput(): Boolean {
189 | return urlConn.doOutput
190 | }
191 |
192 | override fun addRequestProperty(key: String?, value: String?) {
193 | urlConn.addRequestProperty(key, value)
194 | }
195 |
196 | override fun getConnectTimeout(): Int {
197 | return urlConn.connectTimeout
198 | }
199 |
200 | override fun setDoInput(doinput: Boolean) {
201 | urlConn.doInput = doinput
202 | }
203 |
204 | override fun getHeaderFields(): MutableMap> {
205 | return urlConn.headerFields
206 | }
207 |
208 | override fun getInputStream(): InputStream {
209 | Log.d("@@@@@", "You're using an instrumented stream.")
210 | return InstrumentedInputStream(urlConn.inputStream)
211 | }
212 |
213 | override fun getAllowUserInteraction(): Boolean {
214 | return urlConn.allowUserInteraction
215 | }
216 |
217 | override fun getURL(): URL {
218 | return urlConn.url
219 | }
220 |
221 | override fun setRequestProperty(key: String?, value: String?) {
222 | urlConn.setRequestProperty(key, value)
223 | }
224 |
225 | override fun setAllowUserInteraction(allowuserinteraction: Boolean) {
226 | urlConn.allowUserInteraction
227 | }
228 |
229 | override fun getContentLength(): Int {
230 | return urlConn.contentLength
231 | }
232 |
233 | override fun getContentType(): String {
234 | return urlConn.contentType
235 | }
236 |
237 | override fun getRequestProperty(key: String?): String {
238 | return urlConn.getRequestProperty(key)
239 | }
240 |
241 | override fun getOutputStream(): OutputStream {
242 | return urlConn.outputStream
243 | }
244 |
245 | @TargetApi(Build.VERSION_CODES.N)
246 | override fun getHeaderFieldLong(name: String?, Default: Long): Long {
247 | return urlConn.getHeaderFieldLong(name, Default)
248 | }
249 |
250 | override fun getHeaderField(n: Int): String {
251 | return urlConn.getHeaderField(n)
252 | }
253 |
254 | override fun usingProxy(): Boolean {
255 | return urlConn.usingProxy()
256 | }
257 |
258 | override fun getHeaderFieldKey(n: Int): String {
259 | return urlConn.getHeaderFieldKey(n)
260 | }
261 |
262 | override fun setInstanceFollowRedirects(followRedirects: Boolean) {
263 | urlConn.instanceFollowRedirects = followRedirects
264 | }
265 |
266 | override fun getHeaderFieldDate(name: String?, Default: Long): Long {
267 | return urlConn.getHeaderFieldDate(name, Default)
268 | }
269 |
270 | override fun setChunkedStreamingMode(chunklen: Int) {
271 | urlConn.setChunkedStreamingMode(chunklen)
272 | }
273 |
274 | override fun getPermission(): Permission {
275 | return urlConn.permission
276 | }
277 |
278 | override fun getInstanceFollowRedirects(): Boolean {
279 | return urlConn.instanceFollowRedirects
280 | }
281 |
282 | override fun getRequestMethod(): String {
283 | return urlConn.requestMethod
284 | }
285 |
286 | override fun getErrorStream(): InputStream {
287 | return urlConn.errorStream
288 | }
289 |
290 | override fun getResponseMessage(): String {
291 | return urlConn.responseMessage
292 | }
293 |
294 | override fun setFixedLengthStreamingMode(contentLength: Int) {
295 | return urlConn.setFixedLengthStreamingMode(contentLength)
296 | }
297 |
298 | @TargetApi(Build.VERSION_CODES.KITKAT)
299 | override fun setFixedLengthStreamingMode(contentLength: Long) {
300 | return urlConn.setFixedLengthStreamingMode(contentLength)
301 | }
302 |
303 | override fun disconnect() {
304 | urlConn.disconnect()
305 | }
306 |
307 | override fun setRequestMethod(method: String?) {
308 | urlConn.requestMethod = method
309 | }
310 |
311 | override fun getResponseCode(): Int {
312 | return urlConn.responseCode
313 | }
314 |
315 | // Http connections won't ever do anything with HttpsUrlConnection methods
316 |
317 | override fun getLocalPrincipal(): Principal {
318 | throw newNotAnHttpsException()
319 | }
320 |
321 | override fun getHostnameVerifier(): HostnameVerifier {
322 | throw newNotAnHttpsException()
323 | }
324 |
325 | override fun getServerCertificates(): Array {
326 | throw newNotAnHttpsException()
327 | }
328 |
329 | override fun setHostnameVerifier(v: HostnameVerifier?) {
330 | throw newNotAnHttpsException()
331 | }
332 |
333 | override fun setSSLSocketFactory(sf: SSLSocketFactory?) {
334 | throw newNotAnHttpsException()
335 | }
336 |
337 | override fun getPeerPrincipal(): Principal {
338 | throw newNotAnHttpsException()
339 | }
340 |
341 | override fun getCipherSuite(): String {
342 | throw newNotAnHttpsException()
343 | }
344 |
345 | override fun getLocalCertificates(): Array {
346 | throw newNotAnHttpsException()
347 | }
348 |
349 | override fun getSSLSocketFactory(): SSLSocketFactory {
350 | throw newNotAnHttpsException()
351 | }
352 |
353 | private fun newNotAnHttpsException(): RuntimeException {
354 | throw RuntimeException("This is not an HTTPS connection")
355 | }
356 |
357 | }
358 |
359 |
360 | // Since HttpsURLConnection just adds a few extra methods to HttpURLConnection,
361 | // we'll just subclass the above implementation and delegate only the added
362 | // methods. These methods aren't interesting for the purpose of performance
363 | // monitoring, so it's just pure delegation.
364 |
365 | private class InstrumentedHttpsURLConnectionImpl(private val urlConn: HttpsURLConnection)
366 | : InstrumentedHttpURLConnectionImpl(urlConn) {
367 |
368 | override fun getLocalPrincipal(): Principal {
369 | return urlConn.localPrincipal
370 | }
371 |
372 | override fun getHostnameVerifier(): HostnameVerifier {
373 | return urlConn.hostnameVerifier
374 | }
375 |
376 | override fun getServerCertificates(): Array {
377 | return urlConn.serverCertificates
378 | }
379 |
380 | override fun setHostnameVerifier(v: HostnameVerifier?) {
381 | urlConn.hostnameVerifier = v
382 | }
383 |
384 | override fun setSSLSocketFactory(sf: SSLSocketFactory?) {
385 | urlConn.sslSocketFactory = sf
386 | }
387 |
388 | override fun getPeerPrincipal(): Principal {
389 | return urlConn.peerPrincipal
390 | }
391 |
392 | override fun getCipherSuite(): String {
393 | return urlConn.cipherSuite
394 | }
395 |
396 | override fun getLocalCertificates(): Array {
397 | return urlConn.localCertificates
398 | }
399 |
400 | override fun getSSLSocketFactory(): SSLSocketFactory {
401 | return urlConn.sslSocketFactory
402 | }
403 |
404 | }
405 |
406 |
407 | // InstrumentedInputStream decorates an InputStream, and monitors its behavior.
408 |
409 | private class InstrumentedInputStream(private val stream: InputStream) : InputStream() {
410 |
411 | override fun read(): Int {
412 | return stream.read()
413 | }
414 |
415 | override fun read(b: ByteArray?): Int {
416 | val bytesRead = super.read(b)
417 | Log.d("@@@@@", "It just read $bytesRead bytes into an array")
418 | return bytesRead
419 | }
420 |
421 | override fun read(b: ByteArray?, off: Int, len: Int): Int {
422 | return stream.read(b, off, len)
423 | }
424 |
425 | override fun skip(n: Long): Long {
426 | return stream.skip(n)
427 | }
428 |
429 | override fun available(): Int {
430 | return stream.available()
431 | }
432 |
433 | override fun reset() {
434 | stream.reset()
435 | }
436 |
437 | override fun close() {
438 | stream.close()
439 | }
440 |
441 | override fun mark(readlimit: Int) {
442 | stream.mark(readlimit)
443 | }
444 |
445 | override fun markSupported(): Boolean {
446 | return stream.markSupported()
447 | }
448 |
449 | }
450 |
451 |
452 |
453 | // LEGACY
454 | // This wrapper class needs to override ALL exposed API methods and delegate
455 | // to the given HttpsURLConnection given in the constructor.
456 |
457 | //private open class InstrumentedHttpsUrlConnection(url: URL, private val urlc: HttpsURLConnection) : HttpsURLConnection(url) {
458 | // override fun getContentEncoding(): String {
459 | // return urlc.contentEncoding
460 | // }
461 | //
462 | // override fun getHeaderField(name: String?): String {
463 | // return urlc.getHeaderField(name)
464 | // }
465 | //
466 | // override fun getReadTimeout(): Int {
467 | // return urlc.readTimeout
468 | // }
469 | //
470 | // override fun connect() {
471 | // urlc.connect()
472 | // }
473 | //
474 | // override fun getUseCaches(): Boolean {
475 | // return urlc.useCaches
476 | // }
477 | //
478 | // override fun setConnectTimeout(timeout: Int) {
479 | // urlc.connectTimeout = timeout
480 | // }
481 | //
482 | // override fun getDate(): Long {
483 | // return urlc.date
484 | // }
485 | //
486 | // override fun getExpiration(): Long {
487 | // return urlc.expiration
488 | // }
489 | //
490 | // override fun getContent(): Any {
491 | // return urlc.content
492 | // }
493 | //
494 | // override fun getContent(classes: Array>?): Any {
495 | // return urlc.getContent(classes)
496 | // }
497 | //
498 | // @TargetApi(Build.VERSION_CODES.N)
499 | // override fun getContentLengthLong(): Long {
500 | // return urlc.contentLengthLong
501 | // }
502 | //
503 | // override fun getHeaderFieldInt(name: String?, Default: Int): Int {
504 | // return urlc.getHeaderFieldInt(name, Default)
505 | // }
506 | //
507 | // override fun setUseCaches(usecaches: Boolean) {
508 | // urlc.useCaches = usecaches
509 | // }
510 | //
511 | // override fun getIfModifiedSince(): Long {
512 | // return urlc.ifModifiedSince
513 | // }
514 | //
515 | // override fun toString(): String {
516 | // return urlc.toString()
517 | // }
518 | //
519 | // override fun setIfModifiedSince(ifmodifiedsince: Long) {
520 | // urlc.ifModifiedSince = ifmodifiedsince
521 | // }
522 | //
523 | // override fun getDoInput(): Boolean {
524 | // return urlc.doInput
525 | // }
526 | //
527 | // override fun getLastModified(): Long {
528 | // return urlc.lastModified
529 | // }
530 | //
531 | // override fun setDefaultUseCaches(defaultusecaches: Boolean) {
532 | // urlc.defaultUseCaches = defaultusecaches
533 | // }
534 | //
535 | // override fun setDoOutput(dooutput: Boolean) {
536 | // urlc.doOutput = dooutput
537 | // }
538 | //
539 | // override fun getDefaultUseCaches(): Boolean {
540 | // return urlc.defaultUseCaches
541 | // }
542 | //
543 | // override fun getRequestProperties(): MutableMap> {
544 | // return urlc.requestProperties
545 | // }
546 | //
547 | // override fun setReadTimeout(timeout: Int) {
548 | // urlc.readTimeout = timeout
549 | // }
550 | //
551 | // override fun getDoOutput(): Boolean {
552 | // return urlc.doOutput
553 | // }
554 | //
555 | // override fun addRequestProperty(key: String?, value: String?) {
556 | // urlc.addRequestProperty(key, value)
557 | // }
558 | //
559 | // override fun getConnectTimeout(): Int {
560 | // return urlc.connectTimeout
561 | // }
562 | //
563 | // override fun setDoInput(doinput: Boolean) {
564 | // urlc.doInput = doinput
565 | // }
566 | //
567 | // override fun getHeaderFields(): MutableMap> {
568 | // return urlc.headerFields
569 | // }
570 | //
571 | // override fun getInputStream(): InputStream {
572 | // return urlc.inputStream
573 | // }
574 | //
575 | // override fun getAllowUserInteraction(): Boolean {
576 | // return urlc.allowUserInteraction
577 | // }
578 | //
579 | // override fun getURL(): URL {
580 | // return urlc.url
581 | // }
582 | //
583 | // override fun setRequestProperty(key: String?, value: String?) {
584 | // urlc.setRequestProperty(key, value)
585 | // }
586 | //
587 | // override fun setAllowUserInteraction(allowuserinteraction: Boolean) {
588 | // urlc.allowUserInteraction = allowuserinteraction
589 | // }
590 | //
591 | // override fun getContentLength(): Int {
592 | // return urlc.contentLength
593 | // }
594 | //
595 | // override fun getContentType(): String {
596 | // return urlc.contentType
597 | // }
598 | //
599 | // override fun getRequestProperty(key: String?): String {
600 | // return urlc.getRequestProperty(key)
601 | // }
602 | //
603 | // override fun getOutputStream(): OutputStream {
604 | // return urlc.outputStream
605 | // }
606 | //
607 | // @TargetApi(Build.VERSION_CODES.N)
608 | // override fun getHeaderFieldLong(name: String?, Default: Long): Long {
609 | // return urlc.getHeaderFieldLong(name, Default)
610 | // }
611 | //
612 | // override fun getHeaderField(n: Int): String {
613 | // return urlc.getHeaderField(n)
614 | // }
615 | //
616 | // override fun usingProxy(): Boolean {
617 | // return urlc.usingProxy()
618 | // }
619 | //
620 | // override fun getHeaderFieldKey(n: Int): String {
621 | // return urlc.getHeaderFieldKey(n)
622 | // }
623 | //
624 | // override fun setInstanceFollowRedirects(followRedirects: Boolean) {
625 | // urlc.instanceFollowRedirects = followRedirects
626 | // }
627 | //
628 | // override fun getHeaderFieldDate(name: String?, Default: Long): Long {
629 | // return urlc.getHeaderFieldDate(name, Default)
630 | // }
631 | //
632 | // override fun setChunkedStreamingMode(chunklen: Int) {
633 | // urlc.setChunkedStreamingMode(chunklen)
634 | // }
635 | //
636 | // override fun getPermission(): Permission {
637 | // return urlc.permission
638 | // }
639 | //
640 | // override fun getInstanceFollowRedirects(): Boolean {
641 | // return urlc.instanceFollowRedirects
642 | // }
643 | //
644 | // override fun getRequestMethod(): String {
645 | // return urlc.requestMethod
646 | // }
647 | //
648 | // override fun getErrorStream(): InputStream {
649 | // return urlc.errorStream
650 | // }
651 | //
652 | // override fun getResponseMessage(): String {
653 | // return urlc.responseMessage
654 | // }
655 | //
656 | // override fun setFixedLengthStreamingMode(contentLength: Int) {
657 | // urlc.setFixedLengthStreamingMode(contentLength)
658 | // }
659 | //
660 | // @TargetApi(Build.VERSION_CODES.KITKAT)
661 | // override fun setFixedLengthStreamingMode(contentLength: Long) {
662 | // urlc.setFixedLengthStreamingMode(contentLength)
663 | // }
664 | //
665 | // override fun disconnect() {
666 | // urlc.disconnect()
667 | // }
668 | //
669 | // override fun setRequestMethod(method: String?) {
670 | // urlc.requestMethod = method
671 | // }
672 | //
673 | // override fun getResponseCode(): Int {
674 | // return urlc.responseCode
675 | // }
676 | //
677 | // override fun getLocalPrincipal(): Principal {
678 | // return urlc.localPrincipal
679 | // }
680 | //
681 | // override fun getHostnameVerifier(): HostnameVerifier {
682 | // return urlc.hostnameVerifier
683 | // }
684 | //
685 | // override fun getServerCertificates(): Array {
686 | // return urlc.serverCertificates
687 | // }
688 | //
689 | // override fun setHostnameVerifier(v: HostnameVerifier?) {
690 | // urlc.hostnameVerifier = v
691 | // }
692 | //
693 | // override fun setSSLSocketFactory(sf: SSLSocketFactory?) {
694 | // urlc.sslSocketFactory = sf
695 | // }
696 | //
697 | // override fun getPeerPrincipal(): Principal {
698 | // return urlc.peerPrincipal
699 | // }
700 | //
701 | // override fun getCipherSuite(): String {
702 | // return urlc.cipherSuite
703 | // }
704 | //
705 | // override fun getLocalCertificates(): Array {
706 | // return urlc.localCertificates
707 | // }
708 | //
709 | // override fun getSSLSocketFactory(): SSLSocketFactory {
710 | // return urlc.sslSocketFactory
711 | // }
712 | //
713 | // override fun equals(other: Any?): Boolean {
714 | // return urlc == other
715 | // }
716 | //
717 | // override fun hashCode(): Int {
718 | // return urlc.hashCode()
719 | // }
720 | //
721 | //}
722 |
723 | // This class should override and delegate all methods just like above.
724 | // Right now it's just doing the minimum to compile.
725 |
726 | //private open class InstrumentedHttpUrlConnection(url: URL, private val urlc: HttpURLConnection) : HttpURLConnection(url) {
727 | // override fun getContentEncoding(): String {
728 | // return super.getContentEncoding()
729 | // }
730 | //
731 | // override fun getHeaderField(name: String?): String {
732 | // return super.getHeaderField(name)
733 | // }
734 | //
735 | // override fun getReadTimeout(): Int {
736 | // return super.getReadTimeout()
737 | // }
738 | //
739 | // override fun connect() {
740 | // TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
741 | // }
742 | //
743 | // override fun getUseCaches(): Boolean {
744 | // return super.getUseCaches()
745 | // }
746 | //
747 | // override fun setConnectTimeout(timeout: Int) {
748 | // super.setConnectTimeout(timeout)
749 | // }
750 | //
751 | // override fun getDate(): Long {
752 | // return super.getDate()
753 | // }
754 | //
755 | // override fun getExpiration(): Long {
756 | // return super.getExpiration()
757 | // }
758 | //
759 | // override fun getContent(): Any {
760 | // return super.getContent()
761 | // }
762 | //
763 | // override fun getContent(classes: Array>?): Any {
764 | // return super.getContent(classes)
765 | // }
766 | //
767 | // override fun getContentLengthLong(): Long {
768 | // return super.getContentLengthLong()
769 | // }
770 | //
771 | // override fun getHeaderFieldInt(name: String?, Default: Int): Int {
772 | // return super.getHeaderFieldInt(name, Default)
773 | // }
774 | //
775 | // override fun setUseCaches(usecaches: Boolean) {
776 | // super.setUseCaches(usecaches)
777 | // }
778 | //
779 | // override fun getIfModifiedSince(): Long {
780 | // return super.getIfModifiedSince()
781 | // }
782 | //
783 | // override fun toString(): String {
784 | // return super.toString()
785 | // }
786 | //
787 | // override fun setIfModifiedSince(ifmodifiedsince: Long) {
788 | // super.setIfModifiedSince(ifmodifiedsince)
789 | // }
790 | //
791 | // override fun getDoInput(): Boolean {
792 | // return super.getDoInput()
793 | // }
794 | //
795 | // override fun getLastModified(): Long {
796 | // return super.getLastModified()
797 | // }
798 | //
799 | // override fun setDefaultUseCaches(defaultusecaches: Boolean) {
800 | // super.setDefaultUseCaches(defaultusecaches)
801 | // }
802 | //
803 | // override fun setDoOutput(dooutput: Boolean) {
804 | // super.setDoOutput(dooutput)
805 | // }
806 | //
807 | // override fun getDefaultUseCaches(): Boolean {
808 | // return super.getDefaultUseCaches()
809 | // }
810 | //
811 | // override fun getRequestProperties(): MutableMap> {
812 | // return super.getRequestProperties()
813 | // }
814 | //
815 | // override fun setReadTimeout(timeout: Int) {
816 | // super.setReadTimeout(timeout)
817 | // }
818 | //
819 | // override fun getDoOutput(): Boolean {
820 | // return super.getDoOutput()
821 | // }
822 | //
823 | // override fun addRequestProperty(key: String?, value: String?) {
824 | // super.addRequestProperty(key, value)
825 | // }
826 | //
827 | // override fun getConnectTimeout(): Int {
828 | // return super.getConnectTimeout()
829 | // }
830 | //
831 | // override fun setDoInput(doinput: Boolean) {
832 | // super.setDoInput(doinput)
833 | // }
834 | //
835 | // override fun getHeaderFields(): MutableMap> {
836 | // return super.getHeaderFields()
837 | // }
838 | //
839 | // override fun getInputStream(): InputStream {
840 | // return super.getInputStream()
841 | // }
842 | //
843 | // override fun getAllowUserInteraction(): Boolean {
844 | // return super.getAllowUserInteraction()
845 | // }
846 | //
847 | // override fun getURL(): URL {
848 | // return super.getURL()
849 | // }
850 | //
851 | // override fun setRequestProperty(key: String?, value: String?) {
852 | // super.setRequestProperty(key, value)
853 | // }
854 | //
855 | // override fun setAllowUserInteraction(allowuserinteraction: Boolean) {
856 | // super.setAllowUserInteraction(allowuserinteraction)
857 | // }
858 | //
859 | // override fun getContentLength(): Int {
860 | // return super.getContentLength()
861 | // }
862 | //
863 | // override fun getContentType(): String {
864 | // return super.getContentType()
865 | // }
866 | //
867 | // override fun getRequestProperty(key: String?): String {
868 | // return super.getRequestProperty(key)
869 | // }
870 | //
871 | // override fun getOutputStream(): OutputStream {
872 | // return super.getOutputStream()
873 | // }
874 | //
875 | // override fun getHeaderFieldLong(name: String?, Default: Long): Long {
876 | // return super.getHeaderFieldLong(name, Default)
877 | // }
878 | //
879 | // override fun getHeaderField(n: Int): String {
880 | // return super.getHeaderField(n)
881 | // }
882 | //
883 | // override fun usingProxy(): Boolean {
884 | // TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
885 | // }
886 | //
887 | // override fun getHeaderFieldKey(n: Int): String {
888 | // return super.getHeaderFieldKey(n)
889 | // }
890 | //
891 | // override fun setInstanceFollowRedirects(followRedirects: Boolean) {
892 | // super.setInstanceFollowRedirects(followRedirects)
893 | // }
894 | //
895 | // override fun getHeaderFieldDate(name: String?, Default: Long): Long {
896 | // return super.getHeaderFieldDate(name, Default)
897 | // }
898 | //
899 | // override fun setChunkedStreamingMode(chunklen: Int) {
900 | // super.setChunkedStreamingMode(chunklen)
901 | // }
902 | //
903 | // override fun getPermission(): Permission {
904 | // return super.getPermission()
905 | // }
906 | //
907 | // override fun getInstanceFollowRedirects(): Boolean {
908 | // return super.getInstanceFollowRedirects()
909 | // }
910 | //
911 | // override fun getRequestMethod(): String {
912 | // return super.getRequestMethod()
913 | // }
914 | //
915 | // override fun getErrorStream(): InputStream {
916 | // return super.getErrorStream()
917 | // }
918 | //
919 | // override fun getResponseMessage(): String {
920 | // return super.getResponseMessage()
921 | // }
922 | //
923 | // override fun setFixedLengthStreamingMode(contentLength: Int) {
924 | // super.setFixedLengthStreamingMode(contentLength)
925 | // }
926 | //
927 | // override fun setFixedLengthStreamingMode(contentLength: Long) {
928 | // super.setFixedLengthStreamingMode(contentLength)
929 | // }
930 | //
931 | // override fun disconnect() {
932 | // TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
933 | // }
934 | //
935 | // override fun setRequestMethod(method: String?) {
936 | // super.setRequestMethod(method)
937 | // }
938 | //
939 | // override fun getResponseCode(): Int {
940 | // return super.getResponseCode()
941 | // }
942 | //
943 | // override fun equals(other: Any?): Boolean {
944 | // return super.equals(other)
945 | // }
946 | //
947 | // override fun hashCode(): Int {
948 | // return super.hashCode()
949 | // }
950 | //
951 | //}
952 |
--------------------------------------------------------------------------------