├── 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 │ │ ├── java │ │ └── com │ │ │ └── android │ │ │ └── jesse │ │ │ └── privacymonitor │ │ │ ├── MainActivity.kt │ │ │ └── Test.java │ │ └── AndroidManifest.xml ├── privacyTraceConfig.txt ├── proguard-rules.pro └── build.gradle ├── collect ├── consumer-rules.pro ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── android │ │ └── jesse │ │ └── collect │ │ ├── ExcelBuildDataListener.java │ │ ├── bean │ │ └── PrivacyCollectBean.java │ │ ├── PrivacyCollect.java │ │ └── util │ │ └── ExcelUtil.java ├── proguard-rules.pro └── build.gradle ├── plugin ├── .gitignore ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── gradle-plugins │ │ │ └── com.android.jesse.privacy-monitor.properties │ │ ├── groovy │ │ └── com │ │ │ └── android │ │ │ └── jesse │ │ │ ├── extension │ │ │ └── TraceExtension.groovy │ │ │ ├── plugin │ │ │ └── TracePlugin.groovy │ │ │ ├── transforms │ │ │ ├── TraceTransform.groovy │ │ │ └── BaseTransform.groovy │ │ │ └── visitor │ │ │ ├── TraceClassVisitor.groovy │ │ │ └── MonitorDefaultMethodVisitor.groovy │ │ └── java │ │ └── com │ │ └── android │ │ └── jesse │ │ ├── TraceBuildConfig.java │ │ ├── Log.java │ │ └── Util.java ├── build.gradle └── maven.gradle ├── collect-noop ├── consumer-rules.pro ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── android │ │ └── jesse │ │ └── collect │ │ └── PrivacyCollect.java ├── build.gradle └── proguard-rules.pro ├── img ├── build_demo.jpg └── excel_demo.jpg ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /collect/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /collect-noop/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /collect/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /collect-noop/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /img/build_demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/img/build_demo.jpg -------------------------------------------------------------------------------- /img/excel_demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/img/excel_demo.jpg -------------------------------------------------------------------------------- /collect/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | collect 3 | 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':plugin', ':collect', ':collect-noop' 2 | rootProject.name='PrivacyMonitorAndroid' 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PrivacyMonitorAndroid 3 | 4 | -------------------------------------------------------------------------------- /collect-noop/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | collect-noop 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /plugin/src/main/resources/META-INF/gradle-plugins/com.android.jesse.privacy-monitor.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.android.jesse.plugin.TracePlugin -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /collect/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /collect-noop/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesse505/PrivacyMonitorAndroid/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/android/jesse/extension/TraceExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.android.jesse.extension 2 | 3 | class TraceExtension { 4 | boolean enable = false 5 | boolean logEnable = true 6 | String traceConfigFile 7 | } -------------------------------------------------------------------------------- /app/privacyTraceConfig.txt: -------------------------------------------------------------------------------- 1 | -tracemethod java/net/NetworkInterface.getHardwareAddress 2 | -tracemethod android/app/ActivityManager.getRunningTasks 3 | 4 | -keeppackage com/android/jesse/collect 5 | -keepclass com/android/jesse/collect/PrivacyCollect -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Dec 10 14:27:02 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | /repo 16 | .idea -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /collect/src/main/java/com/android/jesse/collect/ExcelBuildDataListener.java: -------------------------------------------------------------------------------- 1 | package com.android.jesse.collect; 2 | 3 | import com.android.jesse.collect.bean.PrivacyCollectBean; 4 | 5 | import java.util.List; 6 | 7 | public interface ExcelBuildDataListener { 8 | List buildData(int sheetIndex, PrivacyCollectBean bean); 9 | } 10 | -------------------------------------------------------------------------------- /plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply plugin: 'maven' 3 | 4 | apply from: 'maven.gradle' 5 | 6 | dependencies { 7 | compile gradleApi() //gradle sdk 8 | compile localGroovy() //groovy sdk 9 | compileOnly 'com.android.tools.build:gradle:3.5.4' 10 | } 11 | 12 | repositories { 13 | jcenter() 14 | } 15 | -------------------------------------------------------------------------------- /collect-noop/src/main/java/com/android/jesse/collect/PrivacyCollect.java: -------------------------------------------------------------------------------- 1 | package com.android.jesse.collect; 2 | 3 | import android.content.Context; 4 | 5 | public class PrivacyCollect { 6 | 7 | 8 | public static void stopCollect(Context context) { 9 | } 10 | 11 | public static void appendData(String monitorMethod, String privacyMethod) { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/maven.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven' 2 | 3 | uploadArchives { 4 | repositories.mavenDeployer { 5 | //本地仓库路径,以放到项目根目录下的 repo 的文件夹为例 6 | repository(url: uri('../repo')) 7 | 8 | //groupId ,自行定义 9 | pom.groupId = 'com.android' 10 | 11 | //artifactId,自行定义 12 | pom.artifactId = 'jesse.plugin' 13 | 14 | //插件版本号 15 | pom.version = '1.0.18' 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /collect/src/main/java/com/android/jesse/collect/bean/PrivacyCollectBean.java: -------------------------------------------------------------------------------- 1 | package com.android.jesse.collect.bean; 2 | 3 | public class PrivacyCollectBean { 4 | public String monitorMethod; 5 | public String monitorStackTrace; 6 | public String privacyMethod; 7 | 8 | public PrivacyCollectBean(String monitorMethod, String monitorStackTrace, String privacyMethod) { 9 | this.monitorMethod = monitorMethod; 10 | this.monitorStackTrace = monitorStackTrace; 11 | this.privacyMethod = privacyMethod; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/jesse/privacymonitor/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.android.jesse.privacymonitor 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.android.jesse.collect.PrivacyCollect 6 | import kotlinx.android.synthetic.main.activity_main.* 7 | 8 | class MainActivity : AppCompatActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | setContentView(R.layout.activity_main) 13 | 14 | Test.getMacAddressByInterface() 15 | 16 | tvConfirm.setOnClickListener { 17 | PrivacyCollect.stopCollect(this) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/android/jesse/plugin/TracePlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.android.jesse.plugin 2 | 3 | import com.android.build.gradle.AppExtension 4 | import com.android.jesse.Log 5 | import com.android.jesse.extension.TraceExtension 6 | import com.android.jesse.transforms.TraceTransform 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.Project 9 | 10 | class TracePlugin implements Plugin { 11 | 12 | @Override 13 | void apply(Project project) { 14 | 15 | TraceExtension traceExtension = project.extensions.create('privacyTrace', TraceExtension) 16 | 17 | //注册 18 | AppExtension appExtension = project.extensions.findByType(AppExtension.class) 19 | appExtension.registerTransform(new TraceTransform(traceExtension)) 20 | } 21 | } -------------------------------------------------------------------------------- /collect-noop/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | android { 5 | compileSdkVersion 30 6 | buildToolsVersion "30.0.3" 7 | 8 | 9 | defaultConfig { 10 | minSdkVersion 19 11 | targetSdkVersion 30 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | consumerProguardFiles 'consumer-rules.pro' 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | implementation 'androidx.appcompat:appcompat:1.0.2' 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /collect/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 | -------------------------------------------------------------------------------- /collect-noop/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 | -------------------------------------------------------------------------------- /collect/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.3" 8 | 9 | 10 | defaultConfig { 11 | minSdkVersion 19 12 | targetSdkVersion 30 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | consumerProguardFiles 'consumer-rules.pro' 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | 31 | implementation 'androidx.appcompat:appcompat:1.0.2' 32 | implementation("net.sourceforge.jexcelapi:jxl:2.6.12") { 33 | exclude module: 'log4j' 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | -------------------------------------------------------------------------------- /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 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | apply plugin: 'com.android.jesse.privacy-monitor' 8 | 9 | privacyTrace { 10 | enable = true 11 | logEnable = true 12 | traceConfigFile = "${project.projectDir}/privacyTraceConfig.txt" 13 | } 14 | 15 | android { 16 | compileSdkVersion 30 17 | buildToolsVersion "30.0.3" 18 | defaultConfig { 19 | applicationId "com.android.jesse.privacymonitor" 20 | minSdkVersion 19 21 | targetSdkVersion 30 22 | versionCode 1 23 | versionName "1.0" 24 | } 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 36 | implementation 'androidx.appcompat:appcompat:1.0.2' 37 | implementation 'androidx.core:core-ktx:1.0.2' 38 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 39 | 40 | debugImplementation 'com.github.Jesse505.PrivacyMonitorAndroid:collect:1.0.0' 41 | releaseImplementation 'com.github.Jesse505.PrivacyMonitorAndroid:collect-noop:1.0.0' 42 | // implementation project(':collect') 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/jesse/privacymonitor/Test.java: -------------------------------------------------------------------------------- 1 | package com.android.jesse.privacymonitor; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.util.Log; 7 | 8 | import androidx.annotation.RequiresApi; 9 | 10 | import java.net.NetworkInterface; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public class Test { 15 | public static String getMacAddressByInterface() { 16 | try { 17 | List all = Collections.list(NetworkInterface.getNetworkInterfaces()); 18 | for (NetworkInterface nif : all) { 19 | if ("wlan0".equalsIgnoreCase(nif.getName())) { 20 | byte[] macBytes = nif.getHardwareAddress(); 21 | if (macBytes == null) { 22 | return ""; 23 | } 24 | 25 | StringBuilder res1 = new StringBuilder(); 26 | for (byte b : macBytes) { 27 | res1.append(String.format("%02X:", b)); 28 | } 29 | 30 | if (res1.length() > 0) { 31 | res1.deleteCharAt(res1.length() - 1); 32 | } 33 | return res1.toString(); 34 | } 35 | } 36 | 37 | } catch (Exception e) { 38 | //ignore 39 | } 40 | return null; 41 | } 42 | 43 | @RequiresApi(api = Build.VERSION_CODES.Q) 44 | public static boolean isForegroundApp(Context context, String pkgName) { 45 | ActivityManager am = context.getSystemService(ActivityManager.class); 46 | Log.i("", ""); 47 | List tasks = am.getRunningTasks(1); 48 | List runningAppProcesses = am.getRunningAppProcesses(); 49 | return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PrivacyMonitorAndroid 2 | 3 | Android 隐私合规检测方案,用于快速找到隐私api调用所在堆栈,解决Android隐私合规问题。通过gradle plugin + ASM技术来实现可配置范围的方法插桩来统计隐私api所调用的堆栈信息,并在本地Excel文件中提供友好的统计展示,方便排查隐私合规问题。 4 | 5 | ## 实现效果预览 6 | 7 | ![excel_demo](img/excel_demo.jpg) 8 | 9 | ## 项目组成 10 | 11 | 1. **plugin:** Gradle 插件,在隐私api调用所在方法执行编译期插桩,织入数据收集代码,便于后面数据的统计和处理。 12 | 2. **collect:** Android Library,隐私api的数据收集,统计和处理,并将整合的信息保存到本地Excel文件中。 13 | 3. **collect-noop:** Android Library,release包下依赖的是noop包,里面没有任何逻辑,只是collet项目中的壳方法,避免将collect中的代码带到线上,也不会增加线上包的大小。 14 | 15 | ## 快速使用 16 | 17 | 1. **在项目根目录的build.gradle 中添加:** 18 | 19 | ```groovy 20 | buildscript { 21 | repositories { 22 | maven { url 'https://jitpack.io' } 23 | } 24 | dependencies { 25 | classpath 'com.github.Jesse505.PrivacyMonitorAndroid:plugin:1.0.0' 26 | } 27 | } 28 | 29 | allprojects { 30 | repositories { 31 | maven { url 'https://jitpack.io' } 32 | } 33 | } 34 | ``` 35 | 36 | 2. **在app的build.gradle 中添加** 37 | 38 | ```groovy 39 | apply plugin: 'com.android.jesse.privacy-monitor' 40 | privacyTrace { 41 | enable = true //插件开关,建议上线前关闭 42 | logEnable = true //插桩日志开关 43 | traceConfigFile = "${project.projectDir}/privacyTraceConfig.txt" 44 | } 45 | 46 | dependencies { 47 | debugImplementation 'com.github.Jesse505.PrivacyMonitorAndroid:collect:1.0.0' 48 | releaseImplementation 'com.github.Jesse505.PrivacyMonitorAndroid:collect-noop:1.0.0' 49 | } 50 | ``` 51 | 52 | 3. **在app module的根目录下创建一个名叫`privacyTraceConfig.txt`的配置文件,并在里面对插桩范围进行配置,下面是配置实例** 53 | 54 | ```java 55 | #配置需要统计的隐私的api 56 | -tracemethod java/net/NetworkInterface.getHardwareAddress 57 | -tracemethod android/app/ActivityManager.getRunningTasks 58 | #配置无需扫描插桩的包 59 | -keeppackage com/android/jesse/collect 60 | #配置无需扫描插桩的类 61 | -keepclass com/android/jesse/collect/PrivacyCollect 62 | ``` 63 | 64 | 4. **在同意隐私协议政策后,调用PrivacyCollect.stopCollect()方法,然后Rebuild项目** 65 | 66 | 可以看到如下build信息中的插桩日志 67 | 68 | ![build_demo.jpg](img/build_demo.jpg) 69 | 70 | 5. **运行app,同意隐私政策后把/storage/emulated/0/Android/data/com.android.jesse.privacymonitor/files/privacy_result.xls 文件导出,里面的内容就是预览显示的效果。** 71 | 72 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/android/jesse/transforms/TraceTransform.groovy: -------------------------------------------------------------------------------- 1 | package com.android.jesse.transforms 2 | 3 | import com.android.jesse.TraceBuildConfig 4 | import com.android.jesse.extension.TraceExtension 5 | import com.android.jesse.visitor.TraceClassVisitor 6 | import org.objectweb.asm.ClassReader 7 | import org.objectweb.asm.ClassVisitor 8 | import org.objectweb.asm.ClassWriter 9 | 10 | class TraceTransform extends BaseTransform { 11 | 12 | private HashSet exclude = new HashSet<>() 13 | private TraceExtension traceExtension 14 | private TraceBuildConfig traceBuildConfig 15 | 16 | TraceTransform(TraceExtension traceExtension) { 17 | this.traceExtension = traceExtension 18 | } 19 | 20 | @Override 21 | boolean isShouldModify(String className) { 22 | //不需要修改字节码的一些包前缀 23 | exclude.add('android/support') 24 | exclude.add('androidx') 25 | 26 | traceBuildConfig?.mBlackPackageList?.each { 27 | String blackPackage -> exclude.add(blackPackage) 28 | } 29 | 30 | Iterator iterator = exclude.iterator() 31 | while (iterator.hasNext()) { 32 | String packageName = iterator.next() 33 | if (className.startsWith(packageName)) { 34 | return false 35 | } 36 | } 37 | 38 | //自动生成的一些文件不需要修改字节码 39 | if (className.contains('R$') || 40 | className.contains('R2$') || 41 | className.contains('R.class') || 42 | className.contains('R2.class') || 43 | className.contains('BuildConfig.class')) { 44 | return false 45 | } 46 | 47 | //配置的一些class不需要修改字节码 48 | for (int i = 0; i < traceBuildConfig?.mBlackClassList?.size(); i++) { 49 | if (traceBuildConfig?.mBlackClassList?.get(i) == 50 | className.replace(".class", "")) { 51 | return false 52 | } 53 | } 54 | 55 | return true 56 | } 57 | 58 | @Override 59 | byte[] modifyClass(byte[] srcClass) throws IOException { 60 | ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS) 61 | ClassVisitor classVisitor = new TraceClassVisitor(classWriter, traceBuildConfig, isLogEnable()) 62 | ClassReader cr = new ClassReader(srcClass) 63 | cr.accept(classVisitor, ClassReader.SKIP_FRAMES) 64 | return classWriter.toByteArray() 65 | } 66 | 67 | @Override 68 | String getName() { 69 | return "TraceTransform" 70 | } 71 | 72 | @Override 73 | void onBeforeTransform() { 74 | super.onBeforeTransform() 75 | final TraceBuildConfig traceConfig = initConfig() 76 | traceConfig.parseTraceConfigFile() 77 | traceBuildConfig = traceConfig 78 | } 79 | 80 | @Override 81 | boolean isModifyEnable() { 82 | return traceExtension.enable 83 | } 84 | 85 | @Override 86 | boolean isLogEnable() { 87 | return traceExtension.logEnable 88 | } 89 | 90 | private TraceBuildConfig initConfig() { 91 | return new TraceBuildConfig(traceExtension.traceConfigFile) 92 | } 93 | } -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/android/jesse/visitor/TraceClassVisitor.groovy: -------------------------------------------------------------------------------- 1 | package com.android.jesse.visitor 2 | 3 | import com.android.jesse.Log 4 | import com.android.jesse.TraceBuildConfig 5 | import org.objectweb.asm.* 6 | 7 | class TraceClassVisitor extends ClassVisitor implements Opcodes { 8 | 9 | private final 10 | static String SDK_API_CLASS = "com/android/jesse/collect/PrivacyCollect" 11 | 12 | TraceBuildConfig mTraceBuildConfig 13 | String traceClassName 14 | boolean mLogEnable 15 | 16 | TraceClassVisitor(final ClassVisitor classVisitor, TraceBuildConfig traceBuildConfig, boolean logEnable) { 17 | super(Opcodes.ASM6, classVisitor) 18 | mTraceBuildConfig = traceBuildConfig 19 | mLogEnable = logEnable 20 | } 21 | 22 | @Override 23 | void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 24 | super.visit(version, access, name, signature, superName, interfaces) 25 | traceClassName = name 26 | } 27 | 28 | @Override 29 | MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 30 | 31 | Set monitorMethodSet = new HashSet<>() 32 | 33 | 34 | MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions) 35 | methodVisitor = new MonitorDefaultMethodVisitor(methodVisitor, access, name, desc) { 36 | @Override 37 | void visitMethodInsn(int opcode, String owner, String name1, String desc1, boolean itf) { 38 | 39 | if (!mTraceBuildConfig.getmTraceMents()?.isEmpty()) { 40 | for (int i = 0; i < mTraceBuildConfig.getmTraceMents().size(); i++) { 41 | TraceBuildConfig.TraceMent replaceMent = mTraceBuildConfig.getmTraceMents().get(i) 42 | if (owner == replaceMent.getSrcClass() && name1 == replaceMent.getSrcMethodName()) { 43 | 44 | monitorMethodSet.add("${owner.replace("/", ".")}#$name1") 45 | 46 | if (mLogEnable) { 47 | Log.i("TraceClassVisitor", "在${traceClassName}类中调用了" + 48 | "${replaceMent.getSrcClass()}类的${replaceMent.getSrcMethodName()}方法") 49 | } 50 | } 51 | } 52 | } 53 | super.visitMethodInsn(opcode, owner, name1, desc1, itf) 54 | } 55 | 56 | @Override 57 | protected void onMethodExit(int opcode) { 58 | super.onMethodExit(opcode) 59 | 60 | for (int i = 0; i < monitorMethodSet.size(); i++) { 61 | methodVisitor.visitLdcInsn(String.valueOf("${traceClassName.replace("/", ".")}#$name")) 62 | methodVisitor.visitLdcInsn(String.valueOf(monitorMethodSet[i])) 63 | methodVisitor.visitMethodInsn(INVOKESTATIC, SDK_API_CLASS, "appendData", "(Ljava/lang/String;Ljava/lang/String;)V", false) 64 | } 65 | } 66 | } 67 | return methodVisitor 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /collect/src/main/java/com/android/jesse/collect/PrivacyCollect.java: -------------------------------------------------------------------------------- 1 | package com.android.jesse.collect; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | import android.util.Log; 6 | 7 | import com.android.jesse.collect.bean.PrivacyCollectBean; 8 | import com.android.jesse.collect.util.ExcelUtil; 9 | 10 | import java.io.File; 11 | import java.text.MessageFormat; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class PrivacyCollect { 16 | 17 | private static final String EXCEL_FILE_NAME = "privacy_result.xls"; 18 | 19 | static List collectBeans = new ArrayList<>(); 20 | 21 | public static void stopCollect(Context context) { 22 | String filePath = context.getExternalFilesDir(null) + File.separator + EXCEL_FILE_NAME; 23 | Log.i("zyf", "保存地址 > " + filePath); 24 | ArrayList sheetNameList = new ArrayList<>(); 25 | sheetNameList.add("隐私合规"); 26 | ArrayList colNameList = new ArrayList<>(); 27 | colNameList.add("隐私函数名"); 28 | colNameList.add("调用堆栈"); 29 | ArrayList> colList = new ArrayList<>(); 30 | colList.add(colNameList); 31 | ExcelUtil.initExcel(filePath, sheetNameList, colList); 32 | 33 | ArrayList privacyCollectBeans = new ArrayList<>(); 34 | privacyCollectBeans.addAll(collectBeans); 35 | ExcelUtil.writeObjListToExcel(filePath, 0, privacyCollectBeans, new ExcelBuildDataListener() { 36 | 37 | @Override 38 | public List buildData(int sheetIndex, PrivacyCollectBean bean) { 39 | ArrayList strings = new ArrayList<>(); 40 | if (null != bean) { 41 | strings.add(bean.privacyMethod); 42 | strings.add(bean.monitorStackTrace); 43 | } 44 | return strings; 45 | } 46 | }); 47 | collectBeans.clear(); 48 | } 49 | 50 | public static void appendData(String monitorMethod, String privacyMethod) { 51 | Log.i("zyf", "在" + monitorMethod + "方法中调用了" + privacyMethod + "方法"); 52 | collectBeans.add(new PrivacyCollectBean(monitorMethod, getStackTrace(), privacyMethod)); 53 | } 54 | 55 | private static String getStackTrace() { 56 | StringBuilder stringBuilder = new StringBuilder(); 57 | StackTraceElement[] elements = Thread.currentThread().getStackTrace(); 58 | if (elements != null && elements.length != 0) { 59 | boolean needAppend = false; 60 | for (int i = 0; i < elements.length; i++) { 61 | 62 | StackTraceElement element = elements[i]; 63 | if (TextUtils.equals(element.getClassName(), PrivacyCollect.class.getName())) { 64 | needAppend = true; 65 | continue; 66 | } 67 | if (needAppend) { 68 | if (stringBuilder.length() > 0) { 69 | stringBuilder.append(System.getProperty("line.separator")); 70 | } 71 | stringBuilder.append( 72 | MessageFormat.format( 73 | "{0}.{1}() {2}", element.getClassName(), element.getMethodName(), element.getLineNumber() 74 | ) 75 | ); 76 | } 77 | } 78 | } 79 | return stringBuilder.toString(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/android/jesse/visitor/MonitorDefaultMethodVisitor.groovy: -------------------------------------------------------------------------------- 1 | package com.android.jesse.visitor 2 | 3 | import org.objectweb.asm.* 4 | import org.objectweb.asm.commons.AdviceAdapter 5 | 6 | class MonitorDefaultMethodVisitor extends AdviceAdapter { 7 | 8 | MonitorDefaultMethodVisitor(MethodVisitor mv, int access, String name, String desc) { 9 | super(Opcodes.ASM6, mv, access, name, desc) 10 | } 11 | 12 | /** 13 | * 表示 ASM 开始扫描这个方法 14 | */ 15 | @Override 16 | void visitCode() { 17 | super.visitCode() 18 | } 19 | 20 | @Override 21 | void visitMethodInsn(int opcode, String owner, String name, String desc) { 22 | super.visitMethodInsn(opcode, owner, name, desc) 23 | } 24 | 25 | @Override 26 | void visitAttribute(Attribute attribute) { 27 | super.visitAttribute(attribute) 28 | } 29 | 30 | /** 31 | * 表示方法输出完毕 32 | */ 33 | @Override 34 | void visitEnd() { 35 | super.visitEnd() 36 | } 37 | 38 | @Override 39 | void visitFieldInsn(int opcode, String owner, String name, String desc) { 40 | super.visitFieldInsn(opcode, owner, name, desc) 41 | } 42 | 43 | @Override 44 | void visitIincInsn(int var, int increment) { 45 | super.visitIincInsn(var, increment) 46 | } 47 | 48 | @Override 49 | void visitIntInsn(int i, int i1) { 50 | super.visitIntInsn(i, i1) 51 | } 52 | 53 | /** 54 | * 该方法是 visitEnd 之前调用的方法,可以反复调用。用以确定类方法在执行时候的堆栈大小。 55 | * @param maxStack 56 | * @param maxLocals 57 | */ 58 | @Override 59 | void visitMaxs(int maxStack, int maxLocals) { 60 | super.visitMaxs(maxStack, maxLocals) 61 | } 62 | 63 | @Override 64 | void visitVarInsn(int opcode, int var) { 65 | super.visitVarInsn(opcode, var) 66 | } 67 | 68 | @Override 69 | void visitJumpInsn(int opcode, Label label) { 70 | super.visitJumpInsn(opcode, label) 71 | } 72 | 73 | @Override 74 | void visitLookupSwitchInsn(Label label, int[] ints, Label[] labels) { 75 | super.visitLookupSwitchInsn(label, ints, labels) 76 | } 77 | 78 | @Override 79 | void visitMultiANewArrayInsn(String s, int i) { 80 | super.visitMultiANewArrayInsn(s, i) 81 | } 82 | 83 | @Override 84 | void visitTableSwitchInsn(int i, int i1, Label label, Label[] labels) { 85 | super.visitTableSwitchInsn(i, i1, label, labels) 86 | } 87 | 88 | @Override 89 | void visitTryCatchBlock(Label label, Label label1, Label label2, String s) { 90 | super.visitTryCatchBlock(label, label1, label2, s) 91 | } 92 | 93 | @Override 94 | void visitTypeInsn(int opcode, String s) { 95 | super.visitTypeInsn(opcode, s) 96 | } 97 | 98 | @Override 99 | void visitLocalVariable(String s, String s1, String s2, Label label, Label label1, int i) { 100 | super.visitLocalVariable(s, s1, s2, label, label1, i) 101 | } 102 | 103 | @Override 104 | void visitInsn(int opcode) { 105 | super.visitInsn(opcode) 106 | } 107 | 108 | @Override 109 | AnnotationVisitor visitAnnotation(String s, boolean b) { 110 | return super.visitAnnotation(s, b) 111 | } 112 | 113 | @Override 114 | protected void onMethodEnter() { 115 | super.onMethodEnter() 116 | } 117 | 118 | @Override 119 | protected void onMethodExit(int opcode) { 120 | super.onMethodExit(opcode) 121 | } 122 | } -------------------------------------------------------------------------------- /plugin/src/main/java/com/android/jesse/TraceBuildConfig.java: -------------------------------------------------------------------------------- 1 | package com.android.jesse; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class TraceBuildConfig { 8 | 9 | private static final String TAG = "TraceBuildConfig"; 10 | 11 | private final String mTraceConfigFile; 12 | 13 | private final List mTraceMents; 14 | private final List mBlackClassList; 15 | private final List mBlackPackageList; 16 | 17 | 18 | public TraceBuildConfig(String traceConfigFile) { 19 | this.mTraceConfigFile = traceConfigFile; 20 | mTraceMents = new ArrayList<>(); 21 | mBlackClassList = new ArrayList<>(); 22 | mBlackPackageList = new ArrayList<>(); 23 | } 24 | 25 | public List getmTraceMents() { 26 | return mTraceMents; 27 | } 28 | 29 | public List getmBlackClassList() { 30 | return mBlackClassList; 31 | } 32 | 33 | public List getmBlackPackageList() { 34 | return mBlackPackageList; 35 | } 36 | 37 | public void parseTraceConfigFile() { 38 | if (Util.isNullOrNil(mTraceConfigFile)) { 39 | Log.w(TAG, "traceConfigFile not config"); 40 | return; 41 | } 42 | File traceConfigFile = new File(mTraceConfigFile); 43 | if (!traceConfigFile.exists()) { 44 | Log.w(TAG, "trace config file not exist %s", traceConfigFile.getAbsoluteFile()); 45 | return; 46 | } 47 | String traceConfigStr = Util.readFileAsString(traceConfigFile.getAbsolutePath()); 48 | String[] configArray = traceConfigStr.split("\n"); 49 | 50 | for (String config : configArray) { 51 | if (config.startsWith("-keepclass ")) { 52 | mBlackClassList.add(config.replace("-keepclass ", "")); 53 | } else if (config.startsWith("-keeppackage ")) { 54 | mBlackPackageList.add(config.replace("-keeppackage ", "")); 55 | } else if (config.startsWith("-tracemethod ")) { 56 | mTraceMents.add(getTraceMent(config.replace("-tracemethod ", ""))); 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * 解析被检测跟踪的方法配置 63 | * 64 | * @param traceStr 65 | * @return 66 | */ 67 | private TraceMent getTraceMent(String traceStr) { 68 | TraceMent traceMent = new TraceMent(); 69 | String[] srcStr = traceStr.split("\\."); 70 | if (srcStr.length == 2) { 71 | traceMent.setSrcClass(srcStr[0]); 72 | traceMent.setSrcMethodName(srcStr[1]); 73 | } 74 | return traceMent; 75 | } 76 | 77 | public static class TraceMent { 78 | private String srcClass; 79 | private String srcMethodName; 80 | 81 | public String getSrcClass() { 82 | return srcClass; 83 | } 84 | 85 | public void setSrcClass(String srcClass) { 86 | this.srcClass = srcClass; 87 | } 88 | 89 | public String getSrcMethodName() { 90 | return srcMethodName; 91 | } 92 | 93 | public void setSrcMethodName(String srcMethodName) { 94 | this.srcMethodName = srcMethodName; 95 | } 96 | 97 | @Override 98 | public String toString() { 99 | return "TraceMent{" + 100 | "srcClass='" + srcClass + '\'' + 101 | ", srcMethodName='" + srcMethodName + '\'' + 102 | '}'; 103 | } 104 | 105 | @Override 106 | public int hashCode() { 107 | return super.hashCode(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/android/jesse/Log.java: -------------------------------------------------------------------------------- 1 | package com.android.jesse; 2 | 3 | 4 | import java.io.PrintWriter; 5 | import java.io.StringWriter; 6 | 7 | public class Log { 8 | 9 | private static LogImp debugLog = new LogImp() { 10 | 11 | @Override 12 | public void v(final String tag, final String msg, final Object... obj) { 13 | String log = obj == null ? msg : String.format(msg, obj); 14 | System.out.println(String.format("[VERBOSE][%s]%s", tag, log)); 15 | } 16 | 17 | @Override 18 | public void i(final String tag, final String msg, final Object... obj) { 19 | String log = obj == null ? msg : String.format(msg, obj); 20 | System.out.println(String.format("[INFO][%s]%s", tag, log)); 21 | } 22 | 23 | @Override 24 | public void d(final String tag, final String msg, final Object... obj) { 25 | String log = obj == null ? msg : String.format(msg, obj); 26 | System.out.println(String.format("[DEBUG][%s]%s", tag, log)); 27 | } 28 | 29 | @Override 30 | public void w(final String tag, final String msg, final Object... obj) { 31 | String log = obj == null ? msg : String.format(msg, obj); 32 | System.out.println(String.format("[WARN][%s]%s", tag, log)); 33 | } 34 | 35 | @Override 36 | public void e(final String tag, final String msg, final Object... obj) { 37 | String log = obj == null ? msg : String.format(msg, obj); 38 | System.out.println(String.format("[ERROR][%s]%s", tag, log)); 39 | } 40 | 41 | @Override 42 | public void printErrStackTrace(String tag, Throwable tr, String format, Object... obj) { 43 | String log = obj == null ? format : String.format(format, obj); 44 | if (log == null) { 45 | log = ""; 46 | } 47 | StringWriter sw = new StringWriter(); 48 | PrintWriter pw = new PrintWriter(sw); 49 | tr.printStackTrace(pw); 50 | log += " " + sw.toString(); 51 | System.out.println(String.format("[ERROR][%s]%s", tag, log)); 52 | } 53 | }; 54 | 55 | private static LogImp logImp = debugLog; 56 | 57 | private Log() { 58 | } 59 | 60 | public static void setLogImp(LogImp imp) { 61 | logImp = imp; 62 | } 63 | 64 | public static LogImp getImpl() { 65 | return logImp; 66 | } 67 | 68 | public static void v(final String tag, final String msg, final Object... obj) { 69 | if (logImp != null) { 70 | logImp.v(tag, msg, obj); 71 | } 72 | } 73 | 74 | public static void e(final String tag, final String msg, final Object... obj) { 75 | if (logImp != null) { 76 | logImp.e(tag, msg, obj); 77 | } 78 | } 79 | 80 | public static void w(final String tag, final String msg, final Object... obj) { 81 | if (logImp != null) { 82 | logImp.w(tag, msg, obj); 83 | } 84 | } 85 | 86 | public static void i(final String tag, final String msg, final Object... obj) { 87 | if (logImp != null) { 88 | logImp.i(tag, msg, obj); 89 | } 90 | } 91 | 92 | public static void d(final String tag, final String msg, final Object... obj) { 93 | if (logImp != null) { 94 | logImp.d(tag, msg, obj); 95 | } 96 | } 97 | 98 | public static void printErrStackTrace(String tag, Throwable tr, final String format, final Object... obj) { 99 | if (logImp != null) { 100 | logImp.printErrStackTrace(tag, tr, format, obj); 101 | } 102 | } 103 | 104 | public interface LogImp { 105 | 106 | void v(final String tag, final String msg, final Object... obj); 107 | 108 | void i(final String tag, final String msg, final Object... obj); 109 | 110 | void w(final String tag, final String msg, final Object... obj); 111 | 112 | void d(final String tag, final String msg, final Object... obj); 113 | 114 | void e(final String tag, final String msg, final Object... obj); 115 | 116 | void printErrStackTrace(String tag, Throwable tr, final String format, final Object... obj); 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/android/jesse/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Tencent is pleased to support the open source community by making wechat-matrix available. 3 | * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the BSD 3-Clause License (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://opensource.org/licenses/BSD-3-Clause 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software 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.android.jesse; 18 | 19 | import java.io.Closeable; 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.InputStreamReader; 26 | import java.io.Reader; 27 | import java.lang.reflect.Field; 28 | import java.util.zip.ZipEntry; 29 | import java.util.zip.ZipException; 30 | import java.util.zip.ZipFile; 31 | import java.util.zip.ZipOutputStream; 32 | 33 | 34 | public final class Util { 35 | private static final String TAG = "Util"; 36 | public static final int BUFFER_SIZE = 16384; 37 | 38 | private Util() { 39 | } 40 | 41 | public static void addZipEntry(ZipOutputStream zipOutputStream, ZipEntry zipEntry, InputStream inputStream) throws Exception { 42 | try { 43 | zipOutputStream.putNextEntry(zipEntry); 44 | byte[] buffer = new byte[BUFFER_SIZE]; 45 | int length = -1; 46 | while ((length = inputStream.read(buffer, 0, buffer.length)) != -1) { 47 | zipOutputStream.write(buffer, 0, length); 48 | zipOutputStream.flush(); 49 | } 50 | } catch (ZipException e) { 51 | Log.e(TAG, "addZipEntry err!"); 52 | } finally { 53 | closeQuietly(inputStream); 54 | 55 | zipOutputStream.closeEntry(); 56 | } 57 | } 58 | 59 | public static boolean isNullOrNil(String str) { 60 | return str == null || str.isEmpty(); 61 | } 62 | 63 | public static Field getDeclaredFieldRecursive(Object clazz, String fieldName) throws NoSuchFieldException, ClassNotFoundException { 64 | Class realClazz = null; 65 | if (clazz instanceof String) { 66 | realClazz = Class.forName((String) clazz); 67 | } else if (clazz instanceof Class) { 68 | realClazz = (Class) clazz; 69 | } else { 70 | throw new IllegalArgumentException("Illegal clazz type: " + clazz.getClass()); 71 | } 72 | Class currClazz = realClazz; 73 | while (true) { 74 | try { 75 | Field field = currClazz.getDeclaredField(fieldName); 76 | field.setAccessible(true); 77 | return field; 78 | } catch (NoSuchFieldException e) { 79 | if (currClazz.equals(Object.class)) { 80 | throw e; 81 | } 82 | currClazz = currClazz.getSuperclass(); 83 | } 84 | } 85 | } 86 | 87 | public static boolean isRealZipOrJar(File input) { 88 | ZipFile zf = null; 89 | try { 90 | zf = new ZipFile(input); 91 | return true; 92 | } catch (Exception e) { 93 | return false; 94 | } finally { 95 | Util.closeQuietly(zf); 96 | } 97 | } 98 | 99 | 100 | /** 101 | * Close {@code target} quietly. 102 | * 103 | * @param obj 104 | * Object to be closed. 105 | */ 106 | public static void closeQuietly(Object obj) { 107 | if (obj == null) { 108 | return; 109 | } 110 | if (obj instanceof Closeable) { 111 | try { 112 | ((Closeable) obj).close(); 113 | } catch (Throwable ignored) { 114 | // ignore 115 | } 116 | } else if (obj instanceof AutoCloseable) { 117 | try { 118 | ((AutoCloseable) obj).close(); 119 | } catch (Throwable ignored) { 120 | // ignore 121 | } 122 | } else if (obj instanceof ZipFile) { 123 | try { 124 | ((ZipFile) obj).close(); 125 | } catch (Throwable ignored) { 126 | // ignore 127 | } 128 | } else { 129 | throw new IllegalArgumentException("obj " + obj + " is not closeable"); 130 | } 131 | } 132 | 133 | public static String readFileAsString(String filePath) { 134 | StringBuffer fileData = new StringBuffer(); 135 | Reader fileReader = null; 136 | InputStream inputStream = null; 137 | try { 138 | inputStream = new FileInputStream(filePath); 139 | fileReader = new InputStreamReader(inputStream, "UTF-8"); 140 | char[] buf = new char[BUFFER_SIZE]; 141 | int numRead = 0; 142 | while ((numRead = fileReader.read(buf)) != -1) { 143 | String readData = String.valueOf(buf, 0, numRead); 144 | fileData.append(readData); 145 | } 146 | } catch (Exception e) { 147 | Log.e(TAG, "file op readFileAsString e type:%s, e msg:%s, filePath:%s", 148 | e.getClass().getSimpleName(), e.getMessage(), filePath); 149 | 150 | } finally { 151 | try { 152 | closeQuietly(fileReader); 153 | closeQuietly(inputStream); 154 | } catch (Exception e) { 155 | Log.e(TAG, "file op readFileAsString close e type:%s, e msg:%s, filePath:%s", 156 | e.getClass().getSimpleName(), e.getMessage(), filePath); 157 | } 158 | } 159 | return fileData.toString(); 160 | } 161 | 162 | public static void copyFileUsingStream(File source, File dest) throws IOException { 163 | FileInputStream is = null; 164 | FileOutputStream os = null; 165 | File parent = dest.getParentFile(); 166 | if (parent != null && (!parent.exists())) { 167 | parent.mkdirs(); 168 | } 169 | try { 170 | is = new FileInputStream(source); 171 | os = new FileOutputStream(dest, false); 172 | 173 | byte[] buffer = new byte[BUFFER_SIZE]; 174 | int length; 175 | while ((length = is.read(buffer)) > 0) { 176 | os.write(buffer, 0, length); 177 | } 178 | } finally { 179 | closeQuietly(is); 180 | closeQuietly(os); 181 | } 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /collect/src/main/java/com/android/jesse/collect/util/ExcelUtil.java: -------------------------------------------------------------------------------- 1 | package com.android.jesse.collect.util; 2 | 3 | import com.android.jesse.collect.ExcelBuildDataListener; 4 | import com.android.jesse.collect.bean.PrivacyCollectBean; 5 | 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.util.List; 11 | 12 | import jxl.Workbook; 13 | import jxl.WorkbookSettings; 14 | import jxl.format.Alignment; 15 | import jxl.format.Border; 16 | import jxl.format.BorderLineStyle; 17 | import jxl.format.Colour; 18 | import jxl.write.Label; 19 | import jxl.write.WritableCell; 20 | import jxl.write.WritableCellFormat; 21 | import jxl.write.WritableFont; 22 | import jxl.write.WritableSheet; 23 | import jxl.write.WritableWorkbook; 24 | import jxl.write.WriteException; 25 | 26 | public class ExcelUtil { 27 | 28 | private static final String UTF8_ENCODING = "UTF-8"; 29 | 30 | private static WritableFont arial14font; 31 | private static WritableCellFormat arial14format; 32 | private static WritableFont arial10font; 33 | private static WritableCellFormat arial10format; 34 | private static WritableFont arial12font; 35 | private static WritableCellFormat arial12format; 36 | 37 | /** 38 | * 表格初始化 39 | * 40 | * @param filePath 文件路径 41 | * @param sheetNameList 表格名字列表 42 | * @param colNameList 列名字列表 43 | */ 44 | public static void initExcel(String filePath, List sheetNameList, List> colNameList) { 45 | format(); 46 | WritableWorkbook workbook = null; 47 | 48 | try { 49 | File file = new File(filePath); 50 | if (file.exists()) { 51 | file.deleteOnExit(); 52 | } 53 | if (!(file.getParentFile().exists())) { 54 | file.getParentFile().mkdirs(); 55 | } 56 | file.createNewFile(); 57 | 58 | workbook = Workbook.createWorkbook(file); 59 | 60 | if (null != sheetNameList) { 61 | for (int i = 0; i < sheetNameList.size(); i++) { 62 | //设置表格的名字 63 | WritableSheet sheet = workbook.createSheet(sheetNameList.get(i), i); 64 | //创建标题栏 65 | sheet.addCell((WritableCell) (new Label(0, 0, filePath, arial14format))); 66 | //设置第一行的数据 67 | if (i < colNameList.size()) { 68 | List colNames = colNameList.get(i); 69 | if (null != colNames) { 70 | for (int j = 0; j < colNames.size(); j++) { 71 | sheet.addCell(new Label(j, 0, colNames.get(j), arial10format)); 72 | } 73 | } 74 | } 75 | //设置行高 76 | sheet.setRowView(0, 340); 77 | } 78 | } 79 | workbook.write(); 80 | } catch (Exception e) { 81 | e.printStackTrace(); 82 | } finally { 83 | if (workbook != null) { 84 | try { 85 | workbook.close(); 86 | } catch (Exception e) { 87 | e.printStackTrace(); 88 | } 89 | } 90 | } 91 | } 92 | 93 | public static void writeObjListToExcel(String filePath, int sheetIndex, 94 | List collectBeanList, ExcelBuildDataListener listener) { 95 | if (collectBeanList == null || collectBeanList.isEmpty()) { 96 | return; 97 | } 98 | WritableWorkbook writebook = null; 99 | InputStream inputStream = null; 100 | 101 | try { 102 | WorkbookSettings workbookSettings = new WorkbookSettings(); 103 | workbookSettings.setEncoding(UTF8_ENCODING); 104 | inputStream = new FileInputStream(new File(filePath)); 105 | Workbook workbook = Workbook.getWorkbook(inputStream); 106 | writebook = workbook.createWorkbook(new File(filePath), workbook); 107 | WritableSheet sheet = writebook.getSheet(sheetIndex); 108 | for (int i = 0; i < collectBeanList.size(); i++) { 109 | if (null != listener) { 110 | List list = listener.buildData(sheetIndex, collectBeanList.get(i)); 111 | if (null != list) { 112 | for (int j = 0; j < list.size(); j++) { 113 | sheet.addCell(new Label(j, i + 1, list.get(j), arial12format)); 114 | if (list.get(j).length() <= 4) { 115 | //设置列宽 116 | sheet.setColumnView(i, list.get(j).length() + 8); 117 | } else { 118 | //设置列宽 119 | sheet.setColumnView(i, list.get(j).length() + 5); 120 | } 121 | } 122 | } 123 | } 124 | 125 | //设置行高 126 | sheet.setRowView(i + 1, 500); 127 | } 128 | writebook.write(); 129 | workbook.close(); 130 | } catch (Exception e) { 131 | e.printStackTrace(); 132 | } finally { 133 | if (writebook != null) { 134 | try { 135 | writebook.close(); 136 | } catch (Exception e) { 137 | e.printStackTrace(); 138 | } 139 | } 140 | if (inputStream != null) { 141 | try { 142 | inputStream.close(); 143 | } catch (IOException e) { 144 | e.printStackTrace(); 145 | } 146 | } 147 | } 148 | 149 | 150 | } 151 | 152 | private static void format() { 153 | try { 154 | arial14font = new WritableFont(WritableFont.ARIAL, 14, WritableFont.BOLD); 155 | arial14font.setColour(Colour.LIGHT_BLUE); 156 | arial14format = new WritableCellFormat(arial14font); 157 | arial14format.setAlignment(Alignment.CENTRE); 158 | arial14format.setBorder(Border.ALL, BorderLineStyle.THIN); 159 | arial14format.setBackground(Colour.VERY_LIGHT_YELLOW); 160 | arial10font = new WritableFont(WritableFont.ARIAL, 10, WritableFont.BOLD); 161 | arial10format = new WritableCellFormat(arial10font); 162 | arial10format.setAlignment(Alignment.CENTRE); 163 | arial10format.setBorder(Border.ALL, BorderLineStyle.THIN); 164 | arial10format.setBackground(Colour.GRAY_25); 165 | arial12font = new WritableFont(WritableFont.ARIAL, 10); 166 | arial12format = new WritableCellFormat(arial12font); 167 | //对齐格式 168 | arial10format.setAlignment(Alignment.CENTRE); 169 | //设置边框 170 | arial12format.setBorder(Border.ALL, BorderLineStyle.THIN); 171 | } catch (WriteException e) { 172 | e.printStackTrace(); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/android/jesse/transforms/BaseTransform.groovy: -------------------------------------------------------------------------------- 1 | package com.android.jesse.transforms 2 | 3 | import com.android.build.api.transform.* 4 | import com.android.build.gradle.internal.pipeline.TransformManager 5 | import com.android.ide.common.internal.WaitableExecutor 6 | import com.android.jesse.Log 7 | import groovy.io.FileType 8 | import org.apache.commons.codec.digest.DigestUtils 9 | import org.apache.commons.io.FileUtils 10 | import org.apache.commons.io.IOUtils 11 | 12 | import java.util.concurrent.Callable 13 | import java.util.jar.JarEntry 14 | import java.util.jar.JarFile 15 | import java.util.jar.JarOutputStream 16 | 17 | abstract class BaseTransform extends Transform { 18 | 19 | 20 | 21 | public WaitableExecutor mWaitableExecutor = WaitableExecutor.useGlobalSharedThreadPool() 22 | 23 | /** 24 | * 需要处理的数据类型,有两种枚举类型 25 | * CLASSES 代表处理的 java 的 class 文件,RESOURCES 代表要处理 java 的资源 26 | * @return 27 | */ 28 | @Override 29 | Set getInputTypes() { 30 | return TransformManager.CONTENT_CLASS 31 | } 32 | 33 | /** 34 | * 指 Transform 要操作内容的范围,官方文档 Scope 有 7 种类型: 35 | * 1. EXTERNAL_LIBRARIES 只有外部库 36 | * 2. PROJECT 只有项目内容 37 | * 3. PROJECT_LOCAL_DEPS 只有项目的本地依赖(本地jar) 38 | * 4. PROVIDED_ONLY 只提供本地或远程依赖项 39 | * 5. SUB_PROJECTS 只有子项目。 40 | * 6. SUB_PROJECTS_LOCAL_DEPS 只有子项目的本地依赖项(本地jar)。 41 | * 7. TESTED_CODE 由当前变量(包括依赖项)测试的代码 42 | * @return 43 | */ 44 | @Override 45 | Set getScopes() { 46 | return TransformManager.SCOPE_FULL_PROJECT 47 | } 48 | 49 | /** 50 | * 是否是增量编译 51 | */ 52 | @Override 53 | boolean isIncremental() { 54 | return true 55 | } 56 | 57 | /** 58 | * 字节码转换的主要逻辑 59 | */ 60 | @Override 61 | void transform(TransformInvocation transformInvocation) throws TransformException, 62 | InterruptedException, IOException { 63 | onBeforeTransform() 64 | _transform(transformInvocation.context, transformInvocation.inputs, 65 | transformInvocation.outputProvider, transformInvocation.incremental) 66 | onAfterTransform() 67 | } 68 | 69 | void _transform(Context context, Collection inputs, TransformOutputProvider outputProvider, 70 | boolean isIncremental) throws IOException, TransformException, InterruptedException { 71 | 72 | if (!isModifyEnable()) { 73 | return 74 | } 75 | if (!isIncremental) { 76 | outputProvider.deleteAll() 77 | } 78 | 79 | /**Transform 的 inputs 有两种类型,一种是目录,一种是 jar 包,要分开遍历 */ 80 | inputs.each { TransformInput input -> 81 | /**遍历目录*/ 82 | input.directoryInputs.each { DirectoryInput directoryInput -> 83 | 84 | mWaitableExecutor.execute(new Callable() { 85 | @Override 86 | Object call() throws Exception { 87 | processDirectoryInput(context, directoryInput, outputProvider, isIncremental) 88 | return null 89 | } 90 | }) 91 | } 92 | 93 | /**遍历 jar*/ 94 | input.jarInputs.each { JarInput jarInput -> 95 | 96 | mWaitableExecutor.execute(new Callable() { 97 | @Override 98 | Object call() throws Exception { 99 | processJarInput(context, jarInput, outputProvider, isIncremental) 100 | return null 101 | } 102 | }) 103 | } 104 | } 105 | mWaitableExecutor.waitForTasksWithQuickFail(true) 106 | } 107 | 108 | /** 109 | * 处理源码文件 110 | * 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的 111 | */ 112 | void processDirectoryInput(Context context, DirectoryInput directoryInput, 113 | TransformOutputProvider outputProvider, boolean isIncremental) { 114 | //当前这个 Transform 输出目录 115 | File dest = outputProvider.getContentLocation(directoryInput.name, 116 | directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY) 117 | FileUtils.forceMkdir(dest) 118 | //当前这个 Transform 输入目录 119 | File dir = directoryInput.file 120 | 121 | if (isLogEnable()) { 122 | Log.i("BaseTransform", "> src.absolutePath: " + dir.absolutePath) 123 | Log.i("BaseTransform", "> dest.absolutePath: " + dest.absolutePath) 124 | } 125 | 126 | if (isIncremental) { 127 | String srcDirPath = dir.absolutePath 128 | String destDirPath = dest.absolutePath 129 | directoryInput.changedFiles.each { 130 | Map.Entry changedFile -> 131 | Status status = changedFile.getValue() 132 | File inputFile = changedFile.getKey() 133 | String destFilePath = inputFile.absolutePath.replace(srcDirPath, destDirPath) 134 | File destFile = new File(destFilePath) 135 | switch (status) { 136 | case Status.NOTCHANGED: 137 | break 138 | case Status.ADDED: 139 | case Status.CHANGED: 140 | FileUtils.touch(destFile) 141 | transformSingleFile(context, dir, inputFile, destFile) 142 | break 143 | case Status.REMOVED: 144 | if (destFile.exists()) { 145 | FileUtils.forceDelete(destFile) 146 | } 147 | break 148 | } 149 | } 150 | } else { 151 | transformDirectory(context, dir, dest) 152 | } 153 | 154 | } 155 | 156 | /** 157 | * 转换单个文件 158 | */ 159 | void transformSingleFile(Context context, File dir, File inputFile, File destFile) { 160 | println("inputFile: " + inputFile.absolutePath) 161 | println("destFile: " + destFile.absolutePath) 162 | 163 | String className = inputFile.absolutePath.replace(dir.absolutePath + File.separator, "") 164 | if (className.endsWith(".class") && isShouldModify(className)) { 165 | File modified = modifyClassFile(dir, inputFile, context.getTemporaryDir()) 166 | if (modified != null) { 167 | FileUtils.copyFile(modified, destFile) 168 | return 169 | } 170 | } 171 | FileUtils.copyFile(inputFile, destFile) 172 | } 173 | 174 | /** 175 | * 转换文件夹 176 | */ 177 | void transformDirectory(Context context, File dir, File dest) { 178 | if (dir) { 179 | HashMap modifyMap = new HashMap<>() 180 | /**遍历以某一扩展名结尾的文件*/ 181 | dir.traverse(type: FileType.FILES, nameFilter: ~/.*\.class/) { 182 | File classFile -> 183 | String className = classFile.absolutePath.replace(dir.absolutePath + File.separator, "") 184 | if (isShouldModify(className)) { 185 | File modified = modifyClassFile(dir, classFile, context.getTemporaryDir()) 186 | if (modified != null) { 187 | /**key 为包名 + 类名,如:/cn/sensorsdata/autotrack/android/app/MainActivity.class*/ 188 | String ke = classFile.absolutePath.replace(dir.absolutePath, "") 189 | modifyMap.put(ke, modified) 190 | } 191 | } 192 | } 193 | FileUtils.copyDirectory(dir, dest) 194 | modifyMap.entrySet().each { 195 | Map.Entry en -> 196 | File target = new File(dest.absolutePath + en.getKey()) 197 | if (target.exists()) { 198 | target.delete() 199 | } 200 | FileUtils.copyFile(en.getValue(), target) 201 | en.getValue().delete() 202 | } 203 | } 204 | } 205 | 206 | /** 207 | * 处理jar 208 | * 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的 209 | */ 210 | void processJarInput(Context context, JarInput jarInput, 211 | TransformOutputProvider outputProvider, boolean isIncremental) { 212 | String destName = jarInput.file.name 213 | 214 | /**截取文件路径的 md5 值重命名输出文件,因为可能同名,会覆盖*/ 215 | def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath).substring(0, 8) 216 | /** 获取 jar 名字*/ 217 | if (destName.endsWith(".jar")) { 218 | destName = destName.substring(0, destName.length() - 4) 219 | } 220 | /** 获得输出文件*/ 221 | File dest = outputProvider.getContentLocation(destName + "_" + hexName, 222 | jarInput.contentTypes, jarInput.scopes, Format.JAR) 223 | 224 | if (isIncremental) { 225 | switch (jarInput.status) { 226 | case Status.NOTCHANGED: 227 | break 228 | case Status.ADDED: 229 | case Status.CHANGED: 230 | transformJar(context, jarInput.file, dest) 231 | break 232 | case Status.REMOVED: 233 | if (dest.exists()) { 234 | FileUtils.forceDelete(dest) 235 | } 236 | break 237 | } 238 | } else { 239 | transformJar(context, jarInput.file, dest) 240 | } 241 | } 242 | 243 | /** 244 | * 转换Jar 245 | */ 246 | void transformJar(Context context, File jarInputFile, File dest) { 247 | 248 | File modifiedJar = modifyJar(jarInputFile, context.getTemporaryDir(), true) 249 | if (modifiedJar == null) { 250 | modifiedJar = jarInputFile 251 | } 252 | FileUtils.copyFile(modifiedJar, dest) 253 | } 254 | 255 | 256 | File modifyClassFile(File dir, File classFile, File tempDir) { 257 | File modified = null 258 | try { 259 | String className = path2ClassName(classFile.absolutePath.replace(dir.absolutePath + 260 | File.separator, "")) 261 | byte[] sourceClassBytes = IOUtils.toByteArray(new FileInputStream(classFile)) 262 | byte[] modifiedClassBytes = modifyClass(sourceClassBytes) 263 | if (modifiedClassBytes) { 264 | modified = new File(tempDir, className.replace('.', '') + 265 | '.class') 266 | if (modified.exists()) { 267 | modified.delete() 268 | } 269 | modified.createNewFile() 270 | new FileOutputStream(modified).write(modifiedClassBytes) 271 | } 272 | } catch (Exception e) { 273 | e.printStackTrace() 274 | modified = classFile 275 | } 276 | return modified 277 | } 278 | 279 | String path2ClassName(String pathName) { 280 | pathName.replace(File.separator, ".").replace(".class", "") 281 | } 282 | 283 | File modifyJar(File jarFile, File tempDir, boolean nameHex) { 284 | /** 285 | * 读取原 jar 286 | */ 287 | def file = new JarFile(jarFile, false) 288 | 289 | /** 290 | * 设置输出到的 jar 291 | */ 292 | def hexName = "" 293 | if (nameHex) { 294 | hexName = DigestUtils.md5Hex(jarFile.absolutePath).substring(0, 8) 295 | } 296 | def outputJar = new File(tempDir, hexName + jarFile.name) 297 | JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(outputJar)) 298 | Enumeration enumeration = file.entries() 299 | while (enumeration.hasMoreElements()) { 300 | JarEntry jarEntry = (JarEntry) enumeration.nextElement() 301 | InputStream inputStream = null 302 | try { 303 | inputStream = file.getInputStream(jarEntry) 304 | } catch (Exception e) { 305 | return null 306 | } 307 | String entryName = jarEntry.getName() 308 | if (entryName.endsWith(".DSA") || entryName.endsWith(".SF")) { 309 | //ignore 310 | } else { 311 | JarEntry jarEntry2 = new JarEntry(entryName) 312 | jarOutputStream.putNextEntry(jarEntry2) 313 | 314 | byte[] modifiedClassBytes = null 315 | byte[] sourceClassBytes = IOUtils.toByteArray(inputStream) 316 | if (entryName.endsWith(".class")) { 317 | if (isShouldModify(entryName)) { 318 | modifiedClassBytes = modifyClass(sourceClassBytes) 319 | } 320 | } 321 | if (modifiedClassBytes == null) { 322 | modifiedClassBytes = sourceClassBytes 323 | } 324 | jarOutputStream.write(modifiedClassBytes) 325 | jarOutputStream.closeEntry() 326 | } 327 | } 328 | jarOutputStream.close() 329 | file.close() 330 | return outputJar 331 | } 332 | 333 | /** 334 | * 过滤需要修改的class文件 335 | * @param className eg: cn/sensorsdata/autotrack/android/app/MainActivity.class 336 | * @return 337 | */ 338 | abstract boolean isShouldModify(String className); 339 | 340 | /** 341 | * 修改class文件 342 | * @param srcClass 源class 343 | * @return 目标class* @throws IOException 344 | */ 345 | abstract byte[] modifyClass(byte[] srcClass) throws IOException; 346 | 347 | /** 348 | * 字节码修改是否开启,默认开启 349 | * @return 350 | */ 351 | boolean isModifyEnable() { 352 | return true 353 | } 354 | 355 | boolean isLogEnable() { 356 | return true 357 | } 358 | 359 | void onBeforeTransform() { 360 | if (isLogEnable()) { 361 | Log.i("BaseTransform", "---------- ${getName()} 开始 ----------") 362 | } 363 | }; 364 | 365 | void onAfterTransform() { 366 | if (isLogEnable()) { 367 | Log.i("BaseTransform", "---------- ${getName()} 结束 ----------") 368 | } 369 | }; 370 | 371 | } --------------------------------------------------------------------------------