├── .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 | --------------------------------------------------------------------------------