From 0e6bb79e698598eebb2e8d6e4c4ee824a32797d4 Mon Sep 17 00:00:00 2001
From: johnsonlee
Date: Tue, 23 Apr 2019 17:28:51 +0800
Subject: [PATCH] init
---
.gitignore | 46 +++
CONTRIBUTING.md | 30 ++
LICENSE.txt | 201 +++++++++++
README.md | 56 +++
booster-android-api/build.gradle | 2 +
.../src/main/java/android/app/Activity.java | 4 +
.../main/java/android/app/ActivityThread.java | 17 +
.../main/java/android/app/Application.java | 31 ++
.../android/content/BroadcastReceiver.java | 4 +
.../main/java/android/content/Context.java | 28 ++
.../java/android/content/ContextWrapper.java | 63 ++++
.../src/main/java/android/content/Intent.java | 4 +
.../java/android/content/IntentFilter.java | 4 +
.../android/content/SharedPreferences.java | 12 +
.../android/content/res/AssetManager.java | 16 +
.../java/android/content/res/Resources.java | 21 ++
.../android/graphics/drawable/Drawable.java | 9 +
.../java/android/media/AudioAttributes.java | 6 +
.../main/java/android/media/MediaPlayer.java | 37 ++
.../java/android/media/MediaTimeProvider.java | 4 +
.../android/media/SubtitleController.java | 17 +
.../src/main/java/android/net/Uri.java | 4 +
.../src/main/java/android/os/AsyncTask.java | 22 ++
.../src/main/java/android/os/Binder.java | 4 +
.../src/main/java/android/os/Build.java | 326 ++++++++++++++++++
.../src/main/java/android/os/Bundle.java | 4 +
.../java/android/os/DeadObjectException.java | 13 +
.../java/android/os/DeadSystemException.java | 9 +
.../src/main/java/android/os/Debug.java | 13 +
.../src/main/java/android/os/Handler.java | 37 ++
.../main/java/android/os/HandlerThread.java | 25 ++
.../src/main/java/android/os/IBinder.java | 4 +
.../src/main/java/android/os/IInterface.java | 7 +
.../src/main/java/android/os/Looper.java | 33 ++
.../src/main/java/android/os/Message.java | 10 +
.../main/java/android/os/MessageQueue.java | 17 +
.../src/main/java/android/os/Parcelable.java | 4 +
.../src/main/java/android/os/Process.java | 15 +
.../main/java/android/os/RemoteException.java | 7 +
.../src/main/java/android/os/SystemClock.java | 45 +++
.../java/android/os/SystemProperties.java | 29 ++
.../java/android/util/AndroidException.java | 4 +
.../android/util/AndroidRuntimeException.java | 21 ++
.../src/main/java/android/util/ArrayMap.java | 75 ++++
.../main/java/android/util/AttributeSet.java | 4 +
.../src/main/java/android/util/Log.java | 276 +++++++++++++++
.../main/java/android/view/InputEvent.java | 6 +
.../src/main/java/android/view/KeyEvent.java | 10 +
.../main/java/android/view/SurfaceHolder.java | 4 +
.../src/main/java/android/view/View.java | 7 +
.../src/main/java/android/view/ViewDebug.java | 8 +
.../src/main/java/android/view/ViewGroup.java | 43 +++
.../main/java/android/view/ViewManager.java | 4 +
.../main/java/android/view/ViewParent.java | 4 +
.../java/android/view/ViewTreeObserver.java | 145 ++++++++
.../main/java/android/view/WindowManager.java | 15 +
.../AccessibilityEventSource.java | 4 +
.../java/android/webkit/CookieManager.java | 4 +
.../android/webkit/IWebViewUpdateService.java | 9 +
.../main/java/android/webkit/WebSettings.java | 11 +
.../main/java/android/webkit/WebStorage.java | 4 +
.../src/main/java/android/webkit/WebView.java | 96 ++++++
.../java/android/webkit/WebViewDatabase.java | 4 +
.../java/android/webkit/WebViewFactory.java | 13 +
.../webkit/WebViewFactoryProvider.java | 4 +
.../java/android/widget/AbsoluteLayout.java | 4 +
.../src/main/java/android/widget/Toast.java | 9 +
booster-android-bugfix-toast/build.gradle | 7 +
.../booster/android/widget/Toast.java | 54 +++
booster-android-bugfix/build.gradle | 6 +
.../android/bugfix/CaughtCallback.java | 23 ++
.../android/bugfix/CaughtRunnable.java | 19 +
.../booster/android/bugfix/Constants.java | 7 +
.../booster/android/bugfix/Reflection.java | 225 ++++++++++++
booster-android-gradle-api/build.gradle | 8 +
.../didiglobal/booster/gradle/VariantScope.kt | 67 ++++
booster-android-gradle-v3_0/build.gradle | 7 +
.../booster/gradle/VariantScopeV30.java | 94 +++++
booster-android-gradle-v3_2/build.gradle | 7 +
.../booster/gradle/VariantScopeV32.java | 95 +++++
booster-gradle-base/build.gradle | 10 +
.../didiglobal/booster/gradle/BaseVariant.kt | 30 ++
.../com/didiglobal/booster/gradle/Project.kt | 13 +
.../booster/gradle/ResolvedArtifactResults.kt | 82 +++++
.../booster/gradle/TransformInvocation.kt | 63 ++++
.../booster/util/ComponentHandler.kt | 36 ++
booster-gradle-plugin/build.gradle | 12 +
.../booster/gradle/BoosterAppTransform.kt | 15 +
.../booster/gradle/BoosterLibTransform.kt | 15 +
.../booster/gradle/BoosterPlugin.kt | 46 +++
.../booster/gradle/BoosterTransform.kt | 58 ++++
.../gradle/BoosterTransformInvocation.kt | 158 +++++++++
.../com.didiglobal.booster.properties | 1 +
booster-kotlinx/build.gradle | 1 +
.../didiglobal/booster/kotlinx/FileTree.kt | 42 +++
.../didiglobal/booster/kotlinx/Wildcard.kt | 119 +++++++
.../com/didiglobal/booster/kotlinx/ansi.kt | 44 +++
.../didiglobal/booster/kotlinx/collections.kt | 30 ++
.../com/didiglobal/booster/kotlinx/io.kt | 188 ++++++++++
.../didiglobal/booster/kotlinx/primitive.kt | 29 ++
.../com/didiglobal/booster/kotlinx/text.kt | 23 ++
.../didiglobal/booster/kotlinx/FileTest.kt | 32 ++
.../booster/kotlinx/WildcardTest.kt | 15 +
booster-task-all/build.gradle | 5 +
booster-task-artifact/build.gradle | 9 +
.../task/artifact/ArtifactVariantProcessor.kt | 22 ++
.../task/artifact/ArtifactsResolver.kt | 23 ++
booster-task-dependency/build.gradle | 9 +
.../booster/task/dependency/CheckSnapshot.kt | 29 ++
.../dependency/DependencyVariantProcessor.kt | 23 ++
booster-task-permission/build.gradle | 9 +
.../task/permission/PermissionExtractor.kt | 88 +++++
.../task/permission/PermissionUsageHandler.kt | 18 +
.../permission/PermissionVariantProcessor.kt | 22 ++
booster-task-spi/build.gradle | 6 +
.../booster/task/spi/VariantProcessor.kt | 9 +
booster-transform-all/build.gradle | 6 +
booster-transform-asm/build.gradle | 14 +
.../transform/asm/ClassTransformer.java | 29 ++
.../booster/transform/asm/AsmTransformer.kt | 45 +++
.../booster/transform/asm/ClassNode.kt | 9 +
.../booster/transform/asm/InsnList.kt | 27 ++
booster-transform-bugfix-toast/build.gradle | 10 +
.../bugfix/toast/ToastBugfixTransformer.kt | 36 ++
.../toast/ToastBugfixVariantProcessor.kt | 18 +
booster-transform-lint/build.gradle | 7 +
.../booster/transform/lint/EntryPoint.kt | 46 +++
.../booster/transform/lint/LintTransformer.kt | 182 ++++++++++
.../booster/transform/lint/graph/CallGraph.kt | 109 ++++++
.../booster/transform/lint/URITest.kt | 18 +
booster-transform-shrink/build.gradle | 7 +
.../shrink/MalformedSymbolListException.kt | 5 +
.../booster/transform/shrink/RFinder.kt | 25 ++
.../transform/shrink/ShrinkTransformer.kt | 131 +++++++
.../booster/transform/shrink/SymbolList.kt | 174 ++++++++++
.../transform/shrink/SymbolListTest.kt | 18 +
.../src/test/resources/R.txt | 186 ++++++++++
booster-transform-spi/build.gradle | 1 +
.../booster/transform/TransformListener.java | 18 +
.../booster/transform/ArtifactManager.kt | 31 ++
.../booster/transform/TransformContext.kt | 67 ++++
.../booster/transform/Transformer.kt | 21 ++
booster-transform-usage/build.gradle | 7 +
.../transform/usage/UsageTransformer.kt | 54 +++
booster-transform-util/build.gradle | 6 +
.../booster/transform/util/transform.kt | 71 ++++
.../booster/transform/FileTransformTest.kt | 36 ++
build.gradle | 118 +++++++
buildSrc/build.gradle | 35 ++
.../booster/buildprops/BuildPropsGenerator.kt | 84 +++++
.../booster/buildprops/BuildPropsPlugin.kt | 83 +++++
.../buildprops/BuildPropsGeneratorTest.kt | 14 +
gradle/booster.gradle | 39 +++
gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes
gradle/wrapper/gradle-wrapper.properties | 5 +
gradlew | 172 +++++++++
gradlew.bat | 84 +++++
license/3rdparty/asm/LICENSE.txt | 27 ++
license/README.md | 27 ++
settings.gradle | 22 ++
160 files changed, 6105 insertions(+)
create mode 100644 .gitignore
create mode 100644 CONTRIBUTING.md
create mode 100644 LICENSE.txt
create mode 100644 README.md
create mode 100644 booster-android-api/build.gradle
create mode 100644 booster-android-api/src/main/java/android/app/Activity.java
create mode 100644 booster-android-api/src/main/java/android/app/ActivityThread.java
create mode 100644 booster-android-api/src/main/java/android/app/Application.java
create mode 100644 booster-android-api/src/main/java/android/content/BroadcastReceiver.java
create mode 100644 booster-android-api/src/main/java/android/content/Context.java
create mode 100644 booster-android-api/src/main/java/android/content/ContextWrapper.java
create mode 100644 booster-android-api/src/main/java/android/content/Intent.java
create mode 100644 booster-android-api/src/main/java/android/content/IntentFilter.java
create mode 100644 booster-android-api/src/main/java/android/content/SharedPreferences.java
create mode 100644 booster-android-api/src/main/java/android/content/res/AssetManager.java
create mode 100644 booster-android-api/src/main/java/android/content/res/Resources.java
create mode 100644 booster-android-api/src/main/java/android/graphics/drawable/Drawable.java
create mode 100644 booster-android-api/src/main/java/android/media/AudioAttributes.java
create mode 100644 booster-android-api/src/main/java/android/media/MediaPlayer.java
create mode 100644 booster-android-api/src/main/java/android/media/MediaTimeProvider.java
create mode 100644 booster-android-api/src/main/java/android/media/SubtitleController.java
create mode 100644 booster-android-api/src/main/java/android/net/Uri.java
create mode 100644 booster-android-api/src/main/java/android/os/AsyncTask.java
create mode 100644 booster-android-api/src/main/java/android/os/Binder.java
create mode 100644 booster-android-api/src/main/java/android/os/Build.java
create mode 100644 booster-android-api/src/main/java/android/os/Bundle.java
create mode 100644 booster-android-api/src/main/java/android/os/DeadObjectException.java
create mode 100644 booster-android-api/src/main/java/android/os/DeadSystemException.java
create mode 100644 booster-android-api/src/main/java/android/os/Debug.java
create mode 100644 booster-android-api/src/main/java/android/os/Handler.java
create mode 100644 booster-android-api/src/main/java/android/os/HandlerThread.java
create mode 100644 booster-android-api/src/main/java/android/os/IBinder.java
create mode 100644 booster-android-api/src/main/java/android/os/IInterface.java
create mode 100644 booster-android-api/src/main/java/android/os/Looper.java
create mode 100644 booster-android-api/src/main/java/android/os/Message.java
create mode 100644 booster-android-api/src/main/java/android/os/MessageQueue.java
create mode 100644 booster-android-api/src/main/java/android/os/Parcelable.java
create mode 100644 booster-android-api/src/main/java/android/os/Process.java
create mode 100644 booster-android-api/src/main/java/android/os/RemoteException.java
create mode 100644 booster-android-api/src/main/java/android/os/SystemClock.java
create mode 100644 booster-android-api/src/main/java/android/os/SystemProperties.java
create mode 100644 booster-android-api/src/main/java/android/util/AndroidException.java
create mode 100644 booster-android-api/src/main/java/android/util/AndroidRuntimeException.java
create mode 100644 booster-android-api/src/main/java/android/util/ArrayMap.java
create mode 100644 booster-android-api/src/main/java/android/util/AttributeSet.java
create mode 100644 booster-android-api/src/main/java/android/util/Log.java
create mode 100644 booster-android-api/src/main/java/android/view/InputEvent.java
create mode 100644 booster-android-api/src/main/java/android/view/KeyEvent.java
create mode 100644 booster-android-api/src/main/java/android/view/SurfaceHolder.java
create mode 100644 booster-android-api/src/main/java/android/view/View.java
create mode 100644 booster-android-api/src/main/java/android/view/ViewDebug.java
create mode 100644 booster-android-api/src/main/java/android/view/ViewGroup.java
create mode 100644 booster-android-api/src/main/java/android/view/ViewManager.java
create mode 100644 booster-android-api/src/main/java/android/view/ViewParent.java
create mode 100644 booster-android-api/src/main/java/android/view/ViewTreeObserver.java
create mode 100644 booster-android-api/src/main/java/android/view/WindowManager.java
create mode 100644 booster-android-api/src/main/java/android/view/accessibility/AccessibilityEventSource.java
create mode 100644 booster-android-api/src/main/java/android/webkit/CookieManager.java
create mode 100644 booster-android-api/src/main/java/android/webkit/IWebViewUpdateService.java
create mode 100644 booster-android-api/src/main/java/android/webkit/WebSettings.java
create mode 100644 booster-android-api/src/main/java/android/webkit/WebStorage.java
create mode 100644 booster-android-api/src/main/java/android/webkit/WebView.java
create mode 100644 booster-android-api/src/main/java/android/webkit/WebViewDatabase.java
create mode 100644 booster-android-api/src/main/java/android/webkit/WebViewFactory.java
create mode 100644 booster-android-api/src/main/java/android/webkit/WebViewFactoryProvider.java
create mode 100644 booster-android-api/src/main/java/android/widget/AbsoluteLayout.java
create mode 100644 booster-android-api/src/main/java/android/widget/Toast.java
create mode 100644 booster-android-bugfix-toast/build.gradle
create mode 100644 booster-android-bugfix-toast/src/main/java/com/didiglobal/booster/android/widget/Toast.java
create mode 100644 booster-android-bugfix/build.gradle
create mode 100644 booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtCallback.java
create mode 100644 booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtRunnable.java
create mode 100644 booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Constants.java
create mode 100644 booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Reflection.java
create mode 100644 booster-android-gradle-api/build.gradle
create mode 100644 booster-android-gradle-api/src/main/kotlin/com/didiglobal/booster/gradle/VariantScope.kt
create mode 100644 booster-android-gradle-v3_0/build.gradle
create mode 100644 booster-android-gradle-v3_0/src/main/java/com/didiglobal/booster/gradle/VariantScopeV30.java
create mode 100644 booster-android-gradle-v3_2/build.gradle
create mode 100644 booster-android-gradle-v3_2/src/main/java/com/didiglobal/booster/gradle/VariantScopeV32.java
create mode 100644 booster-gradle-base/build.gradle
create mode 100644 booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/BaseVariant.kt
create mode 100644 booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/Project.kt
create mode 100644 booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/ResolvedArtifactResults.kt
create mode 100644 booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/TransformInvocation.kt
create mode 100644 booster-gradle-base/src/main/kotlin/com/didiglobal/booster/util/ComponentHandler.kt
create mode 100644 booster-gradle-plugin/build.gradle
create mode 100644 booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterAppTransform.kt
create mode 100644 booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterLibTransform.kt
create mode 100644 booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterPlugin.kt
create mode 100644 booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransform.kt
create mode 100644 booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransformInvocation.kt
create mode 100644 booster-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.didiglobal.booster.properties
create mode 100644 booster-kotlinx/build.gradle
create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/FileTree.kt
create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/Wildcard.kt
create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/ansi.kt
create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/collections.kt
create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/io.kt
create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/primitive.kt
create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/text.kt
create mode 100644 booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/FileTest.kt
create mode 100644 booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/WildcardTest.kt
create mode 100644 booster-task-all/build.gradle
create mode 100644 booster-task-artifact/build.gradle
create mode 100644 booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactVariantProcessor.kt
create mode 100644 booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactsResolver.kt
create mode 100644 booster-task-dependency/build.gradle
create mode 100644 booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/CheckSnapshot.kt
create mode 100644 booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/DependencyVariantProcessor.kt
create mode 100644 booster-task-permission/build.gradle
create mode 100644 booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionExtractor.kt
create mode 100644 booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionUsageHandler.kt
create mode 100644 booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionVariantProcessor.kt
create mode 100644 booster-task-spi/build.gradle
create mode 100644 booster-task-spi/src/main/kotlin/com/didiglobal/booster/task/spi/VariantProcessor.kt
create mode 100644 booster-transform-all/build.gradle
create mode 100644 booster-transform-asm/build.gradle
create mode 100644 booster-transform-asm/src/main/java/com/didiglobal/booster/transform/asm/ClassTransformer.java
create mode 100644 booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/AsmTransformer.kt
create mode 100644 booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/ClassNode.kt
create mode 100644 booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/InsnList.kt
create mode 100644 booster-transform-bugfix-toast/build.gradle
create mode 100644 booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixTransformer.kt
create mode 100644 booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixVariantProcessor.kt
create mode 100644 booster-transform-lint/build.gradle
create mode 100644 booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/EntryPoint.kt
create mode 100644 booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/LintTransformer.kt
create mode 100644 booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/graph/CallGraph.kt
create mode 100644 booster-transform-lint/src/test/kotlin/com/didiglobal/booster/transform/lint/URITest.kt
create mode 100644 booster-transform-shrink/build.gradle
create mode 100644 booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/MalformedSymbolListException.kt
create mode 100644 booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/RFinder.kt
create mode 100644 booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/ShrinkTransformer.kt
create mode 100644 booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/SymbolList.kt
create mode 100644 booster-transform-shrink/src/test/kotlin/com/didiglobal/booster/transform/shrink/SymbolListTest.kt
create mode 100644 booster-transform-shrink/src/test/resources/R.txt
create mode 100644 booster-transform-spi/build.gradle
create mode 100644 booster-transform-spi/src/main/java/com/didiglobal/booster/transform/TransformListener.java
create mode 100644 booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/ArtifactManager.kt
create mode 100644 booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformContext.kt
create mode 100644 booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/Transformer.kt
create mode 100644 booster-transform-usage/build.gradle
create mode 100644 booster-transform-usage/src/main/kotlin/com/didiglobal/booster/transform/usage/UsageTransformer.kt
create mode 100644 booster-transform-util/build.gradle
create mode 100644 booster-transform-util/src/main/kotlin/com/didiglobal/booster/transform/util/transform.kt
create mode 100644 booster-transform-util/src/test/kotlin/com/didiglobal/booster/transform/FileTransformTest.kt
create mode 100644 build.gradle
create mode 100644 buildSrc/build.gradle
create mode 100644 buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsGenerator.kt
create mode 100644 buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsPlugin.kt
create mode 100644 buildSrc/src/test/kotlin/com/didiglobal/booster/buildprops/BuildPropsGeneratorTest.kt
create mode 100644 gradle/booster.gradle
create mode 100644 gradle/wrapper/gradle-wrapper.jar
create mode 100644 gradle/wrapper/gradle-wrapper.properties
create mode 100755 gradlew
create mode 100644 gradlew.bat
create mode 100644 license/3rdparty/asm/LICENSE.txt
create mode 100644 license/README.md
create mode 100644 settings.gradle
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..3a1633699
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,46 @@
+# Generated by Mac OS X
+.DS_Store
+
+# Generated by VIM
+.*.swp
+
+# Generated by Gradle
+.gradle
+build/
+out/
+
+# Generated by IDEA
+.idea
+*.iml
+
+# Generated by Eclipse
+.metadata
+.settings
+.project
+.classpath
+/bin/
+
+# Generated by Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+
+# Generated by IDE
+local.properties
+
+# Node & NPM
+node_modules
+
+# GitBook
+_book
+
+# Misc
+*.log
+*.bak
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..77ccbc488
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,30 @@
+# Contribution Guideline
+
+Thanks for considering to contribute this project. All issues and pull requests are highly appreciated.
+
+## Pull Requests
+
+Before sending pull request to this project, please read and follow guidelines below.
+
+1. Branch: We only accept pull request on `master` branch.
+2. Coding style: Follow the coding style used in this project.
+3. Commit message: Use English and be aware of your spell.
+4. Test: Make sure to test your code.
+
+Add device mode, API version, related log, screenshots and other related information in your pull request if possible.
+
+NOTE: We assume all your contribution can be licensed under the [Apache License 2.0](https://github.com/didi/booster/blob/master/LICENSE).
+
+## Issues
+
+We love clearly described issues. :)
+
+Following information can help us to resolve the issue faster.
+
+* Device mode and hardware information.
+* Operating System information.
+* API version.
+* Logs.
+* Screenshots.
+* Steps to reproduce the issue.
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 000000000..261eeb9e9
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..ecf91fb1b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+# Booster
+
+![GitHub](https://img.shields.io/github/license/didi/booster.svg?style=for-the-badge)
+![Travis (.org)](https://img.shields.io/travis/didi/booster.svg?style=for-the-badge)
+![GitHub contributors](https://img.shields.io/github/release/didi/booster.svg?style=for-the-badge)
+
+Booster is an easy-to-use, lightweight, powerful and extensible optimization toolkit designed specially for mobile applications. Using the dynamic discovering and loading mechanism, booster provides the ability for customizing.
+
+## Prerequisite
+
+- Gradle version 4.1+
+- Android Gradle Plugin version 3.0+
+
+## Getting Started
+
+The plugin can be added to the buildscript classpath and applied:
+
+```groovy
+buildscript {
+ ext.booster_version = '0.1.0'
+ repositories {
+ google()
+ mavenCentral()
+ jcenter()
+ }
+ dependencies {
+ classpath "com.didiglobal.booster:booster-gradle-plugin:$booster_version"
+ classpath "com.didiglobal.booster:booster-task-all:$booster_version"
+ classpath "com.didiglobal.booster:booster-transform-all:$booster_version"
+ }
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'com.didiglobal.booster'
+```
+
+Booster is a modularized project, and the optimizer consist of gradle plugin and a dozen of transformers.
+This means, at least one transformer must be *explicitly* depended to have the desired effect.
+Conveniently, the `booster-transform-all` module can be depended to enable all optimization options.
+
+In addition, Booster provides a collection of [Gradle Task](https://docs.gradle.org/current/userguide/tutorial_using_tasks.html) to help developers be more efficient.
+Conveniently, the `booster-task-all` module can be depended to enable all tasks.
+
+Then build an optimized package by executing the *assemble* task:
+
+```bash
+$ ./gradlew assembleRelease
+```
+
+# Contributing
+
+Welcome to contribute by creating issues or sending pull requests. See [Contributing Guideline](./CONTRIBUTING.md)
+
+# License
+
+Booster is licensed under the [Apache License 2.0](./LICENSE.txt).
diff --git a/booster-android-api/build.gradle b/booster-android-api/build.gradle
new file mode 100644
index 000000000..16ddebc6c
--- /dev/null
+++ b/booster-android-api/build.gradle
@@ -0,0 +1,2 @@
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
diff --git a/booster-android-api/src/main/java/android/app/Activity.java b/booster-android-api/src/main/java/android/app/Activity.java
new file mode 100644
index 000000000..78b5b8c90
--- /dev/null
+++ b/booster-android-api/src/main/java/android/app/Activity.java
@@ -0,0 +1,4 @@
+package android.app;
+
+public class Activity {
+}
diff --git a/booster-android-api/src/main/java/android/app/ActivityThread.java b/booster-android-api/src/main/java/android/app/ActivityThread.java
new file mode 100644
index 000000000..5c47d1b24
--- /dev/null
+++ b/booster-android-api/src/main/java/android/app/ActivityThread.java
@@ -0,0 +1,17 @@
+package android.app;
+
+public class ActivityThread {
+
+ public static ActivityThread currentActivityThread() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static Application currentApplication() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static boolean isSystem() {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/app/Application.java b/booster-android-api/src/main/java/android/app/Application.java
new file mode 100644
index 000000000..4c70bfff6
--- /dev/null
+++ b/booster-android-api/src/main/java/android/app/Application.java
@@ -0,0 +1,31 @@
+package android.app;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.Bundle;
+
+public class Application extends ContextWrapper {
+
+ public interface ActivityLifecycleCallbacks {
+ void onActivityCreated(Activity activity, Bundle savedInstanceState);
+ void onActivityDestroyed(Activity activity);
+ void onActivityPaused(Activity activity);
+ void onActivityResumed(Activity activity);
+ void onActivitySaveInstanceState(Activity activity, Bundle outState);
+ void onActivityStarted(Activity activity);
+ void onActivityStopped(Activity activity);
+ }
+
+ public Application() {
+ super(null);
+ }
+
+ public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callbacks) {
+ throw new RuntimeException("Stub!");
+ }
+
+ final void attach(final Context context) {
+ attachBaseContext(context);
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/content/BroadcastReceiver.java b/booster-android-api/src/main/java/android/content/BroadcastReceiver.java
new file mode 100644
index 000000000..b5e61ebb6
--- /dev/null
+++ b/booster-android-api/src/main/java/android/content/BroadcastReceiver.java
@@ -0,0 +1,4 @@
+package android.content;
+
+public abstract class BroadcastReceiver {
+}
diff --git a/booster-android-api/src/main/java/android/content/Context.java b/booster-android-api/src/main/java/android/content/Context.java
new file mode 100644
index 000000000..4a2771c6b
--- /dev/null
+++ b/booster-android-api/src/main/java/android/content/Context.java
@@ -0,0 +1,28 @@
+package android.content;
+
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+
+public abstract class Context {
+
+ public abstract AssetManager getAssets();
+
+ public abstract Resources getResources();
+
+ public abstract Looper getMainLooper();
+
+ public abstract Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter);
+
+ public abstract Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final int flags);
+
+ public abstract Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final String perm, final Handler handler);
+
+ public abstract Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final String perm, final Handler handler, final int flags);
+
+ public abstract void unregisterReceiver(final BroadcastReceiver receiver);
+
+ public abstract Object getSystemService(final String name);
+
+}
diff --git a/booster-android-api/src/main/java/android/content/ContextWrapper.java b/booster-android-api/src/main/java/android/content/ContextWrapper.java
new file mode 100644
index 000000000..dc858c826
--- /dev/null
+++ b/booster-android-api/src/main/java/android/content/ContextWrapper.java
@@ -0,0 +1,63 @@
+package android.content;
+
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+
+public class ContextWrapper extends Context {
+
+ public ContextWrapper(final Context base) {
+ throw new RuntimeException("Stub!");
+ }
+
+ protected void attachBaseContext(final Context base) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public AssetManager getAssets() {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public Resources getResources() {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public Looper getMainLooper() {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final int flags) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final String perm, final Handler handler) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final String perm, final Handler handler, final int flags) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public void unregisterReceiver(final BroadcastReceiver receiver) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public Object getSystemService(final String name) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/content/Intent.java b/booster-android-api/src/main/java/android/content/Intent.java
new file mode 100644
index 000000000..459e57566
--- /dev/null
+++ b/booster-android-api/src/main/java/android/content/Intent.java
@@ -0,0 +1,4 @@
+package android.content;
+
+public class Intent {
+}
diff --git a/booster-android-api/src/main/java/android/content/IntentFilter.java b/booster-android-api/src/main/java/android/content/IntentFilter.java
new file mode 100644
index 000000000..2b0c01ceb
--- /dev/null
+++ b/booster-android-api/src/main/java/android/content/IntentFilter.java
@@ -0,0 +1,4 @@
+package android.content;
+
+public class IntentFilter {
+}
diff --git a/booster-android-api/src/main/java/android/content/SharedPreferences.java b/booster-android-api/src/main/java/android/content/SharedPreferences.java
new file mode 100644
index 000000000..a77983d40
--- /dev/null
+++ b/booster-android-api/src/main/java/android/content/SharedPreferences.java
@@ -0,0 +1,12 @@
+package android.content;
+
+public interface SharedPreferences {
+
+ public interface Editor {
+
+ void apply();
+
+ boolean commit();
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/content/res/AssetManager.java b/booster-android-api/src/main/java/android/content/res/AssetManager.java
new file mode 100644
index 000000000..d5f2cfcf4
--- /dev/null
+++ b/booster-android-api/src/main/java/android/content/res/AssetManager.java
@@ -0,0 +1,16 @@
+package android.content.res;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public final class AssetManager {
+
+ public final InputStream open(String fileName) throws IOException {
+ throw new RuntimeException("Stub!");
+ }
+
+ public final InputStream open(String fileName, int accessMode) throws IOException {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/content/res/Resources.java b/booster-android-api/src/main/java/android/content/res/Resources.java
new file mode 100644
index 000000000..f2d324372
--- /dev/null
+++ b/booster-android-api/src/main/java/android/content/res/Resources.java
@@ -0,0 +1,21 @@
+package android.content.res;
+
+public class Resources {
+
+ public static class NotFoundException extends RuntimeException {
+
+ public NotFoundException() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public NotFoundException(String name) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public NotFoundException(String name, Exception cause) {
+ throw new RuntimeException("Stub!");
+ }
+
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/graphics/drawable/Drawable.java b/booster-android-api/src/main/java/android/graphics/drawable/Drawable.java
new file mode 100644
index 000000000..4a69e6ae9
--- /dev/null
+++ b/booster-android-api/src/main/java/android/graphics/drawable/Drawable.java
@@ -0,0 +1,9 @@
+package android.graphics.drawable;
+
+public abstract class Drawable {
+
+ public interface Callback {
+ }
+
+
+}
diff --git a/booster-android-api/src/main/java/android/media/AudioAttributes.java b/booster-android-api/src/main/java/android/media/AudioAttributes.java
new file mode 100644
index 000000000..592d6f805
--- /dev/null
+++ b/booster-android-api/src/main/java/android/media/AudioAttributes.java
@@ -0,0 +1,6 @@
+package android.media;
+
+import android.os.Parcelable;
+
+public final class AudioAttributes implements Parcelable {
+}
diff --git a/booster-android-api/src/main/java/android/media/MediaPlayer.java b/booster-android-api/src/main/java/android/media/MediaPlayer.java
new file mode 100644
index 000000000..de23e77e5
--- /dev/null
+++ b/booster-android-api/src/main/java/android/media/MediaPlayer.java
@@ -0,0 +1,37 @@
+package android.media;
+
+import android.content.Context;
+import android.net.Uri;
+import android.view.SurfaceHolder;
+
+public class MediaPlayer {
+
+ public MediaPlayer() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static MediaPlayer create(final Context context, final Uri uri) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static MediaPlayer create(final Context context, final Uri uri, final SurfaceHolder holder) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static MediaPlayer create(final Context context, final Uri uri, final SurfaceHolder holder, final AudioAttributes audioAttributes, final int audioSessionId) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static MediaPlayer create(final Context context, final int resid) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static MediaPlayer create(final Context context, final int resid, final AudioAttributes audioAttributes, final int audioSessionId) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public void setSubtitleAnchor(final SubtitleController controller, final SubtitleController.Anchor anchor) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/media/MediaTimeProvider.java b/booster-android-api/src/main/java/android/media/MediaTimeProvider.java
new file mode 100644
index 000000000..93d84ce97
--- /dev/null
+++ b/booster-android-api/src/main/java/android/media/MediaTimeProvider.java
@@ -0,0 +1,4 @@
+package android.media;
+
+public interface MediaTimeProvider {
+}
diff --git a/booster-android-api/src/main/java/android/media/SubtitleController.java b/booster-android-api/src/main/java/android/media/SubtitleController.java
new file mode 100644
index 000000000..1d741c41c
--- /dev/null
+++ b/booster-android-api/src/main/java/android/media/SubtitleController.java
@@ -0,0 +1,17 @@
+package android.media;
+
+import android.content.Context;
+
+public class SubtitleController {
+
+ public SubtitleController(final Context context, final MediaTimeProvider timeProvider, final Listener listener) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public interface Anchor {
+ }
+
+ public interface Listener {
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/net/Uri.java b/booster-android-api/src/main/java/android/net/Uri.java
new file mode 100644
index 000000000..415eaeda5
--- /dev/null
+++ b/booster-android-api/src/main/java/android/net/Uri.java
@@ -0,0 +1,4 @@
+package android.net;
+
+public class Uri {
+}
diff --git a/booster-android-api/src/main/java/android/os/AsyncTask.java b/booster-android-api/src/main/java/android/os/AsyncTask.java
new file mode 100644
index 000000000..a5755d2d8
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/AsyncTask.java
@@ -0,0 +1,22 @@
+package android.os;
+
+import java.util.concurrent.Executor;
+
+public abstract class AsyncTask {
+
+ private static final Executor STUB = new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ throw new RuntimeException("Stub!");
+ }
+ };
+
+ public static final Executor THREAD_POOL_EXECUTOR = STUB;
+
+ public static final Executor SERIAL_EXECUTOR = STUB;
+
+ public static void setDefaultExecutor(final Executor executor) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/os/Binder.java b/booster-android-api/src/main/java/android/os/Binder.java
new file mode 100644
index 000000000..16a24e385
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/Binder.java
@@ -0,0 +1,4 @@
+package android.os;
+
+public class Binder implements IBinder {
+}
diff --git a/booster-android-api/src/main/java/android/os/Build.java b/booster-android-api/src/main/java/android/os/Build.java
new file mode 100644
index 000000000..4a32e93f0
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/Build.java
@@ -0,0 +1,326 @@
+package android.os;
+
+public class Build {
+
+ public static final String UNKNOWN = "unknown";
+
+ /**
+ * Either a changelist number, or a label like "M4-rc20".
+ */
+ public static final String ID = getString("ro.build.id");
+
+ /**
+ * A build ID string meant for displaying to the user
+ */
+ public static final String DISPLAY = getString("ro.build.display.id");
+
+ /**
+ * The name of the overall product.
+ */
+ public static final String PRODUCT = getString("ro.product.name");
+
+ /**
+ * The name of the industrial design.
+ */
+ public static final String DEVICE = getString("ro.product.device");
+
+ /**
+ * The name of the underlying board, like "goldfish".
+ */
+ public static final String BOARD = getString("ro.product.board");
+
+ /**
+ * The manufacturer of the product/hardware.
+ */
+ public static final String MANUFACTURER = getString("ro.product.manufacturer");
+
+ /**
+ * The consumer-visible brand with which the product/hardware will be associated, if any.
+ */
+ public static final String BRAND = getString("ro.product.brand");
+
+ /**
+ * The end-user-visible name for the end product.
+ */
+ public static final String MODEL = getString("ro.product.model");
+
+ /**
+ * The system bootloader version number.
+ */
+ public static final String BOOTLOADER = getString("ro.bootloader");
+
+ /**
+ * The name of the hardware (from the kernel command line or /proc).
+ */
+ public static final String HARDWARE = getString("ro.hardware");
+
+ /**
+ * Whether this build was for an emulator device.
+ */
+ public static final boolean IS_EMULATOR = getString("ro.kernel.qemu").equals("1");
+
+ /**
+ * An ordered list of ABIs supported by this device. The most preferred ABI is the first
+ * element in the list.
+ *
+ * See {@link #SUPPORTED_32_BIT_ABIS} and {@link #SUPPORTED_64_BIT_ABIS}.
+ */
+ public static final String[] SUPPORTED_ABIS = getStringList("ro.product.cpu.abilist", ",");
+
+ /**
+ * An ordered list of 32 bit ABIs supported by this device. The most preferred ABI
+ * is the first element in the list.
+ *
+ * See {@link #SUPPORTED_ABIS} and {@link #SUPPORTED_64_BIT_ABIS}.
+ */
+ public static final String[] SUPPORTED_32_BIT_ABIS = getStringList("ro.product.cpu.abilist32", ",");
+
+ /**
+ * An ordered list of 64 bit ABIs supported by this device. The most preferred ABI
+ * is the first element in the list.
+ *
+ * See {@link #SUPPORTED_ABIS} and {@link #SUPPORTED_32_BIT_ABIS}.
+ */
+ public static final String[] SUPPORTED_64_BIT_ABIS = getStringList("ro.product.cpu.abilist64", ",");
+
+ public static class VERSION {
+
+ /**
+ * The internal value used by the underlying source control to
+ * represent this build. E.g., a perforce changelist number
+ * or a git hash.
+ */
+ public static final String INCREMENTAL = getString("ro.build.version.incremental");
+
+ /**
+ * The user-visible version string. E.g., "1.0" or "3.4b5".
+ */
+ public static final String RELEASE = getString("ro.build.version.release");
+
+ /**
+ * The base OS build the product is based on.
+ */
+ public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", "");
+
+ /**
+ * The user-visible security patch level.
+ */
+ public static final String SECURITY_PATCH = SystemProperties.get("ro.build.version.security_patch", "");
+
+ /**
+ * The user-visible SDK version of the framework in its raw String
+ * representation; use {@link #SDK_INT} instead.
+ *
+ * @deprecated Use {@link #SDK_INT} to easily get this as an integer.
+ */
+ @Deprecated
+ public static final String SDK = getString("ro.build.version.sdk");
+
+ /**
+ * The user-visible SDK version of the framework; its possible
+ * values are defined in {@link Build.VERSION_CODES}.
+ */
+ public static final int SDK_INT = SystemProperties.getInt("ro.build.version.sdk", 0);
+
+ /**
+ * The developer preview revision of a prerelease SDK. This value will always
+ * be 0
on production platform builds/devices.
+ *
+ *
When this value is nonzero, any new API added since the last
+ * officially published {@link #SDK_INT API level} is only guaranteed to be present
+ * on that specific preview revision. For example, an API Activity.fooBar()
+ * might be present in preview revision 1 but renamed or removed entirely in
+ * preview revision 2, which may cause an app attempting to call it to crash
+ * at runtime.
+ *
+ * Experimental apps targeting preview APIs should check this value for
+ * equality (==
) with the preview SDK revision they were built for
+ * before using any prerelease platform APIs. Apps that detect a preview SDK revision
+ * other than the specific one they expect should fall back to using APIs from
+ * the previously published API level only to avoid unwanted runtime exceptions.
+ *
+ */
+ public static final int PREVIEW_SDK_INT = SystemProperties.getInt("ro.build.version.preview_sdk", 0);
+
+ /**
+ * The current development codename, or the string "REL" if this is
+ * a release build.
+ */
+ public static final String CODENAME = getString("ro.build.version.codename");
+
+ private static final String[] ALL_CODENAMES = getStringList("ro.build.version.all_codenames", ",");
+
+ /**
+ * @hide
+ */
+ public static final String[] ACTIVE_CODENAMES = "REL".equals(ALL_CODENAMES[0])
+ ? new String[0] : ALL_CODENAMES;
+
+ /**
+ * The SDK version to use when accessing resources.
+ * Use the current SDK version code. For every active development codename
+ * we are operating under, we bump the assumed resource platform version by 1.
+ *
+ * @hide
+ */
+ public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
+
+ }
+
+ /**
+ * Enumeration of the currently known SDK version codes. These are the
+ * values that can be found in {@link VERSION#SDK}. Version numbers
+ * increment monotonically with each official platform release.
+ */
+ public static class VERSION_CODES {
+
+ /**
+ * October 2008: The original, first, version of Android. Yay!
+ */
+ public static final int BASE = 1;
+
+ /**
+ * February 2009: First Android update, officially called 1.1.
+ */
+ public static final int BASE_1_1 = 2;
+
+ /**
+ * May 2009: Android 1.5.
+ */
+ public static final int CUPCAKE = 3;
+
+ /**
+ * September 2009: Android 1.6.
+ */
+ public static final int DONUT = 4;
+
+ /**
+ * November 2009: Android 2.0
+ */
+ public static final int ECLAIR = 5;
+
+ /**
+ * December 2009: Android 2.0.1
+ */
+ public static final int ECLAIR_0_1 = 6;
+
+ /**
+ * January 2010: Android 2.1
+ */
+ public static final int ECLAIR_MR1 = 7;
+
+ /**
+ * June 2010: Android 2.2
+ */
+ public static final int FROYO = 8;
+
+ /**
+ * November 2010: Android 2.3
+ */
+ public static final int GINGERBREAD = 9;
+
+ /**
+ * February 2011: Android 2.3.3.
+ */
+ public static final int GINGERBREAD_MR1 = 10;
+
+ /**
+ * February 2011: Android 3.0.
+ */
+ public static final int HONEYCOMB = 11;
+
+ /**
+ * May 2011: Android 3.1.
+ */
+ public static final int HONEYCOMB_MR1 = 12;
+
+ /**
+ * June 2011: Android 3.2.
+ */
+ public static final int HONEYCOMB_MR2 = 13;
+
+ /**
+ * October 2011: Android 4.0.
+ */
+ public static final int ICE_CREAM_SANDWICH = 14;
+
+ /**
+ * December 2011: Android 4.0.3.
+ */
+ public static final int ICE_CREAM_SANDWICH_MR1 = 15;
+
+ /**
+ * June 2012: Android 4.1.
+ */
+ public static final int JELLY_BEAN = 16;
+
+ /**
+ * November 2012: Android 4.2, Moar jelly beans!
+ */
+ public static final int JELLY_BEAN_MR1 = 17;
+
+ /**
+ * July 2013: Android 4.3, the revenge of the beans.
+ */
+ public static final int JELLY_BEAN_MR2 = 18;
+
+ /**
+ * October 2013: Android 4.4, KitKat, another tasty treat.
+ */
+ public static final int KITKAT = 19;
+
+ /**
+ * June 2014: Android 4.4W. KitKat for watches, snacks on the run.
+ */
+ public static final int KITKAT_WATCH = 20;
+
+ /**
+ * Temporary until we completely switch to {@link #LOLLIPOP}.
+ */
+ public static final int L = 21;
+
+ /**
+ * November 2014: Lollipop. A flat one with beautiful shadows. But still tasty.
+ */
+ public static final int LOLLIPOP = 21;
+
+ /**
+ * March 2015: Lollipop with an extra sugar coating on the outside!
+ */
+ public static final int LOLLIPOP_MR1 = 22;
+
+ /**
+ * M is for Marshmallow!
+ */
+ public static final int M = 23;
+
+ /**
+ * N is for Nougat.
+ */
+ public static final int N = 24;
+
+ /**
+ * N MR1: Nougat++.
+ */
+ public static final int N_MR1 = 25;
+
+ /**
+ * O.
+ */
+ public static final int O = 26;
+ }
+
+ private static String getString(String property) {
+ return SystemProperties.get(property, UNKNOWN);
+ }
+
+ private static String[] getStringList(String property, String separator) {
+ String value = SystemProperties.get(property);
+ if (value.isEmpty()) {
+ return new String[0];
+ } else {
+ return value.split(separator);
+ }
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/os/Bundle.java b/booster-android-api/src/main/java/android/os/Bundle.java
new file mode 100644
index 000000000..49ff9ee20
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/Bundle.java
@@ -0,0 +1,4 @@
+package android.os;
+
+public class Bundle {
+}
diff --git a/booster-android-api/src/main/java/android/os/DeadObjectException.java b/booster-android-api/src/main/java/android/os/DeadObjectException.java
new file mode 100644
index 000000000..72a86a94e
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/DeadObjectException.java
@@ -0,0 +1,13 @@
+package android.os;
+
+public class DeadObjectException extends RemoteException {
+
+ public DeadObjectException() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public DeadObjectException(String message) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/os/DeadSystemException.java b/booster-android-api/src/main/java/android/os/DeadSystemException.java
new file mode 100644
index 000000000..1cc6d667c
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/DeadSystemException.java
@@ -0,0 +1,9 @@
+package android.os;
+
+public class DeadSystemException extends DeadObjectException {
+
+ public DeadSystemException() {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/os/Debug.java b/booster-android-api/src/main/java/android/os/Debug.java
new file mode 100644
index 000000000..72a002a1b
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/Debug.java
@@ -0,0 +1,13 @@
+package android.os;
+
+public class Debug {
+
+ public static void startMethodTracing(String path, int bufferSize) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static void stopMethodTracing() {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/os/Handler.java b/booster-android-api/src/main/java/android/os/Handler.java
new file mode 100644
index 000000000..1d93f0dc8
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/Handler.java
@@ -0,0 +1,37 @@
+package android.os;
+
+public class Handler {
+
+ public Handler() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public Handler(final Looper looper) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public final void removeCallbacks(final Runnable runnable) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public final boolean post(final Runnable runnable) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public final boolean postDelayed(final Runnable runnable, final long delayInMillis) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public void handleMessage(final Message msg) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public final Looper getLooper() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public interface Callback {
+ public boolean handleMessage(final Message msg);
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/os/HandlerThread.java b/booster-android-api/src/main/java/android/os/HandlerThread.java
new file mode 100644
index 000000000..345e92ff4
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/HandlerThread.java
@@ -0,0 +1,25 @@
+package android.os;
+
+public class HandlerThread extends Thread {
+
+ public HandlerThread(String name) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public HandlerThread(String name, int priority) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public Looper getLooper() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public boolean quit() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public boolean quitSafely() {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/os/IBinder.java b/booster-android-api/src/main/java/android/os/IBinder.java
new file mode 100644
index 000000000..03ebbfaca
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/IBinder.java
@@ -0,0 +1,4 @@
+package android.os;
+
+public interface IBinder {
+}
diff --git a/booster-android-api/src/main/java/android/os/IInterface.java b/booster-android-api/src/main/java/android/os/IInterface.java
new file mode 100644
index 000000000..46e4301b9
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/IInterface.java
@@ -0,0 +1,7 @@
+package android.os;
+
+public interface IInterface {
+
+ public IBinder asBinder();
+
+}
diff --git a/booster-android-api/src/main/java/android/os/Looper.java b/booster-android-api/src/main/java/android/os/Looper.java
new file mode 100644
index 000000000..a0dacaaea
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/Looper.java
@@ -0,0 +1,33 @@
+package android.os;
+
+public final class Looper {
+
+ public Thread getThread() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static Looper getMainLooper() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static Looper myLooper() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static void prepare() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public void quit() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public void quitSafely() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public MessageQueue getQueue() {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/os/Message.java b/booster-android-api/src/main/java/android/os/Message.java
new file mode 100644
index 000000000..e825062df
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/Message.java
@@ -0,0 +1,10 @@
+package android.os;
+
+public final class Message implements Parcelable {
+
+ public int what;
+ public int arg1;
+ public int arg2;
+ public Object obj;
+
+}
diff --git a/booster-android-api/src/main/java/android/os/MessageQueue.java b/booster-android-api/src/main/java/android/os/MessageQueue.java
new file mode 100644
index 000000000..fc0da7fb4
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/MessageQueue.java
@@ -0,0 +1,17 @@
+package android.os;
+
+public final class MessageQueue {
+
+ public static interface IdleHandler {
+ boolean queueIdle();
+ }
+
+ public void addIdleHandler(final IdleHandler handler) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public void removeIdleHandler(final IdleHandler handler) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/os/Parcelable.java b/booster-android-api/src/main/java/android/os/Parcelable.java
new file mode 100644
index 000000000..3ac3685e4
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/Parcelable.java
@@ -0,0 +1,4 @@
+package android.os;
+
+public interface Parcelable {
+}
diff --git a/booster-android-api/src/main/java/android/os/Process.java b/booster-android-api/src/main/java/android/os/Process.java
new file mode 100644
index 000000000..7d6179a89
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/Process.java
@@ -0,0 +1,15 @@
+package android.os;
+
+public class Process {
+
+ public static int THREAD_PRIORITY_DEFAULT = 0;
+
+ public static final int myPid() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static final void killProcess(final int pid) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/os/RemoteException.java b/booster-android-api/src/main/java/android/os/RemoteException.java
new file mode 100644
index 000000000..0bbb98b75
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/RemoteException.java
@@ -0,0 +1,7 @@
+package android.os;
+
+import android.util.AndroidException;
+
+public class RemoteException extends AndroidException {
+
+}
diff --git a/booster-android-api/src/main/java/android/os/SystemClock.java b/booster-android-api/src/main/java/android/os/SystemClock.java
new file mode 100644
index 000000000..2fc47239f
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/SystemClock.java
@@ -0,0 +1,45 @@
+package android.os;
+
+public final class SystemClock {
+
+ public static void sleep(long ms) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Sets the current wall time, in milliseconds. Requires the calling
+ * process to have appropriate permissions.
+ *
+ * @return if the clock was successfully set to the specified time.
+ */
+ native public static boolean setCurrentTimeMillis(long millis);
+
+ /**
+ * Returns milliseconds since boot, not counting time spent in deep sleep.
+ *
+ * @return milliseconds of non-sleep uptime since boot.
+ */
+ native public static long uptimeMillis();
+
+ /**
+ * Returns milliseconds since boot, including time spent in sleep.
+ *
+ * @return elapsed milliseconds since boot.
+ */
+ native public static long elapsedRealtime();
+
+ /**
+ * Returns nanoseconds since boot, including time spent in sleep.
+ *
+ * @return elapsed nanoseconds since boot.
+ */
+ native public static long elapsedRealtimeNanos();
+
+ /**
+ * Returns milliseconds running in the current thread.
+ *
+ * @return elapsed milliseconds in the thread
+ */
+ native public static long currentThreadTimeMillis();
+
+}
diff --git a/booster-android-api/src/main/java/android/os/SystemProperties.java b/booster-android-api/src/main/java/android/os/SystemProperties.java
new file mode 100644
index 000000000..7ee94512c
--- /dev/null
+++ b/booster-android-api/src/main/java/android/os/SystemProperties.java
@@ -0,0 +1,29 @@
+package android.os;
+
+public class SystemProperties {
+
+ public static String get(String key) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static String get(String key, String def) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static int getInt(String key, int def) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static long getLong(String key, long def) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static boolean getBoolean(String key, boolean def) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static void set(String key, String val) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/util/AndroidException.java b/booster-android-api/src/main/java/android/util/AndroidException.java
new file mode 100644
index 000000000..c27a48252
--- /dev/null
+++ b/booster-android-api/src/main/java/android/util/AndroidException.java
@@ -0,0 +1,4 @@
+package android.util;
+
+public class AndroidException extends Exception {
+}
diff --git a/booster-android-api/src/main/java/android/util/AndroidRuntimeException.java b/booster-android-api/src/main/java/android/util/AndroidRuntimeException.java
new file mode 100644
index 000000000..6d44b80b6
--- /dev/null
+++ b/booster-android-api/src/main/java/android/util/AndroidRuntimeException.java
@@ -0,0 +1,21 @@
+package android.util;
+
+public class AndroidRuntimeException extends RuntimeException {
+
+ public AndroidRuntimeException() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public AndroidRuntimeException(String name) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public AndroidRuntimeException(String name, Throwable cause) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public AndroidRuntimeException(Exception cause) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/util/ArrayMap.java b/booster-android-api/src/main/java/android/util/ArrayMap.java
new file mode 100644
index 000000000..b6c97b260
--- /dev/null
+++ b/booster-android-api/src/main/java/android/util/ArrayMap.java
@@ -0,0 +1,75 @@
+package android.util;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+public final class ArrayMap implements Map {
+ public ArrayMap() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public ArrayMap(int capacity) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public int size() {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public boolean isEmpty() {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public V get(Object key) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public V put(K key, V value) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public V remove(Object key) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public void putAll(Map extends K, ? extends V> m) {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public void clear() {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public Set keySet() {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public Collection values() {
+ throw new RuntimeException("Stub!");
+ }
+
+ @Override
+ public Set> entrySet() {
+ throw new RuntimeException("Stub!");
+ }
+}
diff --git a/booster-android-api/src/main/java/android/util/AttributeSet.java b/booster-android-api/src/main/java/android/util/AttributeSet.java
new file mode 100644
index 000000000..95cbe4c7a
--- /dev/null
+++ b/booster-android-api/src/main/java/android/util/AttributeSet.java
@@ -0,0 +1,4 @@
+package android.util;
+
+public interface AttributeSet {
+}
diff --git a/booster-android-api/src/main/java/android/util/Log.java b/booster-android-api/src/main/java/android/util/Log.java
new file mode 100644
index 000000000..84f1be48e
--- /dev/null
+++ b/booster-android-api/src/main/java/android/util/Log.java
@@ -0,0 +1,276 @@
+package android.util;
+
+
+/**
+ * API for sending log output.
+ *
+ * Generally, use the Log.v() Log.d() Log.i() Log.w() and Log.e()
+ * methods.
+ *
+ *
The order in terms of verbosity, from least to most is
+ * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled
+ * into an application except during development. Debug logs are compiled
+ * in but stripped at runtime. Error, warning and info logs are always kept.
+ *
+ *
Tip: A good convention is to declare a TAG
constant
+ * in your class:
+ *
+ *
private static final String TAG = "MyActivity";
+ *
+ * and use that in subsequent calls to the log methods.
+ *
+ *
+ * Tip: Don't forget that when you make a call like
+ *
Log.v(TAG, "index=" + i);
+ * that when you're building the string to pass into Log.d, the compiler uses a
+ * StringBuilder and at least three allocations occur: the StringBuilder
+ * itself, the buffer, and the String object. Realistically, there is also
+ * another buffer allocation and copy, and even more pressure on the gc.
+ * That means that if your log message is filtered out, you might be doing
+ * significant work and incurring significant overhead.
+ */
+public final class Log {
+
+ /**
+ * Priority constant for the println method; use Log.v.
+ */
+ public static final int VERBOSE = 2;
+
+ /**
+ * Priority constant for the println method; use Log.d.
+ */
+ public static final int DEBUG = 3;
+
+ /**
+ * Priority constant for the println method; use Log.i.
+ */
+ public static final int INFO = 4;
+
+ /**
+ * Priority constant for the println method; use Log.w.
+ */
+ public static final int WARN = 5;
+
+ /**
+ * Priority constant for the println method; use Log.e.
+ */
+ public static final int ERROR = 6;
+
+ /**
+ * Priority constant for the println method.
+ */
+ public static final int ASSERT = 7;
+
+ private Log() {
+ }
+
+ /**
+ * Send a {@link #VERBOSE} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static int v(String tag, String msg) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Send a {@link #VERBOSE} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static int v(String tag, String msg, Throwable tr) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Send a {@link #DEBUG} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static int d(String tag, String msg) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Send a {@link #DEBUG} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static int d(String tag, String msg, Throwable tr) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Send an {@link #INFO} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static int i(String tag, String msg) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Send a {@link #INFO} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static int i(String tag, String msg, Throwable tr) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Send a {@link #WARN} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static int w(String tag, String msg) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Send a {@link #WARN} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static int w(String tag, String msg, Throwable tr) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Checks to see whether or not a log for the specified tag is loggable at the specified level.
+ *
+ * The default level of any tag is set to INFO. This means that any level above and including
+ * INFO will be logged. Before you make any calls to a logging method you should check to see
+ * if your tag should be logged. You can change the default level by setting a system property:
+ * 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>'
+ * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
+ * turn off all logging for your tag. You can also create a local.prop file that with the
+ * following in it:
+ * 'log.tag.<YOUR_LOG_TAG>=<LEVEL>'
+ * and place that in /data/local.prop.
+ *
+ * @param tag The tag to check.
+ * @param level The level to check.
+ * @return Whether or not that this is allowed to be logged.
+ * @throws IllegalArgumentException is thrown if the tag.length() > 23
+ * for Nougat (7.0) releases (API <= 23) and prior, there is no
+ * tag limit of concern after this API level.
+ */
+ public static native boolean isLoggable(String tag, int level);
+
+ /*
+ * Send a {@link #WARN} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param tr An exception to log
+ */
+ public static int w(String tag, Throwable tr) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Send an {@link #ERROR} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static int e(String tag, String msg) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Send a {@link #ERROR} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static int e(String tag, String msg, Throwable tr) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * What a Terrible Failure: Report a condition that should never happen.
+ * The error will always be logged at level ASSERT with the call stack.
+ * Depending on system configuration, a report may be added to the
+ * {@code android.os.DropBoxManager} and/or the process may be terminated
+ * immediately with an error dialog.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public static int wtf(String tag, String msg) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Like {@link #wtf(String, String)}, but also writes to the log the full
+ * call stack.
+ * @hide
+ */
+ public static int wtfStack(String tag, String msg) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, String)}, with an exception to log.
+ * @param tag Used to identify the source of a log message.
+ * @param tr An exception to log.
+ */
+ public static int wtf(String tag, Throwable tr) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, Throwable)}, with a message as well.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log. May be null.
+ */
+ public static int wtf(String tag, String msg, Throwable tr) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Handy function to get a loggable stack trace from a Throwable
+ * @param tr An exception to log
+ */
+ public static String getStackTraceString(Throwable tr) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Low-level logging call.
+ * @param priority The priority/type of this log message
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @return The number of bytes written.
+ */
+ public static int println(int priority, String tag, String msg) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Helper function for long messages. Uses the LineBreakBufferedWriter to break
+ * up long messages and stacktraces along newlines, but tries to write in large
+ * chunks. This is to avoid truncation.
+ */
+ public static int printlns(int bufID, int priority, String tag, String msg, Throwable tr) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/view/InputEvent.java b/booster-android-api/src/main/java/android/view/InputEvent.java
new file mode 100644
index 000000000..d478adfb6
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/InputEvent.java
@@ -0,0 +1,6 @@
+package android.view;
+
+import android.os.Parcelable;
+
+public abstract class InputEvent implements Parcelable {
+}
diff --git a/booster-android-api/src/main/java/android/view/KeyEvent.java b/booster-android-api/src/main/java/android/view/KeyEvent.java
new file mode 100644
index 000000000..01a2a0588
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/KeyEvent.java
@@ -0,0 +1,10 @@
+package android.view;
+
+import android.os.Parcelable;
+
+public class KeyEvent extends InputEvent implements Parcelable {
+
+ public interface Callback {
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/view/SurfaceHolder.java b/booster-android-api/src/main/java/android/view/SurfaceHolder.java
new file mode 100644
index 000000000..f44552da0
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/SurfaceHolder.java
@@ -0,0 +1,4 @@
+package android.view;
+
+public interface SurfaceHolder {
+}
diff --git a/booster-android-api/src/main/java/android/view/View.java b/booster-android-api/src/main/java/android/view/View.java
new file mode 100644
index 000000000..a8be8f898
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/View.java
@@ -0,0 +1,7 @@
+package android.view;
+
+import android.graphics.drawable.Drawable;
+import android.view.accessibility.AccessibilityEventSource;
+
+public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
+}
diff --git a/booster-android-api/src/main/java/android/view/ViewDebug.java b/booster-android-api/src/main/java/android/view/ViewDebug.java
new file mode 100644
index 000000000..4603c6de4
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/ViewDebug.java
@@ -0,0 +1,8 @@
+package android.view;
+
+public class ViewDebug {
+
+ public interface HierarchyHandler {
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/view/ViewGroup.java b/booster-android-api/src/main/java/android/view/ViewGroup.java
new file mode 100644
index 000000000..22b6b8efe
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/ViewGroup.java
@@ -0,0 +1,43 @@
+package android.view;
+
+public abstract class ViewGroup extends View implements ViewParent, ViewManager {
+
+ /**
+ * Interface definition for a callback to be invoked when the hierarchy
+ * within this view changed. The hierarchy changes whenever a child is added
+ * to or removed from this view.
+ */
+ public interface OnHierarchyChangeListener {
+ /**
+ * Called when a new child is added to a parent view.
+ *
+ * @param parent
+ * the view in which a child was added
+ * @param child
+ * the new child view added in the hierarchy
+ */
+ void onChildViewAdded(View parent, View child);
+
+ /**
+ * Called when a child is removed from a parent view.
+ *
+ * @param parent
+ * the view from which the child was removed
+ * @param child
+ * the child removed from the hierarchy
+ */
+ void onChildViewRemoved(View parent, View child);
+ }
+
+ /**
+ * Register a callback to be invoked when a child is added to or removed
+ * from this view.
+ *
+ * @param listener
+ * the callback to invoke on hierarchy change
+ */
+ public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/view/ViewManager.java b/booster-android-api/src/main/java/android/view/ViewManager.java
new file mode 100644
index 000000000..bf9fc12b1
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/ViewManager.java
@@ -0,0 +1,4 @@
+package android.view;
+
+public interface ViewManager {
+}
diff --git a/booster-android-api/src/main/java/android/view/ViewParent.java b/booster-android-api/src/main/java/android/view/ViewParent.java
new file mode 100644
index 000000000..18025da13
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/ViewParent.java
@@ -0,0 +1,4 @@
+package android.view;
+
+public interface ViewParent {
+}
diff --git a/booster-android-api/src/main/java/android/view/ViewTreeObserver.java b/booster-android-api/src/main/java/android/view/ViewTreeObserver.java
new file mode 100644
index 000000000..eb56482c1
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/ViewTreeObserver.java
@@ -0,0 +1,145 @@
+package android.view;
+
+public final class ViewTreeObserver {
+
+ /**
+ * Interface definition for a callback to be invoked when the view hierarchy is
+ * attached to and detached from its window.
+ */
+ public interface OnWindowAttachListener {
+ /**
+ * Callback method to be invoked when the view hierarchy is attached to a window
+ */
+ public void onWindowAttached();
+
+ /**
+ * Callback method to be invoked when the view hierarchy is detached from a window
+ */
+ public void onWindowDetached();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the view hierarchy's window
+ * focus state changes.
+ */
+ public interface OnWindowFocusChangeListener {
+ /**
+ * Callback method to be invoked when the window focus changes in the view tree.
+ *
+ * @param hasFocus
+ * Set to true if the window is gaining focus, false if it is
+ * losing focus.
+ */
+ public void onWindowFocusChanged(boolean hasFocus);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the focus state within
+ * the view tree changes.
+ */
+ public interface OnGlobalFocusChangeListener {
+ /**
+ * Callback method to be invoked when the focus changes in the view tree. When
+ * the view tree transitions from touch mode to non-touch mode, oldFocus is null.
+ * When the view tree transitions from non-touch mode to touch mode, newFocus is
+ * null. When focus changes in non-touch mode (without transition from or to
+ * touch mode) either oldFocus or newFocus can be null.
+ *
+ * @param oldFocus
+ * The previously focused view, if any.
+ * @param newFocus
+ * The newly focused View, if any.
+ */
+ public void onGlobalFocusChanged(View oldFocus, View newFocus);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the global layout state
+ * or the visibility of views within the view tree changes.
+ */
+ public interface OnGlobalLayoutListener {
+ /**
+ * Callback method to be invoked when the global layout state or the visibility of views
+ * within the view tree changes
+ */
+ public void onGlobalLayout();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the view tree is about to be drawn.
+ */
+ public interface OnPreDrawListener {
+ /**
+ * Callback method to be invoked when the view tree is about to be drawn. At this point, all
+ * views in the tree have been measured and given a frame. Clients can use this to adjust
+ * their scroll bounds or even to request a new layout before drawing occurs.
+ *
+ * @return Return true to proceed with the current drawing pass, or false to cancel.
+ * @see android.view.View#onMeasure
+ * @see android.view.View#onLayout
+ * @see android.view.View#onDraw
+ */
+ public boolean onPreDraw();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the view tree is about to be drawn.
+ */
+ public interface OnDrawListener {
+ /**
+ * Callback method to be invoked when the view tree is about to be drawn. At this point,
+ * views cannot be modified in any way.
+ *
+ * Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the
+ * current drawing pass.
+ *
+ * An {@link OnDrawListener} listener cannot be added or removed
+ * from this method.
+ *
+ * @see android.view.View#onMeasure
+ * @see android.view.View#onLayout
+ * @see android.view.View#onDraw
+ */
+ public void onDraw();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the touch mode changes.
+ */
+ public interface OnTouchModeChangeListener {
+ /**
+ * Callback method to be invoked when the touch mode changes.
+ *
+ * @param isInTouchMode
+ * True if the view hierarchy is now in touch mode, false otherwise.
+ */
+ public void onTouchModeChanged(boolean isInTouchMode);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when
+ * something in the view tree has been scrolled.
+ */
+ public interface OnScrollChangedListener {
+ /**
+ * Callback method to be invoked when something in the view tree
+ * has been scrolled.
+ */
+ public void onScrollChanged();
+ }
+
+ /**
+ * Interface definition for a callback noting when a system window has been displayed.
+ * This is only used for non-Activity windows. Activity windows can use
+ * Activity.onEnterAnimationComplete() to get the same signal.
+ *
+ * @hide
+ */
+ public interface OnWindowShownListener {
+ /**
+ * Callback method to be invoked when a non-activity window is fully shown.
+ */
+ void onWindowShown();
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/view/WindowManager.java b/booster-android-api/src/main/java/android/view/WindowManager.java
new file mode 100644
index 000000000..eccdf56d1
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/WindowManager.java
@@ -0,0 +1,15 @@
+package android.view;
+
+public interface WindowManager extends ViewManager {
+
+ public static class BadTokenException extends RuntimeException {
+ public BadTokenException() {
+ throw new RuntimeException("Stub!");
+ }
+
+ public BadTokenException(String name) {
+ throw new RuntimeException("Stub!");
+ }
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/view/accessibility/AccessibilityEventSource.java b/booster-android-api/src/main/java/android/view/accessibility/AccessibilityEventSource.java
new file mode 100644
index 000000000..60a6cff22
--- /dev/null
+++ b/booster-android-api/src/main/java/android/view/accessibility/AccessibilityEventSource.java
@@ -0,0 +1,4 @@
+package android.view.accessibility;
+
+public interface AccessibilityEventSource {
+}
diff --git a/booster-android-api/src/main/java/android/webkit/CookieManager.java b/booster-android-api/src/main/java/android/webkit/CookieManager.java
new file mode 100644
index 000000000..8683e7404
--- /dev/null
+++ b/booster-android-api/src/main/java/android/webkit/CookieManager.java
@@ -0,0 +1,4 @@
+package android.webkit;
+
+public abstract class CookieManager {
+}
diff --git a/booster-android-api/src/main/java/android/webkit/IWebViewUpdateService.java b/booster-android-api/src/main/java/android/webkit/IWebViewUpdateService.java
new file mode 100644
index 000000000..85674cae6
--- /dev/null
+++ b/booster-android-api/src/main/java/android/webkit/IWebViewUpdateService.java
@@ -0,0 +1,9 @@
+package android.webkit;
+
+import android.os.IInterface;
+
+public interface IWebViewUpdateService extends IInterface {
+
+ String getCurrentWebViewPackageName();
+
+}
diff --git a/booster-android-api/src/main/java/android/webkit/WebSettings.java b/booster-android-api/src/main/java/android/webkit/WebSettings.java
new file mode 100644
index 000000000..39a7b98e2
--- /dev/null
+++ b/booster-android-api/src/main/java/android/webkit/WebSettings.java
@@ -0,0 +1,11 @@
+package android.webkit;
+
+import android.content.Context;
+
+public abstract class WebSettings {
+
+ public static String getDefaultUserAgent(Context context) {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/webkit/WebStorage.java b/booster-android-api/src/main/java/android/webkit/WebStorage.java
new file mode 100644
index 000000000..ac374409c
--- /dev/null
+++ b/booster-android-api/src/main/java/android/webkit/WebStorage.java
@@ -0,0 +1,4 @@
+package android.webkit;
+
+public class WebStorage {
+}
diff --git a/booster-android-api/src/main/java/android/webkit/WebView.java b/booster-android-api/src/main/java/android/webkit/WebView.java
new file mode 100644
index 000000000..9628e3f06
--- /dev/null
+++ b/booster-android-api/src/main/java/android/webkit/WebView.java
@@ -0,0 +1,96 @@
+package android.webkit;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.AbsoluteLayout;
+
+public class WebView extends AbsoluteLayout implements ViewTreeObserver.OnGlobalFocusChangeListener, ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
+
+ /**
+ * Constructs a new WebView with a Context object.
+ *
+ * @param context a Context object used to access application assets
+ */
+ public WebView(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Constructs a new WebView with layout parameters.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ */
+ public WebView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Constructs a new WebView with layout parameters and a default style.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ */
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Constructs a new WebView with layout parameters and a default style.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes a resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ throw new RuntimeException("Stub!");
+ }
+
+ /**
+ * Constructs a new WebView with layout parameters and a default style.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param privateBrowsing whether this WebView will be initialized in
+ * private mode
+ *
+ * @deprecated Private browsing is no longer supported directly via
+ * WebView and will be removed in a future release. Prefer using
+ * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
+ * and {@link WebStorage} for fine-grained control of privacy data.
+ */
+ @Deprecated
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {
+ throw new RuntimeException("Stub!");
+ }
+
+
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ }
+
+ @Override
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/webkit/WebViewDatabase.java b/booster-android-api/src/main/java/android/webkit/WebViewDatabase.java
new file mode 100644
index 000000000..69c5b93a4
--- /dev/null
+++ b/booster-android-api/src/main/java/android/webkit/WebViewDatabase.java
@@ -0,0 +1,4 @@
+package android.webkit;
+
+public abstract class WebViewDatabase {
+}
diff --git a/booster-android-api/src/main/java/android/webkit/WebViewFactory.java b/booster-android-api/src/main/java/android/webkit/WebViewFactory.java
new file mode 100644
index 000000000..f65f41c99
--- /dev/null
+++ b/booster-android-api/src/main/java/android/webkit/WebViewFactory.java
@@ -0,0 +1,13 @@
+package android.webkit;
+
+public final class WebViewFactory {
+
+ public static Class getWebViewProviderClass(ClassLoader clazzLoader) {
+ throw new RuntimeException("Stub!");
+ }
+
+ public static IWebViewUpdateService getUpdateService() {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-api/src/main/java/android/webkit/WebViewFactoryProvider.java b/booster-android-api/src/main/java/android/webkit/WebViewFactoryProvider.java
new file mode 100644
index 000000000..f1dedef72
--- /dev/null
+++ b/booster-android-api/src/main/java/android/webkit/WebViewFactoryProvider.java
@@ -0,0 +1,4 @@
+package android.webkit;
+
+public interface WebViewFactoryProvider {
+}
diff --git a/booster-android-api/src/main/java/android/widget/AbsoluteLayout.java b/booster-android-api/src/main/java/android/widget/AbsoluteLayout.java
new file mode 100644
index 000000000..0eea63d2c
--- /dev/null
+++ b/booster-android-api/src/main/java/android/widget/AbsoluteLayout.java
@@ -0,0 +1,4 @@
+package android.widget;
+
+public class AbsoluteLayout {
+}
diff --git a/booster-android-api/src/main/java/android/widget/Toast.java b/booster-android-api/src/main/java/android/widget/Toast.java
new file mode 100644
index 000000000..f8c37531c
--- /dev/null
+++ b/booster-android-api/src/main/java/android/widget/Toast.java
@@ -0,0 +1,9 @@
+package android.widget;
+
+public class Toast {
+
+ public void show() {
+ throw new RuntimeException("Stub!");
+ }
+
+}
diff --git a/booster-android-bugfix-toast/build.gradle b/booster-android-bugfix-toast/build.gradle
new file mode 100644
index 000000000..6eb6d389c
--- /dev/null
+++ b/booster-android-bugfix-toast/build.gradle
@@ -0,0 +1,7 @@
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
+
+dependencies {
+ compileOnly project(':booster-android-api')
+ compile project(':booster-android-bugfix')
+}
diff --git a/booster-android-bugfix-toast/src/main/java/com/didiglobal/booster/android/widget/Toast.java b/booster-android-bugfix-toast/src/main/java/com/didiglobal/booster/android/widget/Toast.java
new file mode 100644
index 000000000..dcc0a45e7
--- /dev/null
+++ b/booster-android-bugfix-toast/src/main/java/com/didiglobal/booster/android/widget/Toast.java
@@ -0,0 +1,54 @@
+package com.didiglobal.booster.android.widget;
+
+import android.os.Build;
+import android.os.Handler;
+import android.util.Log;
+import com.didiglobal.booster.android.bugfix.CaughtCallback;
+import com.didiglobal.booster.android.bugfix.CaughtRunnable;
+
+import static com.didiglobal.booster.android.bugfix.Constants.TAG;
+import static com.didiglobal.booster.android.bugfix.Reflection.getFieldValue;
+import static com.didiglobal.booster.android.bugfix.Reflection.setFieldValue;
+
+public class Toast {
+
+ /**
+ * Fix {@code WindowManager$BadTokenException} for Android N
+ *
+ * @param toast
+ * The original toast
+ */
+ public static void show(final android.widget.Toast toast) {
+ if (Build.VERSION.SDK_INT == 25) {
+ workaround(toast).show();
+ } else {
+ toast.show();
+ }
+ }
+
+ private static android.widget.Toast workaround(final android.widget.Toast toast) {
+ final Object tn = getFieldValue(toast, "mTN");
+ if (null == tn) {
+ Log.w(TAG, "Field mTN of " + toast + " is null");
+ return toast;
+ }
+
+ final Object handler = getFieldValue(tn, "mHandler");
+ if (handler instanceof Handler) {
+ if (setFieldValue(handler, "mCallback", new CaughtCallback((Handler) handler))) {
+ return toast;
+ }
+ }
+
+ final Object show = getFieldValue(tn, "mShow");
+ if (show instanceof Runnable) {
+ if (setFieldValue(tn, "mShow", new CaughtRunnable((Runnable) show))) {
+ return toast;
+ }
+ }
+
+ Log.w(TAG, "Neither field mHandler nor mShow of " + tn + " is accessible");
+ return toast;
+ }
+
+}
diff --git a/booster-android-bugfix/build.gradle b/booster-android-bugfix/build.gradle
new file mode 100644
index 000000000..22a4e3818
--- /dev/null
+++ b/booster-android-bugfix/build.gradle
@@ -0,0 +1,6 @@
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
+
+dependencies {
+ compileOnly project(':booster-android-api')
+}
diff --git a/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtCallback.java b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtCallback.java
new file mode 100644
index 000000000..1d899ba8f
--- /dev/null
+++ b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtCallback.java
@@ -0,0 +1,23 @@
+package com.didiglobal.booster.android.bugfix;
+
+import android.os.Handler;
+import android.os.Message;
+
+public class CaughtCallback implements Handler.Callback {
+
+ private final Handler mHandler;
+
+ public CaughtCallback(final Handler handler) {
+ this.mHandler = handler;
+ }
+
+ @Override
+ public boolean handleMessage(final Message msg) {
+ try {
+ this.mHandler.handleMessage(msg);
+ } catch (final RuntimeException e) {
+ // ignore
+ }
+ return true;
+ }
+}
diff --git a/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtRunnable.java b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtRunnable.java
new file mode 100644
index 000000000..2d8375f81
--- /dev/null
+++ b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtRunnable.java
@@ -0,0 +1,19 @@
+package com.didiglobal.booster.android.bugfix;
+
+public class CaughtRunnable implements Runnable {
+
+ private final Runnable mRunnable;
+
+ public CaughtRunnable(final Runnable runnable) {
+ this.mRunnable = runnable;
+ }
+
+ @Override
+ public void run() {
+ try {
+ this.mRunnable.run();
+ } catch (final RuntimeException e) {
+ // ignore
+ }
+ }
+}
diff --git a/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Constants.java b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Constants.java
new file mode 100644
index 000000000..7acdbdfef
--- /dev/null
+++ b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Constants.java
@@ -0,0 +1,7 @@
+package com.didiglobal.booster.android.bugfix;
+
+public interface Constants {
+
+ String TAG = "booster";
+
+}
diff --git a/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Reflection.java b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Reflection.java
new file mode 100644
index 000000000..cf3ed8a9e
--- /dev/null
+++ b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Reflection.java
@@ -0,0 +1,225 @@
+package com.didiglobal.booster.android.bugfix;
+
+import android.util.Log;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+public abstract class Reflection {
+
+ @SuppressWarnings("unchecked")
+ public static T getStaticFieldValue(final Class> klass, final String name) {
+ if (null != klass && null != name) {
+ try {
+ final Field field = getField(klass, name);
+ if (null != field) {
+ field.setAccessible(true);
+ return (T) field.get(klass);
+ }
+ } catch (final Throwable t) {
+ Log.w(Constants.TAG, "get field " + name + " of " + klass + " error", t);
+ }
+ }
+
+ return null;
+ }
+
+ public static boolean setStaticFieldValue(final Class> klass, final String name, final Object value) {
+ if (null != klass && null != name) {
+ try {
+ final Field field = getField(klass, name);
+ if (null != field) {
+ field.setAccessible(true);
+ field.set(klass, value);
+ return true;
+ }
+ } catch (final Throwable t) {
+ Log.w(Constants.TAG, "set field " + name + " of " + klass + " error", t);
+ }
+ }
+
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T getFieldValue(final Object obj, final String name) {
+ if (null != obj && null != name) {
+ try {
+ final Field field = getField(obj.getClass(), name);
+ if (null != field) {
+ field.setAccessible(true);
+ return (T) field.get(obj);
+ }
+ } catch (final Throwable t) {
+ Log.w(Constants.TAG, "get field " + name + " of " + obj + " error", t);
+ }
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T getFieldValue(final Object obj, final Class> type) {
+ if (null != obj && null != type) {
+ try {
+ final Field field = getField(obj.getClass(), type);
+ if (null != field) {
+ field.setAccessible(true);
+ return (T) field.get(obj);
+ }
+ } catch (final Throwable t) {
+ Log.w(Constants.TAG, "get field with type " + type + " of " + obj + " error", t);
+ }
+ }
+
+ return null;
+ }
+
+ public static boolean setFieldValue(final Object obj, final String name, final Object value) {
+ if (null != obj && null != name) {
+ try {
+ final Field field = getField(obj.getClass(), name);
+ if (null != field) {
+ field.setAccessible(true);
+ field.set(obj, value);
+ return true;
+ }
+ } catch (final Throwable t) {
+ Log.w(Constants.TAG, "set field " + name + " of " + obj + " error", t);
+ }
+ }
+
+ return false;
+ }
+
+ public static T newInstance(final String className, final Object... args) {
+ try {
+ return newInstance(Class.forName(className), args);
+ } catch (final ClassNotFoundException e) {
+ Log.w(Constants.TAG, "new instance of " + className + " error", e);
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T newInstance(final Class> clazz, Object... args) {
+ final Constructor>[] ctors = clazz.getDeclaredConstructors();
+
+ loop:
+ for (final Constructor> ctor : ctors) {
+ final Class>[] types = ctor.getParameterTypes();
+ if (types.length == args.length) {
+ for (int i = 0; i < types.length; i++) {
+ if (null != args[i] && !types[i].isAssignableFrom(args[i].getClass())) {
+ continue loop;
+ }
+ }
+
+ try {
+ ctor.setAccessible(true);
+ return (T) ctor.newInstance(args);
+ } catch (final Throwable t) {
+ Log.w(Constants.TAG, "Invoke constructor " + ctor + " error", t);
+ return null;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T invokeStaticMethod(final Class> klass, final String name) {
+ return invokeStaticMethod(klass, name, new Class[0], new Object[0]);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T invokeStaticMethod(final Class> klass, final String name, final Class[] types, final Object[] args) {
+ if (null != klass && null != name && null != types && null != args && types.length == args.length) {
+ try {
+ final Method method = getMethod(klass, name, types);
+ if (null != method) {
+ method.setAccessible(true);
+ return (T) method.invoke(klass, args);
+ }
+ } catch (final Throwable e) {
+ Log.w(Constants.TAG, "Invoke " + name + "(" + Arrays.toString(types) + ") of " + klass + " error", e);
+ }
+ }
+
+ return null;
+ }
+
+
+ @SuppressWarnings("unchecked")
+ public static T invokeMethod(final Object obj, final String name) {
+ return invokeMethod(obj, name, new Class[0], new Object[0]);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T invokeMethod(final Object obj, final String name, final Class[] types, final Object[] args) {
+ if (null != obj && null != name && null != types && null != args && types.length == args.length) {
+ try {
+ final Method method = getMethod(obj.getClass(), name, types);
+ if (null != method) {
+ method.setAccessible(true);
+ return (T) method.invoke(obj, args);
+ }
+ } catch (final Throwable e) {
+ Log.w(Constants.TAG, "Invoke " + name + "(" + Arrays.toString(types) + ") of " + obj + " error", e);
+ }
+ }
+
+ return null;
+ }
+
+ public static Field getField(final Class> klass, final String name) {
+ try {
+ return klass.getDeclaredField(name);
+ } catch (final NoSuchFieldException e) {
+ final Class> parent = klass.getSuperclass();
+ if (null == parent) {
+ return null;
+ }
+ return getField(parent, name);
+ }
+ }
+
+ public static Field getField(final Class> klass, final Class> type) {
+ final Field[] fields = klass.getDeclaredFields();
+ if (fields.length <= 0) {
+ final Class> parent = klass.getSuperclass();
+ if (null == parent) {
+ return null;
+ }
+ return getField(parent, type);
+ }
+
+ for (final Field field : fields) {
+ if (field.getType() == type) {
+ return field;
+ }
+ }
+
+ return null;
+ }
+
+ private static Method getMethod(final Class> klass, final String name, final Class>[] types) {
+ try {
+ return klass.getDeclaredMethod(name, types);
+ } catch (final NoSuchMethodException e) {
+ final Class> parent = klass.getSuperclass();
+ if (null == parent) {
+ return null;
+ }
+ return getMethod(parent, name, types);
+ }
+ }
+
+ private Reflection() {
+ }
+
+}
+
diff --git a/booster-android-gradle-api/build.gradle b/booster-android-gradle-api/build.gradle
new file mode 100644
index 000000000..585d4fd42
--- /dev/null
+++ b/booster-android-gradle-api/build.gradle
@@ -0,0 +1,8 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ compile project(':booster-android-gradle-v3_0')
+ compile project(':booster-android-gradle-v3_2')
+ compileOnly 'com.android.tools.build:gradle:3.0.0'
+ testCompileOnly 'com.android.tools.build:gradle:3.0.0'
+}
diff --git a/booster-android-gradle-api/src/main/kotlin/com/didiglobal/booster/gradle/VariantScope.kt b/booster-android-gradle-api/src/main/kotlin/com/didiglobal/booster/gradle/VariantScope.kt
new file mode 100644
index 000000000..32bf9c7c7
--- /dev/null
+++ b/booster-android-gradle-api/src/main/kotlin/com/didiglobal/booster/gradle/VariantScope.kt
@@ -0,0 +1,67 @@
+package com.didiglobal.booster.gradle
+
+import com.android.build.gradle.internal.scope.VariantScope
+import com.android.builder.model.Version
+import com.android.repository.Revision
+import java.io.File
+
+private val ANDROID_GRADLE_PLUGIN_VERSION = Revision.parseRevision(Version.ANDROID_GRADLE_PLUGIN_VERSION)
+private val GTE_V32 = ANDROID_GRADLE_PLUGIN_VERSION.major >= 3 && ANDROID_GRADLE_PLUGIN_VERSION.minor >= 2
+private val ALL_ARTIFACTS_GETTER = if (GTE_V32) VariantScopeV32::getAllArtifacts else VariantScopeV30::getAllArtifacts
+private val ALL_CLASSES_GETTER = if (GTE_V32) VariantScopeV32::getAllClasses else VariantScopeV30::getAllClasses
+private val APK_GETTER = if (GTE_V32) VariantScopeV32::getApk else VariantScopeV30::getApk
+private val JAVAC_GETTER = if (GTE_V32) VariantScopeV32::getJavac else VariantScopeV30::getJavac
+private val MERGED_ASSETS_GETTER = if (GTE_V32) VariantScopeV32::getMergedAssets else VariantScopeV30::getMergedAssets
+private val MERGED_MANIFESTS_GETTER = if (GTE_V32) VariantScopeV32::getMergedManifests else VariantScopeV30::getMergedManifests
+private val MERGED_RESOURCE_GETTER = if (GTE_V32) VariantScopeV32::getMergedRes else VariantScopeV30::getMergedRes
+private val PROCESSED_RES_GETTER = if (GTE_V32) VariantScopeV32::getProcessedRes else VariantScopeV30::getProcessedRes
+private val SYMBOL_LIST_GETTER = if (GTE_V32) VariantScopeV32::getSymbolList else VariantScopeV30::getSymbolList
+private val SYMBOL_LIST_WITH_PACKAGE_NAME_GETTER = if (GTE_V32) VariantScopeV32::getSymbolListWithPackageName else VariantScopeV30::getSymbolListWithPackageName
+
+/**
+ * The output directory of APK files
+ */
+val VariantScope.apk: Collection
+ get() = APK_GETTER(this)
+
+val VariantScope.javac: Collection
+ get() = JAVAC_GETTER(this)
+
+/**
+ * The output directory of merged [AndroidManifest.xml](https://developer.android.com/guide/topics/manifest/manifest-intro)
+ */
+val VariantScope.mergedManifests: Collection
+ get() = MERGED_MANIFESTS_GETTER(this)
+
+/**
+ * The output directory of merged resources
+ */
+val VariantScope.mergedRes: Collection
+ get() = MERGED_RESOURCE_GETTER(this)
+
+/**
+ * The output directory of merged assets
+ */
+val VariantScope.mergedAssets: Collection
+ get() = MERGED_ASSETS_GETTER(this)
+
+/**
+ * The output directory of processed resources: *resources-**variant**.ap\_*
+ */
+val VariantScope.processedRes: Collection
+ get() = PROCESSED_RES_GETTER(this)
+
+/**
+ * All of classes
+ */
+val VariantScope.allClasses: Collection
+ get() = ALL_CLASSES_GETTER(this)
+
+val VariantScope.symbolList: Collection
+ get() = SYMBOL_LIST_GETTER(this)
+
+val VariantScope.symbolListWithPackageName: Collection
+ get() = SYMBOL_LIST_WITH_PACKAGE_NAME_GETTER(this)
+
+val VariantScope.allArtifacts: Map>
+ get() = ALL_ARTIFACTS_GETTER(this)
diff --git a/booster-android-gradle-v3_0/build.gradle b/booster-android-gradle-v3_0/build.gradle
new file mode 100644
index 000000000..f09cbc96d
--- /dev/null
+++ b/booster-android-gradle-v3_0/build.gradle
@@ -0,0 +1,7 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ compile gradleApi()
+ compileOnly 'com.android.tools.build:gradle:3.0.0'
+ testCompileOnly 'com.android.tools.build:gradle:3.0.0'
+}
diff --git a/booster-android-gradle-v3_0/src/main/java/com/didiglobal/booster/gradle/VariantScopeV30.java b/booster-android-gradle-v3_0/src/main/java/com/didiglobal/booster/gradle/VariantScopeV30.java
new file mode 100644
index 000000000..a083b0524
--- /dev/null
+++ b/booster-android-gradle-v3_0/src/main/java/com/didiglobal/booster/gradle/VariantScopeV30.java
@@ -0,0 +1,94 @@
+package com.didiglobal.booster.gradle;
+
+import com.android.build.gradle.internal.scope.TaskOutputHolder.AnchorOutputType;
+import com.android.build.gradle.internal.scope.TaskOutputHolder.OutputType;
+import com.android.build.gradle.internal.scope.TaskOutputHolder.TaskOutputType;
+import com.android.build.gradle.internal.scope.VariantScope;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+class VariantScopeV30 {
+
+ /**
+ * The merged AndroidManifest.xml
+ */
+ @NotNull
+ static Collection getMergedManifests(@NotNull final VariantScope scope) {
+ return scope.getOutput(TaskOutputType.MERGED_MANIFESTS).getFiles();
+ }
+
+ /**
+ * The merged resources
+ */
+ @NotNull
+ static Collection getMergedRes(@NotNull final VariantScope scope) {
+ return scope.getOutput(TaskOutputType.MERGED_RES).getFiles();
+ }
+
+ /**
+ * The merged assets
+ */
+ @NotNull
+ static Collection getMergedAssets(@NotNull final VariantScope scope) {
+ return scope.getOutput(TaskOutputType.MERGED_ASSETS).getFiles();
+ }
+
+ /**
+ * The processed resources
+ */
+ @NotNull
+ static Collection getProcessedRes(@NotNull final VariantScope scope) {
+ return scope.getOutput(TaskOutputType.PROCESSED_RES).getFiles();
+ }
+
+ /**
+ * All of classes
+ */
+ @NotNull
+ static Collection getAllClasses(@NotNull final VariantScope scope) {
+ return scope.getOutput(AnchorOutputType.ALL_CLASSES).getFiles();
+ }
+
+ @NotNull
+ static Collection getSymbolList(@NotNull final VariantScope scope) {
+ return getOutput(scope, TaskOutputType.SYMBOL_LIST);
+ }
+
+ @NotNull
+ static Collection getSymbolListWithPackageName(@NotNull final VariantScope scope) {
+ return getOutput(scope, TaskOutputType.SYMBOL_LIST_WITH_PACKAGE_NAME);
+ }
+
+ @NotNull
+ static Collection getApk(@NotNull final VariantScope scope) {
+ return getOutput(scope, TaskOutputType.APK);
+ }
+
+ @NotNull
+ static Collection getJavac(@NotNull final VariantScope scope) {
+ return getOutput(scope, TaskOutputType.JAVAC);
+ }
+
+ @NotNull
+ static Map> getAllArtifacts(@NotNull final VariantScope scope) {
+ return Stream.concat(Arrays.stream(TaskOutputType.values()), Arrays.stream(AnchorOutputType.values()))
+ .collect(Collectors.toMap(OutputType::name, v -> getOutput(scope, v)));
+ }
+
+ @NotNull
+ static Collection getOutput(@NotNull final VariantScope scope, @NotNull final OutputType type) {
+ try {
+ return scope.getOutput(type).getFiles();
+ } catch (final RuntimeException e) {
+ return Collections.emptyList();
+ }
+ }
+
+}
diff --git a/booster-android-gradle-v3_2/build.gradle b/booster-android-gradle-v3_2/build.gradle
new file mode 100644
index 000000000..2dbab4af6
--- /dev/null
+++ b/booster-android-gradle-v3_2/build.gradle
@@ -0,0 +1,7 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ compile gradleApi()
+ compileOnly 'com.android.tools.build:gradle:3.2.0'
+ testCompileOnly 'com.android.tools.build:gradle:3.2.0'
+}
diff --git a/booster-android-gradle-v3_2/src/main/java/com/didiglobal/booster/gradle/VariantScopeV32.java b/booster-android-gradle-v3_2/src/main/java/com/didiglobal/booster/gradle/VariantScopeV32.java
new file mode 100644
index 000000000..f021f1399
--- /dev/null
+++ b/booster-android-gradle-v3_2/src/main/java/com/didiglobal/booster/gradle/VariantScopeV32.java
@@ -0,0 +1,95 @@
+package com.didiglobal.booster.gradle;
+
+import com.android.build.api.artifact.ArtifactType;
+import com.android.build.gradle.internal.scope.AnchorOutputType;
+import com.android.build.gradle.internal.scope.InternalArtifactType;
+import com.android.build.gradle.internal.scope.VariantScope;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+
+class VariantScopeV32 {
+
+ /**
+ * The merged AndroidManifest.xml
+ */
+ @NotNull
+ static Collection getMergedManifests(@NotNull final VariantScope scope) {
+ return getFinalArtifactFiles(scope, InternalArtifactType.MERGED_MANIFESTS);
+ }
+
+ /**
+ * The merged resources
+ */
+ @NotNull
+ static Collection getMergedRes(@NotNull final VariantScope scope) {
+ return getFinalArtifactFiles(scope, InternalArtifactType.MERGED_RES);
+ }
+
+ /**
+ * The merged assets
+ */
+ @NotNull
+ static Collection getMergedAssets(@NotNull final VariantScope scope) {
+ return getFinalArtifactFiles(scope, InternalArtifactType.MERGED_ASSETS);
+ }
+
+ /**
+ * The processed resources
+ */
+ @NotNull
+ static Collection getProcessedRes(@NotNull final VariantScope scope) {
+ return getFinalArtifactFiles(scope, InternalArtifactType.PROCESSED_RES);
+ }
+
+ /**
+ * All of classes
+ */
+ @NotNull
+ static Collection getAllClasses(@NotNull final VariantScope scope) {
+ return getFinalArtifactFiles(scope, AnchorOutputType.ALL_CLASSES);
+ }
+
+ @NotNull
+ static Collection getSymbolList(@NotNull final VariantScope scope) {
+ return getFinalArtifactFiles(scope, InternalArtifactType.SYMBOL_LIST);
+ }
+
+ @NotNull
+ static Collection getSymbolListWithPackageName(@NotNull final VariantScope scope) {
+ return getFinalArtifactFiles(scope, InternalArtifactType.SYMBOL_LIST_WITH_PACKAGE_NAME);
+ }
+
+ @NotNull
+ static Collection getApk(@NotNull final VariantScope scope) {
+ return getFinalArtifactFiles(scope, InternalArtifactType.APK);
+ }
+
+ @NotNull
+ static Collection getJavac(@NotNull final VariantScope scope) {
+ return getFinalArtifactFiles(scope, InternalArtifactType.JAVAC);
+ }
+
+ @NotNull
+ static Map> getAllArtifacts(@NotNull final VariantScope scope) {
+ return Stream.concat(Arrays.stream(InternalArtifactType.values()), Arrays.stream(AnchorOutputType.values()))
+ .collect(Collectors.toMap(Enum::name, v -> getFinalArtifactFiles(scope, v)));
+ }
+
+ @NotNull
+ static Collection getFinalArtifactFiles(@NotNull final VariantScope scope, @NotNull final ArtifactType type) {
+ try {
+ return scope.getArtifacts().getFinalArtifactFiles(type).getFiles();
+ } catch (final RuntimeException e) {
+ return Collections.emptyList();
+ }
+ }
+
+}
diff --git a/booster-gradle-base/build.gradle b/booster-gradle-base/build.gradle
new file mode 100644
index 000000000..faa2ed56b
--- /dev/null
+++ b/booster-gradle-base/build.gradle
@@ -0,0 +1,10 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ compile gradleApi()
+ compile project(':booster-kotlinx')
+ compile project(':booster-android-gradle-api')
+ compile 'com.google.auto.service:auto-service:1.0-rc4'
+ compileOnly 'com.android.tools.build:gradle:3.0.0'
+ testCompileOnly 'com.android.tools.build:gradle:3.0.0'
+}
diff --git a/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/BaseVariant.kt b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/BaseVariant.kt
new file mode 100644
index 000000000..f563e51f3
--- /dev/null
+++ b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/BaseVariant.kt
@@ -0,0 +1,30 @@
+package com.didiglobal.booster.gradle
+
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.internal.api.InstallableVariantImpl
+import com.android.build.gradle.internal.scope.VariantScope
+import com.android.build.gradle.internal.variant.BaseVariantData
+
+/**
+ * The variant dependencies
+ *
+ * @author johnsonlee
+ */
+val BaseVariant.dependencies: ResolvedArtifactResults
+ get() = ResolvedArtifactResults(this)
+
+/**
+ * The variant scope
+ *
+ * @author johnsonlee
+ */
+val BaseVariant.scope: VariantScope
+ get() = variantData.scope
+
+/**
+ * The variant data
+ *
+ * @author johnsonlee
+ */
+val BaseVariant.variantData: BaseVariantData
+ get() = if (this is InstallableVariantImpl) this.variantData else javaClass.getDeclaredMethod("getVariantData").invoke(this) as BaseVariantData
diff --git a/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/Project.kt b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/Project.kt
new file mode 100644
index 000000000..7e56a2988
--- /dev/null
+++ b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/Project.kt
@@ -0,0 +1,13 @@
+package com.didiglobal.booster.gradle
+
+import com.android.build.gradle.BaseExtension
+import org.gradle.api.Project
+
+/**
+ * Returns android extension
+ *
+ * @author johnsonlee
+ */
+inline fun Project.getAndroid(): T {
+ return extensions.getByName("android") as T
+}
diff --git a/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/ResolvedArtifactResults.kt b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/ResolvedArtifactResults.kt
new file mode 100644
index 000000000..d5a5dd9c5
--- /dev/null
+++ b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/ResolvedArtifactResults.kt
@@ -0,0 +1,82 @@
+package com.didiglobal.booster.gradle
+
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.internal.publishing.AndroidArtifacts
+import com.didiglobal.booster.kotlinx.Formatter
+import com.didiglobal.booster.kotlinx.file
+import com.didiglobal.booster.kotlinx.int
+import com.didiglobal.booster.kotlinx.separatorsToSystem
+import com.didiglobal.booster.kotlinx.touch
+import org.gradle.api.artifacts.component.ProjectComponentIdentifier
+import org.gradle.api.artifacts.result.ResolvedArtifactResult
+import java.io.File
+import java.io.PrintWriter
+
+/**
+ * Represents the dependencies of the specified variant
+ */
+class ResolvedArtifactResults(private val variant: BaseVariant) : Collection {
+
+ private val results: Iterable
+ private val maxNameWidth: Int
+ private val maxFileWidth: Int
+
+ init {
+ results = listOf(AndroidArtifacts.ArtifactType.AAR, AndroidArtifacts.ArtifactType.JAR)
+ .asSequence()
+ .map { variant.variantData.scope.getArtifactCollection(AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, AndroidArtifacts.ArtifactScope.ALL, it) }
+ .map { it.artifacts }
+ .flatten()
+ .filter { it.id.componentIdentifier !is ProjectComponentIdentifier }
+ .toList()
+ .distinctBy { it.id.componentIdentifier.displayName }
+ .sortedBy { it.id.componentIdentifier.displayName }
+ .toList()
+ maxNameWidth = int(map { it.id.componentIdentifier.displayName.length }.max()).value
+ maxFileWidth = int(map { it.file.path.length }.max()).value
+ }
+
+ override val size: Int
+ get() = results.count()
+
+ override fun contains(element: ResolvedArtifactResult) = results.contains(element)
+
+ override fun containsAll(elements: Collection) = results.intersect(elements).size == elements.size
+
+ override fun isEmpty() = results.iterator().hasNext()
+
+ override fun iterator(): Iterator = results.iterator()
+
+ /**
+ * Default output location: $buildDir/intermediates/dependencies/${variantDirName}/dependencies.txt
+ */
+ private val output = variant.variantData.scope.globalScope.intermediatesDir.file("dependencies").file(variant.dirName.separatorsToSystem()).file("dependencies.txt")
+
+ /**
+ * Default dependency formatter
+ */
+ private val formatter: Formatter = { result ->
+ result.id.componentIdentifier.displayName + " ".repeat(maxNameWidth + 1 - result.id.componentIdentifier.displayName.length) + result.file + " ".repeat(maxFileWidth + 1 - result.file.path.length) + result.file.length()
+ }
+
+ /**
+ * Dump it to file with specific formatter
+ */
+ fun dump(file: File = output, formatter: Formatter = this.formatter) {
+ file.touch().printWriter().use {
+ print(it, formatter)
+ }
+ }
+
+ /**
+ * Print all component artifacts
+ */
+ fun print(printer: PrintWriter = PrintWriter(System.out, true), formatter: Formatter = this.formatter) {
+ forEach { result ->
+ printer.apply {
+ println(formatter(result))
+ }.flush()
+ }
+ }
+
+}
diff --git a/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/TransformInvocation.kt b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/TransformInvocation.kt
new file mode 100644
index 000000000..d0339ffec
--- /dev/null
+++ b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/TransformInvocation.kt
@@ -0,0 +1,63 @@
+package com.didiglobal.booster.gradle
+
+import com.android.build.api.transform.TransformInvocation
+import com.android.build.gradle.AppExtension
+import com.android.build.gradle.BaseExtension
+import com.android.build.gradle.LibraryExtension
+import com.android.build.gradle.api.BaseVariant
+import org.gradle.api.Project
+import org.gradle.api.internal.AbstractTask
+import java.io.File
+import java.net.URLClassLoader
+
+/**
+ * Represents the booster transform for
+ *
+ * @author johnsonlee
+ */
+val TransformInvocation.project: Project
+ get() = (this.context as AbstractTask).project
+
+/**
+ * Returns the corresponding variant of this transform invocation
+ *
+ * @author johnsonlee
+ */
+val TransformInvocation.variant: BaseVariant
+ get() = project.getAndroid().let { android ->
+ when (android) {
+ is AppExtension -> return android.applicationVariants.single { it.name == this.context.variantName }
+ is LibraryExtension -> return android.libraryVariants.single { it.name == this.context.variantName }
+ }
+ TODO("variant not found")
+ }
+
+/**
+ * Returns the compile classpath of this transform invocation
+ *
+ * @author johnsonlee
+ */
+val TransformInvocation.compileClasspath: Collection
+ get() = listOf(inputs, referencedInputs).flatten().map {
+ it.jarInputs + it.directoryInputs
+ }.flatten().map {
+ it.file
+ }
+
+/**
+ * Returns the runtime classpath of this transform invocation
+ *
+ * @author johnsonlee
+ */
+val TransformInvocation.runtimeClasspath: Collection
+ get() = compileClasspath + project.getAndroid().bootClasspath
+
+/**
+ * Returns the classloader of the specified invocation
+ *
+ * @author johnsonlee
+ */
+val TransformInvocation.classLoader: ClassLoader
+ get() = URLClassLoader(runtimeClasspath.map {
+ it.toURI().toURL()
+ }.toTypedArray())
diff --git a/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/util/ComponentHandler.kt b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/util/ComponentHandler.kt
new file mode 100644
index 000000000..9d9caffb3
--- /dev/null
+++ b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/util/ComponentHandler.kt
@@ -0,0 +1,36 @@
+package com.didiglobal.booster.util
+
+import org.xml.sax.Attributes
+import org.xml.sax.helpers.DefaultHandler
+
+const val ATTR_NAME = "android:name"
+
+class ComponentHandler : DefaultHandler() {
+
+ val applications = mutableSetOf()
+ val activities = mutableSetOf()
+ val services = mutableSetOf()
+ val providers = mutableSetOf()
+ val receivers = mutableSetOf()
+
+ override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
+ when (qName) {
+ "application" -> {
+ applications.add(attributes.getValue(ATTR_NAME))
+ }
+ "activity" -> {
+ activities.add(attributes.getValue(ATTR_NAME))
+ }
+ "service" -> {
+ services.add(attributes.getValue(ATTR_NAME))
+ }
+ "provider" -> {
+ providers.add(attributes.getValue(ATTR_NAME))
+ }
+ "receiver" -> {
+ receivers.add(attributes.getValue(ATTR_NAME))
+ }
+ }
+ }
+
+}
diff --git a/booster-gradle-plugin/build.gradle b/booster-gradle-plugin/build.gradle
new file mode 100644
index 000000000..5d29755b5
--- /dev/null
+++ b/booster-gradle-plugin/build.gradle
@@ -0,0 +1,12 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ kapt 'com.google.auto.service:auto-service:1.0-rc4'
+ compile project(':booster-gradle-base')
+ compile project(':booster-task-spi')
+ compile project(':booster-transform-spi')
+ compile project(':booster-transform-util')
+ compile 'com.google.auto.service:auto-service:1.0-rc4'
+ compileOnly 'com.android.tools.build:gradle:3.0.0'
+ testCompileOnly 'com.android.tools.build:gradle:3.0.0'
+}
diff --git a/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterAppTransform.kt b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterAppTransform.kt
new file mode 100644
index 000000000..a6d17d9e3
--- /dev/null
+++ b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterAppTransform.kt
@@ -0,0 +1,15 @@
+package com.didiglobal.booster.gradle
+
+import com.android.build.api.transform.QualifiedContent
+import com.android.build.gradle.internal.pipeline.TransformManager
+
+/**
+ * Represents android transform for application project
+ *
+ * @author johnsonlee
+ */
+class BoosterAppTransform : BoosterTransform() {
+
+ override fun getScopes(): MutableSet = TransformManager.SCOPE_FULL_PROJECT
+
+}
diff --git a/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterLibTransform.kt b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterLibTransform.kt
new file mode 100644
index 000000000..280e24752
--- /dev/null
+++ b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterLibTransform.kt
@@ -0,0 +1,15 @@
+package com.didiglobal.booster.gradle
+
+import com.android.build.api.transform.QualifiedContent
+import com.android.build.gradle.internal.pipeline.TransformManager
+
+/**
+ * Represents android transform for library project
+ *
+ * @author johnsonlee
+ */
+class BoosterLibTransform : BoosterTransform() {
+
+ override fun getScopes(): MutableSet = TransformManager.PROJECT_ONLY
+
+}
diff --git a/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterPlugin.kt b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterPlugin.kt
new file mode 100644
index 000000000..cb54ddbb8
--- /dev/null
+++ b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterPlugin.kt
@@ -0,0 +1,46 @@
+package com.didiglobal.booster.gradle
+
+import com.android.build.gradle.AppExtension
+import com.android.build.gradle.LibraryExtension
+import com.didiglobal.booster.task.spi.VariantProcessor
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import java.util.ServiceLoader
+
+/**
+ * Represents the booster gradle plugin
+ *
+ * @author johnsonlee
+ */
+class BoosterPlugin : Plugin {
+
+ override fun apply(project: Project) {
+ when {
+ project.plugins.hasPlugin("com.android.application") -> project.getAndroid().let { android ->
+ android.registerTransform(BoosterAppTransform())
+ project.afterEvaluate {
+ ServiceLoader.load(VariantProcessor::class.java, javaClass.classLoader).toList().let { processors ->
+ android.applicationVariants.forEach { variant ->
+ processors.forEach { processor ->
+ processor.process(variant)
+ }
+ }
+ }
+ }
+ }
+ project.plugins.hasPlugin("com.android.library") -> project.getAndroid().let { android ->
+ android.registerTransform(BoosterLibTransform())
+ project.afterEvaluate {
+ ServiceLoader.load(VariantProcessor::class.java, javaClass.classLoader).toList().let { processors ->
+ android.libraryVariants.forEach { variant ->
+ processors.forEach { processor ->
+ processor.process(variant)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransform.kt b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransform.kt
new file mode 100644
index 000000000..cd6182798
--- /dev/null
+++ b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransform.kt
@@ -0,0 +1,58 @@
+package com.didiglobal.booster.gradle
+
+import com.android.build.api.transform.QualifiedContent
+import com.android.build.api.transform.Transform
+import com.android.build.api.transform.TransformInvocation
+import com.android.build.gradle.internal.pipeline.TransformManager
+import com.didiglobal.booster.kotlinx.file
+import com.didiglobal.booster.kotlinx.touch
+import java.util.concurrent.TimeUnit
+
+/**
+ * Represents the transform base
+ *
+ * @author johnsonlee
+ */
+abstract class BoosterTransform : Transform() {
+
+ override fun getName() = "booster"
+
+ override fun isIncremental() = true
+
+ override fun getInputTypes(): MutableSet = TransformManager.CONTENT_CLASS
+
+ final override fun transform(invocation: TransformInvocation?) {
+ if (invocation == null) {
+ return
+ }
+
+ BoosterTransformInvocation(invocation).apply {
+ dumpInputs(this)
+
+ if (isIncremental) {
+ onPreTransform(this)
+ doIncrementalTransform()
+ } else {
+ outputProvider.deleteAll()
+ onPreTransform(this)
+ doFullTransform()
+ }
+
+ this.onPostTransform(this)
+ }.executor.apply {
+ shutdown()
+ awaitTermination(1, TimeUnit.MINUTES)
+ }
+ }
+
+ private fun dumpInputs(invocation: BoosterTransformInvocation) {
+ invocation.context.temporaryDir.file("inputs.txt").touch().printWriter().use { printer ->
+ invocation.inputs.flatMap {
+ it.jarInputs
+ }.map {
+ it.file
+ }.forEach(printer::println)
+ }
+ }
+
+}
diff --git a/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransformInvocation.kt b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransformInvocation.kt
new file mode 100644
index 000000000..987168e0b
--- /dev/null
+++ b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransformInvocation.kt
@@ -0,0 +1,158 @@
+package com.didiglobal.booster.gradle
+
+import com.android.build.api.transform.Context
+import com.android.build.api.transform.Format
+import com.android.build.api.transform.SecondaryInput
+import com.android.build.api.transform.Status
+import com.android.build.api.transform.TransformInput
+import com.android.build.api.transform.TransformInvocation
+import com.android.build.api.transform.TransformOutputProvider
+import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.ALL
+import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.AAR
+import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.JAR
+import com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH
+import com.didiglobal.booster.kotlinx.ifNotEmpty
+import com.didiglobal.booster.transform.ArtifactManager
+import com.didiglobal.booster.transform.TransformContext
+import com.didiglobal.booster.transform.TransformListener
+import com.didiglobal.booster.transform.Transformer
+import com.didiglobal.booster.transform.util.transform
+import java.io.File
+import java.util.ServiceLoader
+import java.util.concurrent.ForkJoinPool
+
+/**
+ * Represents a delegate of TransformInvocation
+ *
+ * @author johnsonlee
+ */
+internal class BoosterTransformInvocation(private val delegate: TransformInvocation) : TransformInvocation, TransformContext, TransformListener, ArtifactManager {
+
+ /*
+ * Preload transformers as List to fix NoSuchElementException caused by ServiceLoader in parallel mode
+ */
+ private val transformers = ServiceLoader.load(Transformer::class.java, javaClass.classLoader).toList()
+
+ override val name: String = delegate.context.variantName
+
+ override val projectDir: File = delegate.project.projectDir
+
+ override val buildDir: File = delegate.project.buildDir
+
+ override val temporaryDir: File = delegate.context.temporaryDir
+
+ override val executor = ForkJoinPool(Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true)
+
+ override val compileClasspath = delegate.compileClasspath
+
+ override val runtimeClasspath = delegate.runtimeClasspath
+
+ override val artifacts = this
+
+ override fun hasProperty(name: String): Boolean {
+ return project.hasProperty(name)
+ }
+
+ override fun getProperty(name: String): String? {
+ return project.properties[name]?.toString()
+ }
+
+ override fun getInputs(): MutableCollection = delegate.inputs
+
+ override fun getSecondaryInputs(): MutableCollection = delegate.secondaryInputs
+
+ override fun getReferencedInputs(): MutableCollection = delegate.referencedInputs
+
+ override fun isIncremental() = delegate.isIncremental
+
+ override fun getOutputProvider(): TransformOutputProvider = delegate.outputProvider
+
+ override fun getContext(): Context = delegate.context
+
+ override fun onPreTransform(context: TransformContext) {
+ transformers.forEach {
+ it.onPreTransform(this)
+ }
+ }
+
+ override fun onPostTransform(context: TransformContext) {
+ transformers.forEach {
+ it.onPostTransform(this)
+ }
+ }
+
+ override fun get(type: String): Collection {
+ when (type) {
+ ArtifactManager.AAR -> return variant.scope.getArtifactCollection(RUNTIME_CLASSPATH, ALL, AAR).artifactFiles.files
+ ArtifactManager.ALL_CLASSES -> return variant.scope.allClasses
+ ArtifactManager.APK -> return variant.scope.apk
+ ArtifactManager.JAR -> return variant.scope.getArtifactCollection(RUNTIME_CLASSPATH, ALL, JAR).artifactFiles.files
+ ArtifactManager.JAVAC -> return variant.scope.javac
+ ArtifactManager.MERGED_ASSETS -> return variant.scope.mergedAssets
+ ArtifactManager.MERGED_RES -> return variant.scope.mergedRes
+ ArtifactManager.MERGED_MANIFESTS -> return variant.scope.mergedManifests
+ ArtifactManager.PROCESSED_RES -> return variant.scope.processedRes
+ ArtifactManager.SYMBOL_LIST -> return variant.scope.symbolList
+ ArtifactManager.SYMBOL_LIST_WITH_PACKAGE_NAME -> return variant.scope.symbolListWithPackageName
+ }
+ TODO("Unexpected type: $type")
+ }
+
+ internal fun doFullTransform() {
+ this.inputs.parallelStream().forEach { input ->
+ input.directoryInputs.parallelStream().forEach {
+ it.file.transform(outputProvider.getContentLocation(it.file.name, it.contentTypes, it.scopes, Format.DIRECTORY)) { bytecode ->
+ bytecode.transform(this)
+ }
+ }
+ input.jarInputs.parallelStream().forEach {
+ it.file.transform(outputProvider.getContentLocation(it.name, it.contentTypes, it.scopes, Format.JAR)) { bytecode ->
+ bytecode.transform(this)
+ }
+ }
+ }
+ }
+
+ internal fun doIncrementalTransform() {
+ this.inputs.parallelStream().forEach { input ->
+ input.jarInputs.parallelStream().filter { it.status != Status.NOTCHANGED }.forEach { jarInput ->
+ if (Status.REMOVED == jarInput.status || Status.CHANGED == jarInput.status) {
+ jarInput.file.delete()
+ }
+
+ if (Status.ADDED == jarInput.status || Status.CHANGED == jarInput.status) {
+ val root = outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
+ jarInput.file.transform(root) { bytecode ->
+ bytecode.transform(this)
+ }
+ }
+ }
+
+ input.directoryInputs.parallelStream().forEach { dirInput ->
+ dirInput.changedFiles.ifNotEmpty {
+ it.forEach { file, status ->
+ if (Status.REMOVED == status || Status.CHANGED == status) {
+ file.delete()
+ }
+
+ if (Status.ADDED == status || Status.CHANGED == status) {
+ val root = outputProvider.getContentLocation(dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY)
+ val path = file.absolutePath.substring(dirInput.file.absolutePath.length + File.separator.length)
+ file.transform(File(root, path)) { bytecode ->
+ bytecode.transform(this)
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+ private fun ByteArray.transform(invocation: BoosterTransformInvocation): ByteArray {
+ return transformers.fold(this) { bytes, transformer ->
+ transformer.transform(invocation, bytes)
+ }
+ }
+
+}
diff --git a/booster-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.didiglobal.booster.properties b/booster-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.didiglobal.booster.properties
new file mode 100644
index 000000000..9a04a4e74
--- /dev/null
+++ b/booster-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.didiglobal.booster.properties
@@ -0,0 +1 @@
+implementation-class=com.didiglobal.booster.gradle.BoosterPlugin
diff --git a/booster-kotlinx/build.gradle b/booster-kotlinx/build.gradle
new file mode 100644
index 000000000..345bf9ee5
--- /dev/null
+++ b/booster-kotlinx/build.gradle
@@ -0,0 +1 @@
+apply from: '../gradle/booster.gradle'
diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/FileTree.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/FileTree.kt
new file mode 100644
index 000000000..9186fde11
--- /dev/null
+++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/FileTree.kt
@@ -0,0 +1,42 @@
+package com.didiglobal.booster.kotlinx
+
+import java.io.File
+import java.util.concurrent.ForkJoinPool
+import java.util.concurrent.RecursiveTask
+
+private val forkJoinPool = ForkJoinPool()
+
+/**
+ * Represents a file tree which walk in parallel
+ *
+ * @author johnsonlee
+ */
+internal class FileTree(private val root: File) : Iterable {
+
+ override fun iterator() = walk()
+
+ private fun walk(): Iterator {
+ return forkJoinPool.invoke(FileWalker(root)).iterator()
+ }
+
+ class FileWalker(private val root: File) : RecursiveTask>() {
+
+ override fun compute(): List {
+ val tasks = mutableListOf>>()
+ val result = mutableListOf()
+
+ root.listFiles()?.forEach { file ->
+ result.add(file)
+ if (file.isDirectory) {
+ FileWalker(file).also { task ->
+ tasks.add(task)
+ }.fork()
+ }
+ }
+
+ return result + tasks.flatMap { it.join() }
+ }
+
+ }
+
+}
diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/Wildcard.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/Wildcard.kt
new file mode 100644
index 000000000..768223bbd
--- /dev/null
+++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/Wildcard.kt
@@ -0,0 +1,119 @@
+package com.didiglobal.booster.kotlinx
+
+import java.util.ArrayList
+import java.util.Stack
+
+class Wildcard(private val pattern: String, private val ignoreCase: Boolean = false) {
+
+ fun matches(text: String): Boolean {
+ val wcs = splitOnTokens(pattern)
+ var anyChars = false
+ var textIdx = 0
+ var wcsIdx = 0
+ val backtrack = Stack()
+
+ do {
+ if (backtrack.size > 0) {
+ val array = backtrack.pop()
+ wcsIdx = array[0]
+ textIdx = array[1]
+ anyChars = true
+ }
+
+ while (wcsIdx < wcs.size) {
+
+ if (wcs[wcsIdx] == "?") {
+ textIdx++
+ if (textIdx > text.length) {
+ break
+ }
+ anyChars = false
+
+ } else if (wcs[wcsIdx] == "*") {
+ anyChars = true
+ if (wcsIdx == wcs.size - 1) {
+ textIdx = text.length
+ }
+
+ } else {
+ if (anyChars) {
+ textIdx = checkIndexOf(text, textIdx, wcs[wcsIdx])
+ if (textIdx == -1) {
+ break
+ }
+ val repeat = checkIndexOf(text, textIdx + 1, wcs[wcsIdx])
+ if (repeat >= 0) {
+ backtrack.push(intArrayOf(wcsIdx, repeat))
+ }
+ } else {
+ if (!checkRegionMatches(text, textIdx, wcs[wcsIdx])) {
+ break
+ }
+ }
+
+ textIdx += wcs[wcsIdx].length
+ anyChars = false
+ }
+
+ wcsIdx++
+ }
+
+ if (wcsIdx == wcs.size && textIdx == text.length) {
+ return true
+ }
+
+ } while (backtrack.size > 0)
+
+ return false
+ }
+
+ private fun splitOnTokens(text: String): Array {
+ // used by wildcardMatch
+ // package level so a unit test may run on this
+
+ if (text.indexOf('?') == -1 && text.indexOf('*') == -1) {
+ return arrayOf(text)
+ }
+
+ val array = text.toCharArray()
+ val list = ArrayList()
+ val buffer = StringBuilder()
+ for (i in array.indices) {
+ if (array[i] == '?' || array[i] == '*') {
+ if (buffer.isNotEmpty()) {
+ list.add(buffer.toString())
+ buffer.setLength(0)
+ }
+ if (array[i] == '?') {
+ list.add("?")
+ } else if (list.isEmpty() || i > 0 && list[list.size - 1] != "*") {
+ list.add("*")
+ }
+ } else {
+ buffer.append(array[i])
+ }
+ }
+ if (buffer.isNotEmpty()) {
+ list.add(buffer.toString())
+ }
+
+ return list.toTypedArray()
+ }
+
+ private fun checkIndexOf(str: String, strStartIndex: Int, search: String): Int {
+ val endIndex = str.length - search.length
+ if (endIndex >= strStartIndex) {
+ for (i in strStartIndex..endIndex) {
+ if (checkRegionMatches(str, i, search)) {
+ return i
+ }
+ }
+ }
+ return -1
+ }
+
+ private fun checkRegionMatches(str: String, strStartIndex: Int, search: String): Boolean {
+ return str.regionMatches(strStartIndex, search, 0, search.length, ignoreCase = ignoreCase)
+ }
+
+}
diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/ansi.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/ansi.kt
new file mode 100644
index 000000000..f06ae57e8
--- /dev/null
+++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/ansi.kt
@@ -0,0 +1,44 @@
+package com.didiglobal.booster.kotlinx
+
+const val ESC = '\u001B'
+
+const val RESET = "$ESC[0m"
+
+const val BOLD = "$ESC[1m"
+const val UNDERLINE = "$ESC[4m"
+const val BLINK = "$ESC[5m"
+const val REVERSED = "$ESC[7m"
+
+const val BLACK = "$ESC[30m"
+const val RED = "$ESC[31m"
+const val GREEN = "$ESC[32m"
+const val YELLOW = "$ESC[33m"
+const val BLUE = "$ESC[34m"
+const val MAGENTA = "$ESC[35m"
+const val CYAN = "$ESC[36m"
+const val WHITE = "$ESC[37m"
+const val BRIGHT_BLACK = "$ESC[30,1m"
+const val BRIGHT_RED = "$ESC[31,1m"
+const val BRIGHT_GREEN = "$ESC[32,1m"
+const val BRIGHT_YELLOW = "$ESC[33,1m"
+const val BRIGHT_BLUE = "$ESC[34,1m"
+const val BRIGHT_MAGENTA = "$ESC[35,1m"
+const val BRIGHT_CYAN = "$ESC[36,1m"
+const val BRIGHT_WHITE = "$ESC[37,1m"
+
+const val BACKGROUND_BLACK = "$ESC[40m"
+const val BACKGROUND_RED = "$ESC[41m"
+const val BACKGROUND_GREEN = "$ESC[42m"
+const val BACKGROUND_YELLOW = "$ESC[43m"
+const val BACKGROUND_BLUE = "$ESC[44m"
+const val BACKGROUND_MAGENTA = "$ESC[45m"
+const val BACKGROUND_CYAN = "$ESC[46m"
+const val BACKGROUND_WHITE = "$ESC[47m"
+const val BACKGROUND_BRIGHT_BLACK = "$ESC[40,1m"
+const val BACKGROUND_BRIGHT_RED = "$ESC[41,1m"
+const val BACKGROUND_BRIGHT_GREEN = "$ESC[42,1m"
+const val BACKGROUND_BRIGHT_YELLOW = "$ESC[43,1m"
+const val BACKGROUND_BRIGHT_BLUE = "$ESC[44,1m"
+const val BACKGROUND_BRIGHT_MAGENTA = "$ESC[45,1m"
+const val BACKGROUND_BRIGHT_CYAN = "$ESC[46,1m"
+const val BACKGROUND_BRIGHT_WHITE = "$ESC[47,1m"
diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/collections.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/collections.kt
new file mode 100644
index 000000000..9b763b78c
--- /dev/null
+++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/collections.kt
@@ -0,0 +1,30 @@
+package com.didiglobal.booster.kotlinx
+
+import java.util.stream.Stream
+import java.util.stream.StreamSupport
+
+fun Iterable.isEmpty() = iterator().hasNext()
+
+inline fun Map.ifNotEmpty(action: (Map) -> Unit): Map {
+ if (isNotEmpty()) {
+ action(this)
+ }
+ return this
+}
+
+inline fun Collection.ifNotEmpty(action: (Collection) -> Unit): Collection {
+ if (isNotEmpty()) {
+ action(this)
+ }
+ return this
+}
+
+fun Iterator.asIterable(): Iterable = Iterable { this }
+
+fun Iterator.stream(): Stream = asIterable().stream()
+
+fun Iterator.parallelStream(): Stream = asIterable().parallelStream()
+
+fun Iterable.stream(): Stream = StreamSupport.stream(spliterator(), false)
+
+fun Iterable.parallelStream(): Stream = StreamSupport.stream(spliterator(), true)
diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/io.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/io.kt
new file mode 100644
index 000000000..be36b7a59
--- /dev/null
+++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/io.kt
@@ -0,0 +1,188 @@
+package com.didiglobal.booster.kotlinx
+
+import java.io.BufferedReader
+import java.io.File
+import java.io.InputStream
+import java.io.InputStreamReader
+import java.io.OutputStream
+import java.io.Reader
+import java.util.jar.JarEntry
+import java.util.jar.JarFile
+import java.util.jar.JarOutputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+import java.util.zip.ZipOutputStream
+
+fun File.file(vararg path: String) = File(this, path.joinToString(File.separator))
+
+/**
+ * Create a new file if not exists
+ *
+ * @author johnsonlee
+ */
+fun File.touch(): File {
+ if (!this.exists()) {
+ this.parentFile?.mkdirs()
+ this.createNewFile()
+ }
+ return this
+}
+
+fun File.ifExists(block: (File) -> Unit) {
+ if (this.exists()) {
+ block(this)
+ }
+}
+
+/**
+ * Archive this file as a JAR
+ *
+ * @author johnsonlee
+ */
+fun File.jar(): JarFile {
+ return jar(File.createTempFile("tmp-", ".jar"))
+}
+
+/**
+ * Archive this file as the specified JAR
+ *
+ * @author johnsonlee
+ */
+fun File.jar(dest: File): JarFile {
+ JarOutputStream(dest.outputStream()).use { output ->
+ if (this.isFile) {
+ output.putNextEntry(JarEntry(this.name))
+ this.inputStream().use { input ->
+ input.copyTo(output)
+ }
+ } else {
+ this.parallelWalk().filter { it != this && it.isFile }.forEach { file ->
+ val path = file.absolutePath.substring(this.absolutePath.length + File.separator.length)
+ output.putNextEntry(JarEntry(path.separatorsToUnix()))
+ file.inputStream().use { input ->
+ input.copyTo(output)
+ }
+ }
+ }
+ }
+ return JarFile(dest)
+}
+
+fun File.unjar(out: File): List {
+ val files = mutableListOf()
+
+ JarFile(this).use { jar ->
+ jar.entries().asSequence().filter { !it.isDirectory }.forEach { entry ->
+ val path = entry.name.separatorsToSystem()
+ jar.getInputStream(entry).use { input ->
+ val file = File(out, path)
+ input.redirect(file)
+ files.add(file)
+ }
+ }
+ }
+
+ return files
+}
+
+fun File.zip(): ZipFile {
+ return this.zip(File.createTempFile("tmp-", ".zip"))
+}
+
+fun File.zip(dest: File): ZipFile {
+ ZipOutputStream(dest.outputStream()).use { output ->
+ if (this.isFile) {
+ output.putNextEntry(ZipEntry(this.name))
+ this.inputStream().use { input ->
+ input.copyTo(output)
+ }
+ } else {
+ this.parallelWalk().filter { it != this && it.isFile }.forEach { file ->
+ val path = file.absolutePath.substring(this.absolutePath.length + File.separator.length)
+ output.putNextEntry(ZipEntry(path.separatorsToUnix()))
+ file.inputStream().use { input ->
+ input.copyTo(output)
+ }
+ }
+ }
+ }
+ return ZipFile(dest)
+}
+
+fun File.unzip(out: File): List {
+ var files = mutableListOf()
+
+ ZipFile(this).use { zip ->
+ zip.entries().asSequence().filter { !it.isDirectory }.forEach { entry ->
+ val path = entry.name.separatorsToSystem()
+ zip.getInputStream(entry).use { input ->
+ val file = File(out, path)
+ input.redirect(file)
+ files.add(file)
+ }
+ }
+ }
+
+ return files
+}
+
+/**
+ * Walk file in parallel
+ */
+fun File.parallelWalk(): Iterable {
+ return FileTree(this)
+}
+
+/**
+ * Return the first line of file
+ */
+fun File.head(): String? {
+ return inputStream().use {
+ it.head()
+ }
+}
+
+/**
+ * Returns the first line of input stream
+ */
+fun InputStream.head(): String? {
+ return BufferedReader(InputStreamReader(this)).head()
+}
+
+/**
+ * Returns the first line of reader
+ */
+fun Reader.head(): String? {
+ return BufferedReader(this).readLine()
+}
+
+/**
+ * Redirect this input stream to the specified file
+ *
+ * @author johnsonlee
+ */
+fun InputStream.redirect(file: File): Long {
+ file.touch().outputStream().use {
+ return this.copyTo(it)
+ }
+}
+
+/**
+ * Redirect this byte data to the specified file
+ *
+ * @author johnsonlee
+ */
+fun ByteArray.redirect(file: File): Long {
+ this.inputStream().use {
+ return it.redirect(file)
+ }
+}
+
+/**
+ * Redirect this byte data to the specified output stream
+ *
+ * @author johnsonlee
+ */
+fun ByteArray.redirect(output: OutputStream): Long {
+ return this.inputStream().copyTo(output)
+}
diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/primitive.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/primitive.kt
new file mode 100644
index 000000000..054d03701
--- /dev/null
+++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/primitive.kt
@@ -0,0 +1,29 @@
+package com.didiglobal.booster.kotlinx
+
+data class byte(val value: Byte = 0) {
+ constructor(value: Byte? = null) : this(value ?: 0)
+}
+
+data class char(val value: Char = '\u0000') {
+ constructor(value: Char? = null) : this(value ?: '\u0000')
+}
+
+data class short(val value: Short = 0) {
+ constructor(value: Short? = null) : this(value ?: 0)
+}
+
+data class int(val value: Int = 0) {
+ constructor(value: Int? = null) : this(value ?: 0)
+}
+
+data class float(val value: Float = 0f) {
+ constructor(value: Float? = null) : this(value ?: 0f)
+}
+
+data class double(val value: Double = 0.0) {
+ constructor(value: Double? = null) : this(value ?: 0.0)
+}
+
+data class boolean(val value: Boolean = false) {
+ constructor(value: Boolean? = null) : this(value ?: false)
+}
diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/text.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/text.kt
new file mode 100644
index 000000000..327c13419
--- /dev/null
+++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/text.kt
@@ -0,0 +1,23 @@
+package com.didiglobal.booster.kotlinx
+
+import java.io.File
+import java.math.BigInteger
+import java.security.MessageDigest
+
+fun String.separatorsToUnix(): String {
+ return if ('/' == File.separatorChar) this else this.replace(File.separatorChar, '/')
+}
+
+fun String.separatorsToSystem(): String {
+ return if ('/' != File.separatorChar && this.contains('/')) this.replace('/', File.separatorChar) else this
+}
+
+fun String.md5(): String {
+ return BigInteger(1, MessageDigest.getInstance("MD5").digest(toByteArray())).toString(16).padStart(32, '0')
+}
+
+fun String.matches(wildcard: Wildcard): Boolean {
+ return wildcard.matches(this)
+}
+
+typealias Formatter = (value: T) -> CharSequence
diff --git a/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/FileTest.kt b/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/FileTest.kt
new file mode 100644
index 000000000..5c17e7396
--- /dev/null
+++ b/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/FileTest.kt
@@ -0,0 +1,32 @@
+package com.didiglobal.booster.kotlinx
+
+import java.io.File
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+/**
+ * Unit test for File extension
+ *
+ * @author johnsonlee
+ */
+class FileTest {
+
+ @Test
+ fun `archive current dir as a jar`() {
+ val pwd = File(System.getProperty("user.dir"))
+ val count = pwd.walk().filter { it != pwd && it.isFile }.count()
+ assertEquals(count, pwd.jar().entries().asSequence().count())
+ }
+
+ @Test
+ fun `read the 1st line of file`() {
+ val head = "Hello, booster!"
+ val file = File.createTempFile("FileTest-", ".txt")
+
+ file.printWriter().use {
+ it.println(head)
+ }
+ assertEquals(head, file.head())
+ }
+
+}
diff --git a/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/WildcardTest.kt b/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/WildcardTest.kt
new file mode 100644
index 000000000..42d34721f
--- /dev/null
+++ b/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/WildcardTest.kt
@@ -0,0 +1,15 @@
+package com.didiglobal.booster.kotlinx
+
+import kotlin.test.Test
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class WildcardTest {
+
+ @Test
+ fun `wildcard matching`() {
+ assertFalse("java/io/File.exists()B".matches(Wildcard("java/x/**")))
+ assertTrue("java/io/File.exists()B".matches(Wildcard("java/io/*")))
+ }
+
+}
diff --git a/booster-task-all/build.gradle b/booster-task-all/build.gradle
new file mode 100644
index 000000000..2b52ad3d2
--- /dev/null
+++ b/booster-task-all/build.gradle
@@ -0,0 +1,5 @@
+dependencies {
+ compile project(':booster-task-artifact')
+ compile project(':booster-task-dependency')
+ compile project(':booster-task-permission')
+}
diff --git a/booster-task-artifact/build.gradle b/booster-task-artifact/build.gradle
new file mode 100644
index 000000000..c01d0d476
--- /dev/null
+++ b/booster-task-artifact/build.gradle
@@ -0,0 +1,9 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ kapt "com.google.auto.service:auto-service:1.0-rc4"
+ implementation project(':booster-gradle-base')
+ implementation project(':booster-task-spi')
+ compileOnly 'com.android.tools.build:gradle:3.0.0'
+ testCompileOnly 'com.android.tools.build:gradle:3.0.0'
+}
diff --git a/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactVariantProcessor.kt b/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactVariantProcessor.kt
new file mode 100644
index 000000000..afd10400f
--- /dev/null
+++ b/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactVariantProcessor.kt
@@ -0,0 +1,22 @@
+package com.didiglobal.booster.task.artifact
+
+import com.android.build.gradle.api.BaseVariant
+import com.didiglobal.booster.gradle.scope
+import com.didiglobal.booster.task.spi.VariantProcessor
+import com.google.auto.service.AutoService
+
+@AutoService(VariantProcessor::class)
+class ArtifactVariantProcessor : VariantProcessor {
+
+ override fun process(variant: BaseVariant) {
+ val tasks = variant.scope.globalScope.project.tasks
+ val artifacts = tasks.findByName("showArtifacts") ?: tasks.create("showArtifacts")
+ tasks.create("show${variant.name.capitalize()}Artifacts", ArtifactsResolver::class.java) {
+ it.variant = variant
+ it.outputs.upToDateWhen { false }
+ }.also {
+ artifacts.dependsOn(it)
+ }
+ }
+
+}
diff --git a/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactsResolver.kt b/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactsResolver.kt
new file mode 100644
index 000000000..f7cdf13a4
--- /dev/null
+++ b/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactsResolver.kt
@@ -0,0 +1,23 @@
+package com.didiglobal.booster.task.artifact
+
+import com.android.build.gradle.api.BaseVariant
+import com.didiglobal.booster.gradle.allArtifacts
+import com.didiglobal.booster.gradle.scope
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+
+internal open class ArtifactsResolver : DefaultTask() {
+
+ lateinit var variant: BaseVariant
+
+ @TaskAction
+ fun run() {
+ val artifacts = this.variant.scope.allArtifacts
+ val maxTypeWidth: Int = artifacts.keys.map { it.length }.max()!!
+
+ artifacts.forEach { type, files ->
+ println("${".".repeat(maxTypeWidth - type.length + 1)}$type : $files")
+ }
+ }
+
+}
diff --git a/booster-task-dependency/build.gradle b/booster-task-dependency/build.gradle
new file mode 100644
index 000000000..c01d0d476
--- /dev/null
+++ b/booster-task-dependency/build.gradle
@@ -0,0 +1,9 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ kapt "com.google.auto.service:auto-service:1.0-rc4"
+ implementation project(':booster-gradle-base')
+ implementation project(':booster-task-spi')
+ compileOnly 'com.android.tools.build:gradle:3.0.0'
+ testCompileOnly 'com.android.tools.build:gradle:3.0.0'
+}
diff --git a/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/CheckSnapshot.kt b/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/CheckSnapshot.kt
new file mode 100644
index 000000000..88b842813
--- /dev/null
+++ b/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/CheckSnapshot.kt
@@ -0,0 +1,29 @@
+package com.didiglobal.booster.task.dependency
+
+import com.android.build.gradle.api.BaseVariant
+import com.didiglobal.booster.gradle.dependencies
+import com.didiglobal.booster.kotlinx.RESET
+import com.didiglobal.booster.kotlinx.YELLOW
+import com.didiglobal.booster.kotlinx.ifNotEmpty
+import org.gradle.api.DefaultTask
+import org.gradle.api.internal.artifacts.repositories.resolver.MavenUniqueSnapshotComponentIdentifier
+import org.gradle.api.tasks.TaskAction
+
+internal open class CheckSnapshot : DefaultTask() {
+
+ lateinit var variant: BaseVariant
+
+ @TaskAction
+ fun run() {
+ if (!variant.buildType.isDebuggable) {
+ variant.dependencies.filter {
+ it.id.componentIdentifier is MavenUniqueSnapshotComponentIdentifier
+ }.map {
+ it.id.componentIdentifier as MavenUniqueSnapshotComponentIdentifier
+ }.ifNotEmpty { snapshots ->
+ println("$YELLOW ⚠️ ${snapshots.size} SNAPSHOT artifacts found in ${variant.name} variant:$RESET\n${snapshots.joinToString("\n") { snapshot -> "$YELLOW→ ${snapshot.displayName}$RESET" }}")
+ }
+ }
+ }
+
+}
diff --git a/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/DependencyVariantProcessor.kt b/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/DependencyVariantProcessor.kt
new file mode 100644
index 000000000..a40bf2383
--- /dev/null
+++ b/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/DependencyVariantProcessor.kt
@@ -0,0 +1,23 @@
+package com.didiglobal.booster.task.dependency
+
+import com.android.build.gradle.api.BaseVariant
+import com.didiglobal.booster.gradle.scope
+import com.didiglobal.booster.task.spi.VariantProcessor
+import com.google.auto.service.AutoService
+
+@AutoService(VariantProcessor::class)
+class DependencyVariantProcessor : VariantProcessor {
+
+ override fun process(variant: BaseVariant) {
+ val tasks = variant.scope.globalScope.project.tasks
+ val checkSnapshot = tasks.findByName("checkSnapshot") ?: tasks.create("checkSnapshot")
+ tasks.create("check${variant.name.capitalize()}Snapshot", CheckSnapshot::class.java) {
+ it.variant = variant
+ it.outputs.upToDateWhen { false }
+ }.also {
+ variant.javaCompiler.dependsOn(it)
+ checkSnapshot.dependsOn(it)
+ }
+ }
+
+}
diff --git a/booster-task-permission/build.gradle b/booster-task-permission/build.gradle
new file mode 100644
index 000000000..c01d0d476
--- /dev/null
+++ b/booster-task-permission/build.gradle
@@ -0,0 +1,9 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ kapt "com.google.auto.service:auto-service:1.0-rc4"
+ implementation project(':booster-gradle-base')
+ implementation project(':booster-task-spi')
+ compileOnly 'com.android.tools.build:gradle:3.0.0'
+ testCompileOnly 'com.android.tools.build:gradle:3.0.0'
+}
diff --git a/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionExtractor.kt b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionExtractor.kt
new file mode 100644
index 000000000..b174e61ba
--- /dev/null
+++ b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionExtractor.kt
@@ -0,0 +1,88 @@
+package com.didiglobal.booster.task.permission
+
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.ALL
+import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.AAR
+import com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH
+import com.didiglobal.booster.gradle.scope
+import com.didiglobal.booster.kotlinx.RESET
+import com.didiglobal.booster.kotlinx.YELLOW
+import com.didiglobal.booster.kotlinx.ifNotEmpty
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.util.zip.ZipFile
+import javax.xml.parsers.SAXParserFactory
+
+internal open class PermissionExtractor : DefaultTask() {
+
+ private val factory = SAXParserFactory.newInstance()
+
+ lateinit var variant: BaseVariant
+
+ init {
+ factory.isXIncludeAware = false
+ factory.isNamespaceAware = true
+ factory.setFeature("http://xml.org/sax/features/namespace-prefixes", true)
+ factory.setFeature("http://xml.org/sax/features/xmlns-uris", true)
+ factory.isValidating = false
+ }
+
+ @TaskAction
+ fun run() {
+ variant.scope.getArtifactFileCollection(RUNTIME_CLASSPATH, ALL, AAR).files.forEach { aar ->
+ ZipFile(aar).use { zip ->
+ zip.getEntry("AndroidManifest.xml")?.let { entry ->
+ zip.getInputStream(entry).use { source ->
+ PermissionUsageHandler().also { handler ->
+ factory.newSAXParser().parse(source, handler)
+ }.permissions.sorted().ifNotEmpty { permissions ->
+ println("${aar.componentId} [$YELLOW${variant.name}$RESET]")
+ permissions.forEach { permission ->
+ println(" - $permission")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
+
+private val HEX = "[a-zA-Z0-9]+".toRegex()
+
+private val EXTRA_ANDROID_M2REPOSITORY = "${File.separator}extras${File.separator}android${File.separator}m2repository${File.separator}"
+private val LEN_EXTRA_ANDROID_M2REPOSITORY = EXTRA_ANDROID_M2REPOSITORY.length
+
+private val EXTRA_GOOGLE_M2REPOSITORY = "${File.separator}extras${File.separator}google${File.separator}m2repository${File.separator}"
+private val LEN_EXTRA_GOOGLE_M2REPOSITORY = EXTRA_GOOGLE_M2REPOSITORY.length
+
+internal val File.componentId: String
+ get() {
+ val parent = this.parentFile
+ if (parent.name.matches(HEX)) {
+ val version = parent.parentFile
+ val artifact = version.parentFile
+ val group = artifact.parentFile
+ return "${group.name}:${artifact.name}:${version.name}"
+ }
+
+ this.absolutePath.let {
+ val idxAndroidM2 = it.indexOf(EXTRA_ANDROID_M2REPOSITORY)
+ if (idxAndroidM2 > -1) {
+ val artifact = parent.parentFile
+ val group = artifact.parentFile.absolutePath.substring(idxAndroidM2 + LEN_EXTRA_ANDROID_M2REPOSITORY).replace(File.separatorChar, '.')
+ return "$group:${artifact.name}:${parent.name}"
+ }
+
+ val idxGoogleM2 = it.indexOf(EXTRA_GOOGLE_M2REPOSITORY)
+ if (idxGoogleM2 > -1) {
+ val artifact = parent.parentFile
+ val group = artifact.parentFile.absolutePath.substring(idxGoogleM2 + LEN_EXTRA_GOOGLE_M2REPOSITORY).replace(File.separatorChar, '.')
+ return "$group:${artifact.name}:${parent.name}"
+ }
+
+ TODO("Unrecognizable AAR: $absolutePath")
+ }
+ }
diff --git a/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionUsageHandler.kt b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionUsageHandler.kt
new file mode 100644
index 000000000..80b9a96b9
--- /dev/null
+++ b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionUsageHandler.kt
@@ -0,0 +1,18 @@
+package com.didiglobal.booster.task.permission
+
+import org.xml.sax.Attributes
+import org.xml.sax.helpers.DefaultHandler
+
+internal class PermissionUsageHandler : DefaultHandler() {
+
+ internal val permissions = mutableSetOf()
+
+ override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
+ when (localName) {
+ "uses-permission" -> permissions.add(attributes.getValue("android:name"))
+ "uses-permission-sdk-23" -> permissions.add(attributes.getValue("android:name"))
+ "uses-permission-sdk-m" -> permissions.add(attributes.getValue("android:name"))
+ }
+ }
+
+}
diff --git a/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionVariantProcessor.kt b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionVariantProcessor.kt
new file mode 100644
index 000000000..801659d6b
--- /dev/null
+++ b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionVariantProcessor.kt
@@ -0,0 +1,22 @@
+package com.didiglobal.booster.task.permission
+
+import com.android.build.gradle.api.BaseVariant
+import com.didiglobal.booster.gradle.scope
+import com.didiglobal.booster.task.spi.VariantProcessor
+import com.google.auto.service.AutoService
+
+@AutoService(VariantProcessor::class)
+class PermissionVariantProcessor : VariantProcessor {
+
+ override fun process(variant: BaseVariant) {
+ val tasks = variant.scope.globalScope.project.tasks
+ val showPermission = tasks.findByName("showPermissions") ?: tasks.create("showPermissions")
+ tasks.create("show${variant.name.capitalize()}Permissions", PermissionExtractor::class.java) {
+ it.variant = variant
+ it.outputs.upToDateWhen { false }
+ }.also {
+ showPermission.dependsOn(it)
+ }
+ }
+
+}
diff --git a/booster-task-spi/build.gradle b/booster-task-spi/build.gradle
new file mode 100644
index 000000000..84969aa0c
--- /dev/null
+++ b/booster-task-spi/build.gradle
@@ -0,0 +1,6 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ compileOnly 'com.android.tools.build:gradle:3.0.0'
+ testCompileOnly 'com.android.tools.build:gradle:3.0.0'
+}
diff --git a/booster-task-spi/src/main/kotlin/com/didiglobal/booster/task/spi/VariantProcessor.kt b/booster-task-spi/src/main/kotlin/com/didiglobal/booster/task/spi/VariantProcessor.kt
new file mode 100644
index 000000000..c120853e4
--- /dev/null
+++ b/booster-task-spi/src/main/kotlin/com/didiglobal/booster/task/spi/VariantProcessor.kt
@@ -0,0 +1,9 @@
+package com.didiglobal.booster.task.spi
+
+import com.android.build.gradle.api.BaseVariant
+
+interface VariantProcessor {
+
+ fun process(variant: BaseVariant)
+
+}
diff --git a/booster-transform-all/build.gradle b/booster-transform-all/build.gradle
new file mode 100644
index 000000000..ea65c0f44
--- /dev/null
+++ b/booster-transform-all/build.gradle
@@ -0,0 +1,6 @@
+dependencies {
+ compile project(':booster-transform-bugfix-toast')
+ compile project(':booster-transform-lint')
+ compile project(':booster-transform-shrink')
+ compile project(':booster-transform-usage')
+}
diff --git a/booster-transform-asm/build.gradle b/booster-transform-asm/build.gradle
new file mode 100644
index 000000000..f69f06806
--- /dev/null
+++ b/booster-transform-asm/build.gradle
@@ -0,0 +1,14 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ kapt "com.google.auto.service:auto-service:1.0-rc4"
+ compile gradleApi()
+ compile project(':booster-kotlinx')
+ compile project(':booster-transform-spi')
+ compile 'org.ow2.asm:asm:7.1'
+ compile 'org.ow2.asm:asm-analysis:7.1'
+ compile 'org.ow2.asm:asm-commons:7.1'
+ compile 'org.ow2.asm:asm-tree:7.1'
+ compile 'org.ow2.asm:asm-util:7.1'
+ compile 'com.google.auto.service:auto-service:1.0-rc4'
+}
diff --git a/booster-transform-asm/src/main/java/com/didiglobal/booster/transform/asm/ClassTransformer.java b/booster-transform-asm/src/main/java/com/didiglobal/booster/transform/asm/ClassTransformer.java
new file mode 100644
index 000000000..904506aae
--- /dev/null
+++ b/booster-transform-asm/src/main/java/com/didiglobal/booster/transform/asm/ClassTransformer.java
@@ -0,0 +1,29 @@
+package com.didiglobal.booster.transform.asm;
+
+import com.didiglobal.booster.transform.TransformContext;
+import com.didiglobal.booster.transform.TransformListener;
+import org.jetbrains.annotations.NotNull;
+import org.objectweb.asm.tree.ClassNode;
+
+/**
+ * Represents class transformer
+ *
+ * @author johnsonlee
+ */
+public interface ClassTransformer extends TransformListener {
+
+ /**
+ * Transform the specified class node
+ *
+ * @param context
+ * The transform context
+ * @param klass
+ * The class node to be transformed
+ * @return The transformed class node
+ */
+ @NotNull
+ default ClassNode transform(@NotNull final TransformContext context, @NotNull final ClassNode klass) {
+ return klass;
+ }
+
+}
diff --git a/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/AsmTransformer.kt b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/AsmTransformer.kt
new file mode 100644
index 000000000..c7edf0e38
--- /dev/null
+++ b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/AsmTransformer.kt
@@ -0,0 +1,45 @@
+package com.didiglobal.booster.transform.asm
+
+import com.didiglobal.booster.transform.TransformContext
+import com.didiglobal.booster.transform.Transformer
+import com.google.auto.service.AutoService
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.tree.ClassNode
+import java.util.ServiceLoader
+
+/**
+ * Represents bytecode transformer using ASM
+ *
+ * @author johnsonlee
+ */
+@AutoService(Transformer::class)
+class AsmTransformer : Transformer {
+
+ /*
+ * Preload transformers as List to fix NoSuchElementException caused by ServiceLoader in parallel mode
+ */
+ private val transformers = ServiceLoader.load(ClassTransformer::class.java, javaClass.classLoader).toList()
+
+ override fun transform(context: TransformContext, bytecode: ByteArray): ByteArray {
+ return ClassWriter(ClassWriter.COMPUTE_MAXS).also { writer ->
+ transformers.fold(ClassNode().also { klass ->
+ ClassReader(bytecode).accept(klass, 0)
+ }) { klass, transformer ->
+ transformer.transform(context, klass)
+ }.accept(writer)
+ }.toByteArray()
+ }
+
+ override fun onPreTransform(context: TransformContext) {
+ transformers.forEach {
+ it.onPreTransform(context)
+ }
+ }
+
+ override fun onPostTransform(context: TransformContext) {
+ transformers.forEach {
+ it.onPostTransform(context)
+ }
+ }
+}
diff --git a/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/ClassNode.kt b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/ClassNode.kt
new file mode 100644
index 000000000..042744a64
--- /dev/null
+++ b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/ClassNode.kt
@@ -0,0 +1,9 @@
+package com.didiglobal.booster.transform.asm
+
+import org.objectweb.asm.tree.ClassNode
+
+/**
+ * The simple name of class
+ */
+val ClassNode.simpleName: String
+ get() = this.name.substring(this.name.lastIndexOf('/') + 1)
diff --git a/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/InsnList.kt b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/InsnList.kt
new file mode 100644
index 000000000..43b984281
--- /dev/null
+++ b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/InsnList.kt
@@ -0,0 +1,27 @@
+package com.didiglobal.booster.transform.asm
+
+import com.didiglobal.booster.kotlinx.asIterable
+import org.objectweb.asm.tree.AbstractInsnNode
+import org.objectweb.asm.tree.InsnList
+
+/**
+ * Find the first instruction node with the specified opcode
+ *
+ * @param opcode The opcode to search
+ */
+fun InsnList.find(opcode: Int): AbstractInsnNode? {
+ return this.iterator().asIterable().find {
+ it.opcode == opcode
+ }
+}
+
+/**
+ * Find all of instruction nodes with the specified opcode
+ *
+ * @param opcodes The opcode to search
+ */
+fun InsnList.findAll(vararg opcodes: Int): Collection {
+ return this.iterator().asIterable().filter {
+ it.opcode in opcodes
+ }.toList()
+}
diff --git a/booster-transform-bugfix-toast/build.gradle b/booster-transform-bugfix-toast/build.gradle
new file mode 100644
index 000000000..b02db7d31
--- /dev/null
+++ b/booster-transform-bugfix-toast/build.gradle
@@ -0,0 +1,10 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ kapt "com.google.auto.service:auto-service:1.0-rc4"
+ implementation project(':booster-gradle-base')
+ implementation project(':booster-task-spi')
+ implementation project(':booster-transform-asm')
+ compileOnly 'com.android.tools.build:gradle:3.0.0'
+ testCompileOnly 'com.android.tools.build:gradle:3.0.0'
+}
diff --git a/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixTransformer.kt b/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixTransformer.kt
new file mode 100644
index 000000000..29cd963ed
--- /dev/null
+++ b/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixTransformer.kt
@@ -0,0 +1,36 @@
+package com.didiglobal.booster.transform.bugfix.toast
+
+import com.didiglobal.booster.kotlinx.asIterable
+import com.didiglobal.booster.transform.TransformContext
+import com.didiglobal.booster.transform.asm.ClassTransformer
+import com.google.auto.service.AutoService
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.MethodInsnNode
+
+/**
+ * Represents a class node transformer used to fix [bug: 30150688](https://android.googlesource.com/platform/frameworks/base/+/dc24f93)
+ *
+ * @author johnsonlee
+ */
+@AutoService(ClassTransformer::class)
+class ToastBugfixTransformer : ClassTransformer {
+
+ override fun transform(context: TransformContext, klass: ClassNode): ClassNode {
+ klass.methods.forEach { method ->
+ method.instructions?.iterator()?.asIterable()?.filterIsInstance(MethodInsnNode::class.java)?.filter {
+ it.owner == TOAST && it.name == "show" && it.desc == "()V"
+ }?.forEach {
+ it.owner = `TOAST'`
+ it.desc = "(L$TOAST;)V"
+ it.opcode = Opcodes.INVOKESTATIC
+ }
+ }
+ return klass
+ }
+
+}
+
+private const val TOAST = "android/widget/Toast"
+
+private const val `TOAST'` = "com/didiglobal/booster/$TOAST"
diff --git a/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixVariantProcessor.kt b/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixVariantProcessor.kt
new file mode 100644
index 000000000..f11502095
--- /dev/null
+++ b/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixVariantProcessor.kt
@@ -0,0 +1,18 @@
+package com.didiglobal.booster.transform.bugfix.toast
+
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.api.LibraryVariant
+import com.didiglobal.booster.gradle.scope
+import com.didiglobal.booster.task.spi.VariantProcessor
+import com.google.auto.service.AutoService
+
+@AutoService(VariantProcessor::class)
+class ToastBugfixVariantProcessor : VariantProcessor {
+
+ override fun process(variant: BaseVariant) {
+ if (variant !is LibraryVariant) {
+ variant.scope.globalScope.project.dependencies.add("implementation", "${Build.GROUP}:booster-android-bugfix-toast:${Build.VERSION}")
+ }
+ }
+
+}
diff --git a/booster-transform-lint/build.gradle b/booster-transform-lint/build.gradle
new file mode 100644
index 000000000..b4ab5659f
--- /dev/null
+++ b/booster-transform-lint/build.gradle
@@ -0,0 +1,7 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ kapt "com.google.auto.service:auto-service:1.0-rc4"
+ implementation project(':booster-gradle-base')
+ implementation project(':booster-transform-asm')
+}
diff --git a/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/EntryPoint.kt b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/EntryPoint.kt
new file mode 100644
index 000000000..af39098fc
--- /dev/null
+++ b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/EntryPoint.kt
@@ -0,0 +1,46 @@
+package com.didiglobal.booster.transform.lint
+
+import java.util.Objects
+
+/**
+ * Represents the entry point of main thread / UI thread
+ *
+ * @author johnsonlee
+ */
+internal class EntryPoint(val name: String, val desc: String) {
+
+ override fun hashCode(): Int {
+ return Objects.hash(name, desc)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+ if (other !is EntryPoint) {
+ return false
+ }
+
+ return name == other.name && desc == other.desc
+ }
+
+ override fun toString(): String {
+ return "$name$desc"
+ }
+}
+
+internal val APPLICATION_ENTRY_POINTS = setOf(EntryPoint("onCreate", "()V"))
+
+internal val ACTIVITY_ENTRY_POINTS = setOf(
+ EntryPoint("onCreate", "(Landroid/os/Bundle;)V"),
+ EntryPoint("onStart", "()V"),
+ EntryPoint("onResume", "()V"),
+ EntryPoint("onPause", "()V"),
+ EntryPoint("onStop", "()V"),
+ EntryPoint("onDestroy", "()")
+)
+
+internal val SERVICE_ENTRY_POINTS = setOf(
+ EntryPoint("onCreate", "()V"),
+ EntryPoint("onStartCommand", "(Landroid/content/Intent;II)I")
+)
diff --git a/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/LintTransformer.kt b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/LintTransformer.kt
new file mode 100644
index 000000000..3202fda79
--- /dev/null
+++ b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/LintTransformer.kt
@@ -0,0 +1,182 @@
+package com.didiglobal.booster.transform.lint
+
+import com.didiglobal.booster.kotlinx.MAGENTA
+import com.didiglobal.booster.kotlinx.RESET
+import com.didiglobal.booster.kotlinx.asIterable
+import com.didiglobal.booster.kotlinx.file
+import com.didiglobal.booster.transform.ArtifactManager
+import com.didiglobal.booster.transform.TransformContext
+import com.didiglobal.booster.transform.asm.ClassTransformer
+import com.didiglobal.booster.transform.lint.graph.CallGraph
+import com.didiglobal.booster.transform.lint.graph.CallGraph.Node
+import com.didiglobal.booster.util.ComponentHandler
+import com.google.auto.service.AutoService
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.MethodInsnNode
+import java.io.File
+import java.net.URI
+import java.util.stream.Collectors
+import javax.xml.parsers.SAXParserFactory
+
+/**
+ * Represents a class node transformer for static analysis
+ *
+ * @author johnsonlee
+ */
+@AutoService(ClassTransformer::class)
+class LintTransformer : ClassTransformer {
+
+ private val builder = CallGraph.Builder()
+
+ override fun transform(context: TransformContext, klass: ClassNode): ClassNode {
+ klass.methods.forEach { method ->
+ method.instructions.iterator().asIterable().filterIsInstance(MethodInsnNode::class.java).forEach { invoke ->
+ val from = CallGraph.Node(klass.name, method.name, method.desc)
+ val to = CallGraph.Node(invoke.owner, invoke.name, invoke.desc)
+ // break recursive invocation
+ if (!builder.hasEdge(to, from)) {
+ builder.addEdge(from, to)
+ }
+ }
+ }
+ return klass
+ }
+
+ override fun onPostTransform(context: TransformContext) {
+ val factory = SAXParserFactory.newInstance()
+ val parser = factory.newSAXParser()
+ val manifest = context.artifacts.get(ArtifactManager.MERGED_MANIFESTS).single().file("AndroidManifest.xml")
+ val graph = builder.build()
+ val apis = if (context.hasProperty(PROPERTY_LINT_APIS)) context.lintApis else LINT_APIS
+
+ ComponentHandler().let { handler ->
+ parser.parse(manifest, handler)
+ graph.analyse(handler.applications, APPLICATION_ENTRY_POINTS, apis)
+ graph.analyse(handler.activities, ACTIVITY_ENTRY_POINTS, apis)
+ graph.analyse(handler.services, SERVICE_ENTRY_POINTS, apis)
+
+ }
+ }
+
+}
+
+private val TransformContext.lintApis: Set
+ get() {
+ val uri = URI(this.getProperty(PROPERTY_LINT_APIS))
+ val url = if (uri.isAbsolute) uri.toURL() else File(uri).toURI().toURL()
+
+ url.openStream().bufferedReader().use {
+ return it.lines().filter(String::isNotBlank).map { line ->
+ Node.from(line.trim())
+ }.collect(Collectors.toSet())
+ }
+ }
+
+private fun CallGraph.analyse(components: Set, entryPoints: Set, apis: Set) {
+ components.map {
+ it.replace('.', '/')
+ }.forEach { component ->
+ entryPoints.map {
+ Node(component, it.name, it.desc)
+ }.forEach {
+ analyse(it, listOf(it), apis)
+ }
+ }
+}
+
+private fun CallGraph.analyse(node: Node, parent: List, apis: Set) {
+ parent.last()
+ this.edges[node]?.forEach {
+ if (parent.contains(it)) {
+ return
+ }
+
+ val paths = parent.plus(it)
+ if (apis.contains(it)) {
+ println(" ⚠️ $MAGENTA${paths.joinToString("$RESET -> $MAGENTA")} $RESET")
+ return
+ }
+ analyse(it, paths, apis)
+ }
+}
+
+private val PROPERTY_LINT_APIS = "${Build.ARTIFACT.replace('-', '.')}.apis"
+
+internal val LINT_APIS = setOf(
+ Node("java/lang/Object", "wait", "()V"),
+ Node("java/lang/Object", "wait", "(J)V"),
+ Node("java/lang/Object", "wait", "(JI)V"),
+ Node("java/lang/ClassLoader", "getResource", "(Ljava/lang/String;)Ljava/net/URL;"),
+ Node("java/lang/ClassLoader", "getResources", "(Ljava/lang/String;)Ljava/util/Enumeration;"),
+ Node("java/lang/ClassLoader", "getResourceAsStream", "(Ljava/lang/String;)Ljava/io/InputStream;"),
+ Node("java/lang/ClassLoader", "getSystemResource", "(Ljava/lang/String;)Ljava/net/URL;"),
+ Node("java/lang/ClassLoader", "getSystemResources", "(Ljava/lang/String;)Ljava/util/Enumeration;"),
+ Node("java/lang/ClassLoader", "getSystemResourceAsStream", "(Ljava/lang/String;)Ljava/io/InputStream;"),
+ Node("java/io/InputStream", "read", "()I"),
+ Node("java/io/InputStream", "read", "([B)I"),
+ Node("java/io/InputStream", "read", "([BII)I"),
+ Node("java/io/BufferedInputStream", "read", "()I"),
+ Node("java/io/BufferedInputStream", "read", "([BII)I"),
+ Node("java/io/OutputStream", "write", "(I)V"),
+ Node("java/io/OutputStream", "write", "([B)V"),
+ Node("java/io/OutputStream", "write", "([BII)V"),
+ Node("java/io/OutputStream", "flush", "()V"),
+ Node("java/io/BufferedOutputStream", "write", "(I)V"),
+ Node("java/io/BufferedOutputStream", "write", "([BII)V"),
+ Node("java/io/BufferedOutputStream", "flush", "()V"),
+ Node("java/io/Reader", "read", "()I"),
+ Node("java/io/Reader", "read", "([C)I"),
+ Node("java/io/Reader", "read", "([CII)I"),
+ Node("java/io/Reader", "read", "(Ljava/nio/CharBuffer;)I"),
+ Node("java/io/Writer", "append", "(C)Ljava/io/Writer;"),
+ Node("java/io/Writer", "append", "(Ljava/lang/CharSequence;)Ljava/io/Writer;"),
+ Node("java/io/Writer", "append", "(Ljava/lang/CharSequence;II)Ljava/io/Writer;"),
+ Node("java/io/Writer", "flush", "()V"),
+ Node("java/io/Writer", "write", "(I)V"),
+ Node("java/io/Writer", "write", "(Ljava/lang/String;)V"),
+ Node("java/io/Writer", "write", "(Ljava/lang/String;II)V"),
+ Node("java/io/Writer", "write", "([C)V"),
+ Node("java/io/Writer", "write", "([CII)V"),
+ Node("java/util/ServiceLoader", "load", "(Ljava/lang/Class;)Ljava/util/ServiceLoader;"),
+ Node("java/util/ServiceLoader", "load", "(Ljava/lang/Class;Ljava/lang/ClassLoader;)Ljava/util/ServiceLoader;"),
+ Node("java/util/zip/ZipFile", "", "(Ljava/lang/String;)"),
+ Node("java/util/zip/ZipFile", "getInputStream", "(Ljava/util/zip/ZipEntry;)"),
+ Node("java/util/jar/JarFile", "", "(Ljava/lang/String;)"),
+ Node("java/util/jar/JarFile", "getInputStream", "(Ljava/util/jar/JarEntry;)"),
+ Node("android/content/Context", "getSharedPreferences", "Ljava/lang/String;I)Landroid/content/SharedPreferences;"),
+ Node("android/content/SharedPreferences\$Editor", "apply", "()V"),
+ Node("android/content/SharedPreferences\$Editor", "commit", "()B"),
+ Node("android/content/res/AssetManager", "list", "(Ljava/lang/String;)[Ljava/lang/String;"),
+ Node("android/content/res/AssetManager", "open", "(Ljava/lang/String;)Ljava/io/InputStream;"),
+ Node("android/content/res/AssetManager", "open", "(Ljava/lang/String;I)Ljava/io/InputStream;"),
+ Node("android/content/res/AssetManager", "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;"),
+ Node("android/content/res/AssetManager", "openNonAssetFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;"),
+ Node("android/content/res/AssetManager", "openNonAssetFd", "(ILjava/lang/String;)Landroid/content/res/AssetFileDescriptor;"),
+ Node("android/content/res/AssetManager", "openXmlResourceParser", "(Ljava/lang/String;)Landroid/content/res/XmlResourceParser;"),
+ Node("android/content/res/AssetManager", "openXmlResourceParser", "(ILjava/lang/String;)Landroid/content/res/XmlResourceParser;"),
+ Node("android/database/sqlite/SQLiteDatabase", "beginTransaction", "()V"),
+ Node("android/database/sqlite/SQLiteDatabase", "beginTransactionNonExclusive", "()V"),
+ Node("android/database/sqlite/SQLiteDatabase", "beginTransactionWithListener", "(Landroid/database/sqlite/SQLiteTransactionListener;)V"),
+ Node("android/database/sqlite/SQLiteDatabase", "delete", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V"),
+ Node("android/database/sqlite/SQLiteDatabase", "deleteDatabase", "(Ljava/io/File;)Z"),
+ Node("android/database/sqlite/SQLiteDatabase", "endTransaction", "()V"),
+ Node("android/database/sqlite/SQLiteDatabase", "execSQL", "(Ljava/lang/String;)V"),
+ Node("android/database/sqlite/SQLiteDatabase", "execSQL", "(Ljava/lang/String;[Ljava/lang/Object;)V"),
+ Node("android/database/sqlite/SQLiteDatabase", "insert", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;)J"),
+ Node("android/database/sqlite/SQLiteDatabase", "insertOrThrow", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;)J"),
+ Node("android/database/sqlite/SQLiteDatabase", "insertWithOnConflict", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;I)J"),
+ Node("android/database/sqlite/SQLiteDatabase", "query", "(ZLjava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;"),
+ Node("android/database/sqlite/SQLiteDatabase", "query", "(Ljava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;"),
+ Node("android/database/sqlite/SQLiteDatabase", "query", "(ZLjava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/os/CancellationSignal;)Landroid/database/Cursor;"),
+ Node("android/database/sqlite/SQLiteDatabase", "query", "(Ljava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;"),
+ Node("android/database/sqlite/SQLiteDatabase", "queryWithFactory", "(Landroid/database/sqlite/SQLiteDatabase\$CursorFactory;ZLjava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/os/CancellationSignal;)Landroid/database/Cursor;"),
+ Node("android/database/sqlite/SQLiteDatabase", "queryWithFactory", "(Landroid/database/sqlite/SQLiteDatabase\$CursorFactory;ZLjava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;"),
+ Node("android/database/sqlite/SQLiteDatabase", "rawQuery", "(Ljava/lang/String;[Ljava/lang/String;Landroid/os/CancellationSignal;)Landroid/database/Cursor;"),
+ Node("android/database/sqlite/SQLiteDatabase", "rawQuery", "(Ljava/lang/String;[Ljava/lang/String;)Landroid/database/Cursor;"),
+ Node("android/database/sqlite/SQLiteDatabase", "rawQueryWithFactory", "(Landroid/database/sqlite/SQLiteDatabase\$CursorFactory;Ljava/lang/String;[java/lang/String;Ljava/lang/String;Landroid/os/CancellationSignal;)Landroid/database/Cursor;"),
+ Node("android/database/sqlite/SQLiteDatabase", "rawQueryWithFactory", "(Landroid/database/sqlite/SQLiteDatabase\$CursorFactory;Ljava/lang/String;[java/lang/String;Ljava/lang/String;)Landroid/database/Cursor;"),
+ Node("android/database/sqlite/SQLiteDatabase", "replace", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;)J"),
+ Node("android/database/sqlite/SQLiteDatabase", "replaceOrThrow", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;)J"),
+ Node("android/database/sqlite/SQLiteDatabase", "update", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;)J"),
+ Node("android/database/sqlite/SQLiteDatabase", "updateWithOnConflict", "(Ljava/lang/String;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;I)J")
+)
diff --git a/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/graph/CallGraph.kt b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/graph/CallGraph.kt
new file mode 100644
index 000000000..03200658d
--- /dev/null
+++ b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/graph/CallGraph.kt
@@ -0,0 +1,109 @@
+package com.didiglobal.booster.transform.lint.graph
+
+import java.util.Objects
+
+/**
+ * Represents the call graph
+ *
+ * @author johnsonlee
+ */
+class CallGraph private constructor(val edges: Map>) : Iterable {
+
+ override fun iterator(): Iterator {
+ return edges.map { pair ->
+ pair.value.map {
+ Edge(pair.key, it)
+ }.toSet()
+ }.flatten().iterator()
+ }
+
+ class Node(val type: String, val name: String, val desc: String) {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ if (other !is Node) {
+ return false
+ }
+
+ return type == other.type && name == other.name && desc == other.desc
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(type, name, desc)
+ }
+
+ override fun toString(): String {
+ return "$type.$name$desc"
+ }
+
+ companion object {
+ fun from(s: String): Node {
+ val lbrace = s.lastIndexOf('(')
+ val dot = s.lastIndexOf('.', lbrace)
+ if (lbrace < 0 || dot < 0) {
+ throw IllegalArgumentException(s)
+ }
+ return Node(s.substring(0, dot), s.substring(dot + 1, lbrace), s.substring(lbrace))
+ }
+ }
+ }
+
+ class Edge(val from: Node, val to: Node) {
+
+ override fun hashCode(): Int {
+ return Objects.hash(from, to)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ if (other !is Edge) {
+ return false
+ }
+
+ return from == other.from && to == other.to
+ }
+
+ override fun toString(): String {
+ return "$from -> $to"
+ }
+ }
+
+ class Builder {
+
+ private val edges = mutableMapOf>()
+
+ fun addEdge(edge: Edge): Builder {
+ this.edges.getOrPut(edge.from) {
+ mutableSetOf()
+ }.add(edge.to)
+ return this
+ }
+
+ fun hasEdge(edge: Edge): Boolean {
+ return this.hasEdge(edge.from, edge.to)
+ }
+
+ fun hasEdge(from: Node, to: Node): Boolean {
+ return this.edges.containsKey(from) && this.edges[from]?.contains(to) == true
+ }
+
+ fun addEdge(from: Node, to: Node): Builder {
+ this.edges.getOrPut(from) {
+ mutableSetOf()
+ }.add(to)
+ return this
+ }
+
+ fun build(): CallGraph {
+ return CallGraph(this.edges)
+ }
+
+ }
+
+}
diff --git a/booster-transform-lint/src/test/kotlin/com/didiglobal/booster/transform/lint/URITest.kt b/booster-transform-lint/src/test/kotlin/com/didiglobal/booster/transform/lint/URITest.kt
new file mode 100644
index 000000000..a65c3d088
--- /dev/null
+++ b/booster-transform-lint/src/test/kotlin/com/didiglobal/booster/transform/lint/URITest.kt
@@ -0,0 +1,18 @@
+package com.didiglobal.booster.transform.lint
+
+import java.net.URI
+import kotlin.test.Test
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class URITest {
+
+ @Test
+ fun `parse string as uri`() {
+ val home = URI(System.getProperty("user.home"))
+ assertFalse(home.isAbsolute)
+ val github = URI("http://github.com")
+ assertTrue(github.isAbsolute)
+ }
+
+}
diff --git a/booster-transform-shrink/build.gradle b/booster-transform-shrink/build.gradle
new file mode 100644
index 000000000..b4ab5659f
--- /dev/null
+++ b/booster-transform-shrink/build.gradle
@@ -0,0 +1,7 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ kapt "com.google.auto.service:auto-service:1.0-rc4"
+ implementation project(':booster-gradle-base')
+ implementation project(':booster-transform-asm')
+}
diff --git a/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/MalformedSymbolListException.kt b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/MalformedSymbolListException.kt
new file mode 100644
index 000000000..0c589ac82
--- /dev/null
+++ b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/MalformedSymbolListException.kt
@@ -0,0 +1,5 @@
+package com.didiglobal.booster.transform.shrink
+
+import java.lang.Exception
+
+class MalformedSymbolListException(msg: String?) : Exception(msg)
diff --git a/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/RFinder.kt b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/RFinder.kt
new file mode 100644
index 000000000..1acf26c1f
--- /dev/null
+++ b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/RFinder.kt
@@ -0,0 +1,25 @@
+package com.didiglobal.booster.transform.shrink
+
+import java.io.File
+import java.util.concurrent.RecursiveTask
+
+internal class RFinder(private val root: File) : RecursiveTask>() {
+
+ override fun compute(): Collection {
+ val tasks = mutableListOf>>()
+ val result = mutableListOf()
+
+ root.listFiles()?.forEach { file ->
+ if (file.isDirectory) {
+ RFinder(file).also { task ->
+ tasks.add(task)
+ }.fork()
+ } else if ("R.class" == file.name || (file.name.startsWith("R$") && file.name.endsWith(".class"))) {
+ result.add(file)
+ }
+ }
+
+ return result + tasks.flatMap { it.join() }
+ }
+
+}
diff --git a/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/ShrinkTransformer.kt b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/ShrinkTransformer.kt
new file mode 100644
index 000000000..707bd3ab6
--- /dev/null
+++ b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/ShrinkTransformer.kt
@@ -0,0 +1,131 @@
+package com.didiglobal.booster.transform.shrink
+
+import com.didiglobal.booster.kotlinx.BLUE
+import com.didiglobal.booster.kotlinx.RED
+import com.didiglobal.booster.kotlinx.RESET
+import com.didiglobal.booster.kotlinx.YELLOW
+import com.didiglobal.booster.kotlinx.asIterable
+import com.didiglobal.booster.kotlinx.head
+import com.didiglobal.booster.kotlinx.separatorsToSystem
+import com.didiglobal.booster.transform.ArtifactManager.Companion.JAVAC
+import com.didiglobal.booster.transform.ArtifactManager.Companion.SYMBOL_LIST
+import com.didiglobal.booster.transform.ArtifactManager.Companion.SYMBOL_LIST_WITH_PACKAGE_NAME
+import com.didiglobal.booster.transform.TransformContext
+import com.didiglobal.booster.transform.asm.ClassTransformer
+import com.didiglobal.booster.transform.asm.simpleName
+import com.google.auto.service.AutoService
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.FieldInsnNode
+import org.objectweb.asm.tree.FieldNode
+import org.objectweb.asm.tree.LdcInsnNode
+import java.util.concurrent.ForkJoinPool
+
+internal const val R_STYLEABLE = "R\$styleable"
+internal const val R_STYLEABLE_CLASS = "$R_STYLEABLE.class"
+internal const val ANDROID_R = "android/R$"
+internal const val COM_ANDROID_INTERNAL_R = "com/android/internal/R$"
+
+internal val CONST_TYPE_SIGNATURES = setOf("Z", "B", "C", "S", "I", "J", "F", "D", "L/java/lang/String;")
+
+/**
+ * Represents a class node transformer for constants shrinking
+ *
+ * @author johnsonlee
+ */
+@AutoService(ClassTransformer::class)
+class ShrinkTransformer : ClassTransformer {
+
+ lateinit var pkg: String
+ lateinit var pkgRStyleable: String
+ lateinit var symbols: SymbolList
+
+ override fun onPreTransform(context: TransformContext) {
+ this.pkg = context.artifacts.get(SYMBOL_LIST_WITH_PACKAGE_NAME).single().head()!!.replace('.', '/')
+ this.symbols = SymbolList.from(context.artifacts.get(SYMBOL_LIST).single())
+ this.pkgRStyleable = "$pkg/$R_STYLEABLE"
+
+ ForkJoinPool().also { pool ->
+ context.artifacts.get(JAVAC).forEach { root ->
+ pool.invoke(RFinder(root)).filter {
+ // only keep application's R$styleable.class
+ !it.parent.endsWith(pkg.separatorsToSystem()) || it.name != R_STYLEABLE_CLASS
+ }.forEach {
+ // delete library's R
+ if (it.delete()) {
+ println("$YELLOW x ${it.absolutePath}$RESET")
+ } else {
+ println("$BLUE ? ${it.absolutePath}$RESET")
+ }
+ }
+ }
+ }.shutdown()
+ }
+
+ override fun transform(context: TransformContext, klass: ClassNode): ClassNode {
+ when {
+ klass.name == this.pkgRStyleable -> removeIntFields(klass)
+ klass.simpleName == "BuildConfig" -> removeConstantFields(klass)
+ else -> replaceSymbolReferenceWithConstant(klass)
+ }
+ return klass
+ }
+
+ private fun replaceSymbolReferenceWithConstant(klass: ClassNode) {
+ klass.methods.forEach { method ->
+ val insns = method.instructions.iterator().asIterable().filter {
+ it.opcode == Opcodes.GETSTATIC
+ }.map {
+ it as FieldInsnNode
+ }.filter {
+ ("I" == it.desc || "[I" == it.desc)
+ && it.owner.substring(it.owner.lastIndexOf('/') + 1).startsWith("R$")
+ && !(it.owner.startsWith(COM_ANDROID_INTERNAL_R) || it.owner.startsWith(ANDROID_R))
+ }
+
+ val intFields = insns.filter { "I" == it.desc }
+ val intArrayFields = insns.filter { "[I" == it.desc }
+
+ // Replace int field with constant
+ intFields.forEach { field ->
+ val type = field.owner.substring(field.owner.lastIndexOf("/R$") + 3)
+ try {
+ method.instructions.insertBefore(field, LdcInsnNode(symbols.getInt(type, field.name)))
+ method.instructions.remove(field)
+ } catch (e: NullPointerException) {
+ println("$RED ! Unresolvable symbol R.$type.${field.name} : ${klass.name}.${method.name}${method.desc} $RESET")
+ }
+ }
+
+ // Replace library's R fields with application's R fields
+ intArrayFields.forEach { field ->
+ field.owner = "$pkg/${field.owner.substring(field.owner.lastIndexOf('/') + 1)}"
+ }
+ }
+ }
+
+}
+
+private fun removeIntFields(klass: ClassNode) {
+ klass.fields.map {
+ it as FieldNode
+ }.filter {
+ it.desc == "I"
+ }.forEach {
+ klass.fields.remove(it)
+ println("$YELLOW x ${klass.name}.${it.name} : ${it.desc}$RESET")
+ }
+}
+
+private fun removeConstantFields(klass: ClassNode) {
+ klass.fields.map {
+ it as FieldNode
+ }.filter {
+ 0 != (Opcodes.ACC_STATIC and it.access)
+ && 0 != (Opcodes.ACC_FINAL and it.access)
+ && CONST_TYPE_SIGNATURES.contains(it.desc)
+ }.forEach {
+ klass.fields.remove(it)
+ println("$YELLOW x ${klass.name}.${it.name} : ${it.desc}$RESET")
+ }
+}
diff --git a/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/SymbolList.kt b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/SymbolList.kt
new file mode 100644
index 000000000..7582f1b6f
--- /dev/null
+++ b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/SymbolList.kt
@@ -0,0 +1,174 @@
+package com.didiglobal.booster.transform.shrink
+
+import java.io.File
+import java.util.Objects
+
+/**
+ * Represents the symbol list of resources
+ *
+ * @author johnsonlee
+ */
+class SymbolList private constructor(builder: Builder) : Iterable> {
+
+ private val symbols = builder.symbols
+
+ override fun iterator(): Iterator> = this.symbols.iterator()
+
+ fun getInt(type: String, name: String): Int {
+ return (this.symbols.find { it.type == type && it.name == name }?.value as? Int)!!
+ }
+
+ fun getIntArray(type: String = "styleable", name: String): IntArray {
+ return (this.symbols.find { it.type == type && it.name == name && it is IntArraySymbol }?.value as? IntArray)!!
+ }
+
+ abstract class Symbol(val type: String, val name: String, val value: T) {
+
+ override fun hashCode(): Int {
+ return Objects.hash(type, name, value)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ if (other !is Symbol<*>) {
+ return false
+ }
+
+ return type == other.type && name == other.name && value == other.value
+ }
+ }
+
+ class IntSymbol(type: String, name: String, value: Int) : Symbol(type, name, value) {
+ override fun toString(): String = "int $type $name $value"
+ }
+
+ class IntArraySymbol(type: String, name: String, value: IntArray) : Symbol(type, name, value) {
+ override fun toString(): String = "int[] $type $name ${value.joinToString(", ", "{ ", " }") { "0x${it.toString(16)}" }}"
+ }
+
+ class Builder {
+
+ val symbols = mutableListOf>()
+
+ fun build(): SymbolList {
+ return SymbolList(this)
+ }
+
+ fun addSymbol(symbol: Symbol<*>): Builder {
+ this.symbols.add(symbol)
+ return this
+ }
+
+ }
+
+ companion object {
+
+ /**
+ * Parses symbols from the specified file
+ *
+ * @param file symbol list file
+ */
+ fun from(file: File): SymbolList {
+ return SymbolList.Builder().also { builder ->
+ file.forEachLine { line ->
+ val sp1 = line.nextColumnIndex(' ')
+ val dataType = line.substring(0, sp1)
+ when (dataType) {
+ "int" -> {
+ val sp2 = line.nextColumnIndex(' ', sp1 + 1)
+ val type = line.substring(sp1 + 1, sp2)
+ val sp3 = line.nextColumnIndex(' ', sp2 + 1)
+ val name = line.substring(sp2 + 1, sp3)
+ val value: Int = line.substring(sp3 + 1).toInt()
+ builder.addSymbol(IntSymbol(type, name, value))
+ }
+ "int[]" -> {
+ val sp2 = line.nextColumnIndex(' ', sp1 + 1)
+ val type = line.substring(sp1 + 1, sp2)
+ val sp3 = line.nextColumnIndex(' ', sp2 + 1)
+ val name = line.substring(sp2 + 1, sp3)
+ val leftBrace = line.nextColumnIndex('{', sp3)
+ val rightBrace = line.prevColumnIndex('}')
+ val vStart = line.skipWhitespaceForward(leftBrace + 1)
+ val vEnd = line.skipWhitespaceBackward(rightBrace - 1) + 1
+ val values = mutableListOf()
+ var i = vStart
+
+ while (i < vEnd) {
+ val comma = line.nextColumnIndex(',', i, true)
+ i = if (comma > -1) {
+ values.add(line.substring(line.skipWhitespaceForward(i), comma).toInt())
+ line.skipWhitespaceForward(comma + 1)
+ } else {
+ values.add(line.substring(i, vEnd).toInt())
+ vEnd
+ }
+ }
+
+ builder.addSymbol(IntArraySymbol(type, name, values.toIntArray()))
+ }
+ else -> throw MalformedSymbolListException(file.absolutePath)
+ }
+ }
+ }.build()
+ }
+
+ }
+
+}
+
+
+private fun String.toInt(): Int {
+ return if (startsWith("0x")) {
+ substring(2).toInt(16)
+ } else {
+ toInt(10)
+ }
+}
+
+private fun String.nextColumnIndex(delimiter: Char = ' ', startIndex: Int = 0, optional: Boolean = false): Int {
+ val index = this.indexOf(delimiter, startIndex)
+ if (!optional && index < 0) {
+ throw MalformedSymbolListException(this)
+ }
+ return index
+}
+
+private fun String.prevColumnIndex(delimiter: Char = ' ', startIndex: Int = lastIndex, optional: Boolean = false): Int {
+ val index = this.lastIndexOf(delimiter, startIndex)
+ if (!optional && index < 0) {
+ throw MalformedSymbolListException(this)
+ }
+ return index
+}
+
+private fun String.skipWhitespaceForward(startIndex: Int = 0): Int {
+ var i = startIndex
+ val n = length
+
+ while (i < n) {
+ if (!this[i].isWhitespace()) {
+ break
+ }
+ i++
+ }
+
+ return i
+}
+
+private fun String.skipWhitespaceBackward(startIndex: Int = lastIndex): Int {
+ var i = startIndex
+
+ while (i >= 0) {
+ if (!this[i].isWhitespace()) {
+ break
+ }
+ i--
+ }
+
+ return i
+}
+
diff --git a/booster-transform-shrink/src/test/kotlin/com/didiglobal/booster/transform/shrink/SymbolListTest.kt b/booster-transform-shrink/src/test/kotlin/com/didiglobal/booster/transform/shrink/SymbolListTest.kt
new file mode 100644
index 000000000..2118d8b80
--- /dev/null
+++ b/booster-transform-shrink/src/test/kotlin/com/didiglobal/booster/transform/shrink/SymbolListTest.kt
@@ -0,0 +1,18 @@
+package com.didiglobal.booster.transform.shrink
+
+import com.didiglobal.booster.kotlinx.file
+import java.io.File
+import kotlin.test.Test
+
+val PWD = File(System.getProperty("user.dir"))
+
+class SymbolListTest {
+
+ @Test
+ fun `parse symbol list from R txt`() {
+ SymbolList.from(PWD.file("src", "test", "resources", "R.txt")).forEach { symbol ->
+ println(symbol)
+ }
+ }
+
+}
diff --git a/booster-transform-shrink/src/test/resources/R.txt b/booster-transform-shrink/src/test/resources/R.txt
new file mode 100644
index 000000000..424aaad6e
--- /dev/null
+++ b/booster-transform-shrink/src/test/resources/R.txt
@@ -0,0 +1,186 @@
+int attr alpha 0x7f040001
+int attr coordinatorLayoutStyle 0x7f040002
+int attr font 0x7f040003
+int attr fontProviderAuthority 0x7f040004
+int attr fontProviderCerts 0x7f040005
+int attr fontProviderFetchStrategy 0x7f040006
+int attr fontProviderFetchTimeout 0x7f040007
+int attr fontProviderPackage 0x7f040008
+int attr fontProviderQuery 0x7f040009
+int attr fontStyle 0x7f04000a
+int attr fontVariationSettings 0x7f04000b
+int attr fontWeight 0x7f04000c
+int attr keylines 0x7f04000d
+int attr layout_anchor 0x7f04000e
+int attr layout_anchorGravity 0x7f04000f
+int attr layout_behavior 0x7f040010
+int attr layout_dodgeInsetEdges 0x7f040011
+int attr layout_insetEdge 0x7f040012
+int attr layout_keyline 0x7f040013
+int attr statusBarBackground 0x7f040014
+int attr ttcIndex 0x7f040015
+int color notification_action_color_filter 0x7f060001
+int color notification_icon_bg_color 0x7f060002
+int color notification_material_background_media_default_color 0x7f060003
+int color primary_text_default_material_dark 0x7f060004
+int color ripple_material_light 0x7f060005
+int color secondary_text_default_material_dark 0x7f060006
+int color secondary_text_default_material_light 0x7f060007
+int dimen compat_button_inset_horizontal_material 0x7f080001
+int dimen compat_button_inset_vertical_material 0x7f080002
+int dimen compat_button_padding_horizontal_material 0x7f080003
+int dimen compat_button_padding_vertical_material 0x7f080004
+int dimen compat_control_corner_material 0x7f080005
+int dimen compat_notification_large_icon_max_height 0x7f080006
+int dimen compat_notification_large_icon_max_width 0x7f080007
+int dimen notification_action_icon_size 0x7f080008
+int dimen notification_action_text_size 0x7f080009
+int dimen notification_big_circle_margin 0x7f08000a
+int dimen notification_content_margin_start 0x7f08000b
+int dimen notification_large_icon_height 0x7f08000c
+int dimen notification_large_icon_width 0x7f08000d
+int dimen notification_main_column_padding_top 0x7f08000e
+int dimen notification_media_narrow_margin 0x7f08000f
+int dimen notification_right_icon_size 0x7f080010
+int dimen notification_right_side_padding_top 0x7f080011
+int dimen notification_small_icon_background_padding 0x7f080012
+int dimen notification_small_icon_size_as_large 0x7f080013
+int dimen notification_subtext_size 0x7f080014
+int dimen notification_top_pad 0x7f080015
+int dimen notification_top_pad_large_text 0x7f080016
+int dimen subtitle_corner_radius 0x7f080017
+int dimen subtitle_outline_width 0x7f080018
+int dimen subtitle_shadow_offset 0x7f080019
+int dimen subtitle_shadow_radius 0x7f08001a
+int drawable notification_action_background 0x7f090001
+int drawable notification_bg 0x7f090002
+int drawable notification_bg_low 0x7f090003
+int drawable notification_bg_low_normal 0x7f090004
+int drawable notification_bg_low_pressed 0x7f090005
+int drawable notification_bg_normal 0x7f090006
+int drawable notification_bg_normal_pressed 0x7f090007
+int drawable notification_icon_background 0x7f090008
+int drawable notification_template_icon_bg 0x7f090009
+int drawable notification_template_icon_low_bg 0x7f09000a
+int drawable notification_tile_bg 0x7f09000b
+int drawable notify_panel_notification_icon_bg 0x7f09000c
+int id action0 0x7f0c0001
+int id action_container 0x7f0c0002
+int id action_divider 0x7f0c0003
+int id action_image 0x7f0c0004
+int id action_text 0x7f0c0005
+int id actions 0x7f0c0006
+int id async 0x7f0c0007
+int id blocking 0x7f0c0008
+int id bottom 0x7f0c0009
+int id cancel_action 0x7f0c000a
+int id chronometer 0x7f0c000b
+int id end 0x7f0c000c
+int id end_padder 0x7f0c000d
+int id forever 0x7f0c000e
+int id icon 0x7f0c000f
+int id icon_group 0x7f0c0010
+int id info 0x7f0c0011
+int id italic 0x7f0c0012
+int id left 0x7f0c0013
+int id line1 0x7f0c0014
+int id line3 0x7f0c0015
+int id media_actions 0x7f0c0016
+int id none 0x7f0c0017
+int id normal 0x7f0c0018
+int id notification_background 0x7f0c0019
+int id notification_main_column 0x7f0c001a
+int id notification_main_column_container 0x7f0c001b
+int id right 0x7f0c001c
+int id right_icon 0x7f0c001d
+int id right_side 0x7f0c001e
+int id start 0x7f0c001f
+int id status_bar_latest_event_content 0x7f0c0020
+int id tag_transition_group 0x7f0c0021
+int id tag_unhandled_key_event_manager 0x7f0c0022
+int id tag_unhandled_key_listeners 0x7f0c0023
+int id text 0x7f0c0024
+int id text2 0x7f0c0025
+int id time 0x7f0c0026
+int id title 0x7f0c0027
+int id top 0x7f0c0028
+int integer cancel_button_image_alpha 0x7f0d0001
+int integer status_bar_notification_info_maxnum 0x7f0d0002
+int layout notification_action 0x7f0f0001
+int layout notification_action_tombstone 0x7f0f0002
+int layout notification_media_action 0x7f0f0003
+int layout notification_media_cancel_action 0x7f0f0004
+int layout notification_template_big_media 0x7f0f0005
+int layout notification_template_big_media_custom 0x7f0f0006
+int layout notification_template_big_media_narrow 0x7f0f0007
+int layout notification_template_big_media_narrow_custom 0x7f0f0008
+int layout notification_template_custom_big 0x7f0f0009
+int layout notification_template_icon_group 0x7f0f000a
+int layout notification_template_lines_media 0x7f0f000b
+int layout notification_template_media 0x7f0f000c
+int layout notification_template_media_custom 0x7f0f000d
+int layout notification_template_part_chronometer 0x7f0f000e
+int layout notification_template_part_time 0x7f0f000f
+int string status_bar_notification_info_overflow 0x7f150001
+int style TextAppearance_Compat_Notification 0x7f160001
+int style TextAppearance_Compat_Notification_Info 0x7f160002
+int style TextAppearance_Compat_Notification_Info_Media 0x7f160003
+int style TextAppearance_Compat_Notification_Line2 0x7f160004
+int style TextAppearance_Compat_Notification_Line2_Media 0x7f160005
+int style TextAppearance_Compat_Notification_Media 0x7f160006
+int style TextAppearance_Compat_Notification_Time 0x7f160007
+int style TextAppearance_Compat_Notification_Time_Media 0x7f160008
+int style TextAppearance_Compat_Notification_Title 0x7f160009
+int style TextAppearance_Compat_Notification_Title_Media 0x7f16000a
+int style Widget_Compat_NotificationActionContainer 0x7f16000b
+int style Widget_Compat_NotificationActionText 0x7f16000c
+int style Widget_Support_CoordinatorLayout 0x7f16000d
+int[] styleable ColorStateListItem { 0x7f040001, 0x101031f, 0x10101a5 }
+int styleable ColorStateListItem_alpha 0
+int styleable ColorStateListItem_android_alpha 1
+int styleable ColorStateListItem_android_color 2
+int[] styleable CoordinatorLayout { 0x7f04000d, 0x7f040014 }
+int styleable CoordinatorLayout_keylines 0
+int styleable CoordinatorLayout_statusBarBackground 1
+int[] styleable CoordinatorLayout_Layout { 0x10100b3, 0x7f04000e, 0x7f04000f, 0x7f040010, 0x7f040011, 0x7f040012, 0x7f040013 }
+int styleable CoordinatorLayout_Layout_android_layout_gravity 0
+int styleable CoordinatorLayout_Layout_layout_anchor 1
+int styleable CoordinatorLayout_Layout_layout_anchorGravity 2
+int styleable CoordinatorLayout_Layout_layout_behavior 3
+int styleable CoordinatorLayout_Layout_layout_dodgeInsetEdges 4
+int styleable CoordinatorLayout_Layout_layout_insetEdge 5
+int styleable CoordinatorLayout_Layout_layout_keyline 6
+int[] styleable FontFamily { 0x7f040004, 0x7f040005, 0x7f040006, 0x7f040007, 0x7f040008, 0x7f040009 }
+int styleable FontFamily_fontProviderAuthority 0
+int styleable FontFamily_fontProviderCerts 1
+int styleable FontFamily_fontProviderFetchStrategy 2
+int styleable FontFamily_fontProviderFetchTimeout 3
+int styleable FontFamily_fontProviderPackage 4
+int styleable FontFamily_fontProviderQuery 5
+int[] styleable FontFamilyFont { 0x1010532, 0x101053f, 0x1010570, 0x1010533, 0x101056f, 0x7f040003, 0x7f04000a, 0x7f04000b, 0x7f04000c, 0x7f040015 }
+int styleable FontFamilyFont_android_font 0
+int styleable FontFamilyFont_android_fontStyle 1
+int styleable FontFamilyFont_android_fontVariationSettings 2
+int styleable FontFamilyFont_android_fontWeight 3
+int styleable FontFamilyFont_android_ttcIndex 4
+int styleable FontFamilyFont_font 5
+int styleable FontFamilyFont_fontStyle 6
+int styleable FontFamilyFont_fontVariationSettings 7
+int styleable FontFamilyFont_fontWeight 8
+int styleable FontFamilyFont_ttcIndex 9
+int[] styleable GradientColor { 0x101020b, 0x10101a2, 0x10101a3, 0x101019e, 0x1010512, 0x1010513, 0x10101a4, 0x101019d, 0x1010510, 0x1010511, 0x1010201, 0x10101a1 }
+int styleable GradientColor_android_centerColor 0
+int styleable GradientColor_android_centerX 1
+int styleable GradientColor_android_centerY 2
+int styleable GradientColor_android_endColor 3
+int styleable GradientColor_android_endX 4
+int styleable GradientColor_android_endY 5
+int styleable GradientColor_android_gradientRadius 6
+int styleable GradientColor_android_startColor 7
+int styleable GradientColor_android_startX 8
+int styleable GradientColor_android_startY 9
+int styleable GradientColor_android_tileMode 10
+int styleable GradientColor_android_type 11
+int[] styleable GradientColorItem { 0x10101a5, 0x1010514 }
+int styleable GradientColorItem_android_color 0
+int styleable GradientColorItem_android_offset 1
diff --git a/booster-transform-spi/build.gradle b/booster-transform-spi/build.gradle
new file mode 100644
index 000000000..345bf9ee5
--- /dev/null
+++ b/booster-transform-spi/build.gradle
@@ -0,0 +1 @@
+apply from: '../gradle/booster.gradle'
diff --git a/booster-transform-spi/src/main/java/com/didiglobal/booster/transform/TransformListener.java b/booster-transform-spi/src/main/java/com/didiglobal/booster/transform/TransformListener.java
new file mode 100644
index 000000000..8f6368b36
--- /dev/null
+++ b/booster-transform-spi/src/main/java/com/didiglobal/booster/transform/TransformListener.java
@@ -0,0 +1,18 @@
+package com.didiglobal.booster.transform;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Represents the transform lifecycle listener
+ *
+ * @author johnsonlee
+ */
+public interface TransformListener {
+
+ default void onPreTransform(@NotNull final TransformContext context) {
+ }
+
+ default void onPostTransform(@NotNull final TransformContext context) {
+ }
+
+}
diff --git a/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/ArtifactManager.kt b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/ArtifactManager.kt
new file mode 100644
index 000000000..c306c30a5
--- /dev/null
+++ b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/ArtifactManager.kt
@@ -0,0 +1,31 @@
+package com.didiglobal.booster.transform
+
+import java.io.File
+
+/**
+ * Represents a manager of artifacts
+ */
+interface ArtifactManager {
+
+ companion object {
+ const val AAR = "AAR"
+ const val ALL_CLASSES = "ALL_CLASSES"
+ const val APK = "APK"
+ const val JAR = "JAR"
+ const val JAVAC = "JAVAC"
+ const val MERGED_ASSETS = "MERGED_ASSETS"
+ const val MERGED_RES = "MERGED_RES"
+ const val MERGED_MANIFESTS = "MERGED_MANIFESTS"
+ const val PROCESSED_RES = "PROCESSED_RES"
+ const val SYMBOL_LIST = "SYMBOL_LIST"
+ const val SYMBOL_LIST_WITH_PACKAGE_NAME = "SYMBOL_LIST_WITH_PACKAGE_NAME"
+ }
+
+ /**
+ * Returns the specified type of artifacts
+ *
+ * @param type The type of artifacts
+ */
+ fun get(type: String): Collection = emptyList()
+
+}
diff --git a/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformContext.kt b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformContext.kt
new file mode 100644
index 000000000..fd620db9a
--- /dev/null
+++ b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformContext.kt
@@ -0,0 +1,67 @@
+package com.didiglobal.booster.transform
+
+import java.io.File
+import java.util.concurrent.ExecutorService
+
+/**
+ * Represent the transform context
+ *
+ * @author johnsonlee
+ */
+interface TransformContext {
+
+ /**
+ * The name of transform
+ */
+ val name: String
+
+ /**
+ * The project directory
+ */
+ val projectDir: File
+
+ /**
+ * The build directory
+ */
+ val buildDir: File
+
+ /**
+ * The temporary directory
+ */
+ val temporaryDir: File
+
+ /**
+ * The executor service
+ */
+ val executor: ExecutorService
+
+ /**
+ * The compile classpath
+ */
+ val compileClasspath: Collection
+
+ /**
+ * The runtime classpath
+ */
+ val runtimeClasspath: Collection
+
+ /**
+ * The artifact manager
+ */
+ val artifacts: ArtifactManager
+
+ /**
+ * Check if has the specified property. Generally, the property is equivalent to project property
+ *
+ * @param name the name of property
+ */
+ fun hasProperty(name: String): Boolean
+
+ /**
+ * Returns the value of the specified property. Generally, the property is equivalent to project property
+ *
+ * @param name the name of property
+ */
+ fun getProperty(name: String): String?
+
+}
diff --git a/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/Transformer.kt b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/Transformer.kt
new file mode 100644
index 000000000..172ada870
--- /dev/null
+++ b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/Transformer.kt
@@ -0,0 +1,21 @@
+package com.didiglobal.booster.transform
+
+/**
+ * Represents bytecode transformer
+ *
+ * @author johnsonlee
+ */
+interface Transformer : TransformListener {
+
+ /**
+ * Returns the transformed bytecode
+ *
+ * @param context
+ * The transforming context
+ * @param bytecode
+ * The bytecode to be transformed
+ * @return the transformed bytecode
+ */
+ fun transform(context: TransformContext, bytecode: ByteArray): ByteArray
+
+}
diff --git a/booster-transform-usage/build.gradle b/booster-transform-usage/build.gradle
new file mode 100644
index 000000000..dfdcbdc4b
--- /dev/null
+++ b/booster-transform-usage/build.gradle
@@ -0,0 +1,7 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ kapt "com.google.auto.service:auto-service:1.0-rc4"
+ compile project(':booster-gradle-base')
+ compile project(':booster-transform-asm')
+}
diff --git a/booster-transform-usage/src/main/kotlin/com/didiglobal/booster/transform/usage/UsageTransformer.kt b/booster-transform-usage/src/main/kotlin/com/didiglobal/booster/transform/usage/UsageTransformer.kt
new file mode 100644
index 000000000..a08f6c5e9
--- /dev/null
+++ b/booster-transform-usage/src/main/kotlin/com/didiglobal/booster/transform/usage/UsageTransformer.kt
@@ -0,0 +1,54 @@
+package com.didiglobal.booster.transform.usage
+
+import com.didiglobal.booster.kotlinx.RESET
+import com.didiglobal.booster.kotlinx.YELLOW
+import com.didiglobal.booster.transform.TransformContext
+import com.didiglobal.booster.transform.asm.ClassTransformer
+import com.google.auto.service.AutoService
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.MethodInsnNode
+import java.io.File
+import java.net.URI
+import java.util.stream.Collectors
+
+/**
+ * Represents a class node transformer for type/method/field usage analysis
+ *
+ * @author johnsonlee
+ */
+@AutoService(ClassTransformer::class)
+class UsageTransformer : ClassTransformer {
+
+ override fun transform(context: TransformContext, klass: ClassNode): ClassNode {
+ if (context.hasProperty(PROPERTY_USED_APIS)) {
+ val apis = context.usedApis
+
+ klass.methods.forEach { method ->
+ method.instructions.iterator().asSequence().filterIsInstance(MethodInsnNode::class.java).map {
+ "${it.owner}.${it.name}${it.desc}"
+ }.filter {
+ apis.contains(it)
+ }.forEach {
+ println("$YELLOW ! ${klass.name}.${method.name}${method.desc}: $RESET$it")
+ }
+ }
+ }
+
+ return klass
+ }
+
+}
+
+private val TransformContext.usedApis: Set
+ get() {
+ val uri = URI(this.getProperty(PROPERTY_USED_APIS))
+ val url = if (uri.isAbsolute) uri.toURL() else File(uri).toURI().toURL()
+
+ url.openStream().bufferedReader().use {
+ return it.lines().filter(String::isNotBlank).map { line ->
+ line.trim()
+ }.collect(Collectors.toSet())
+ }
+ }
+
+private val PROPERTY_USED_APIS = "${Build.ARTIFACT.replace('-', '.')}.apis"
diff --git a/booster-transform-util/build.gradle b/booster-transform-util/build.gradle
new file mode 100644
index 000000000..d9d38ea87
--- /dev/null
+++ b/booster-transform-util/build.gradle
@@ -0,0 +1,6 @@
+apply from: '../gradle/booster.gradle'
+
+dependencies {
+ compile project(':booster-transform-spi')
+ compile project(':booster-kotlinx')
+}
diff --git a/booster-transform-util/src/main/kotlin/com/didiglobal/booster/transform/util/transform.kt b/booster-transform-util/src/main/kotlin/com/didiglobal/booster/transform/util/transform.kt
new file mode 100644
index 000000000..44247dfbf
--- /dev/null
+++ b/booster-transform-util/src/main/kotlin/com/didiglobal/booster/transform/util/transform.kt
@@ -0,0 +1,71 @@
+package com.didiglobal.booster.transform.util
+
+import com.didiglobal.booster.kotlinx.parallelWalk
+import com.didiglobal.booster.kotlinx.redirect
+import com.didiglobal.booster.kotlinx.touch
+import java.io.File
+import java.io.InputStream
+import java.util.jar.JarEntry
+import java.util.jar.JarFile
+import java.util.jar.JarOutputStream
+
+fun nop(data: ByteArray): ByteArray = data
+
+/**
+ * Transform this file or directory to the output by the specified transformer
+ *
+ * @param output The output location
+ * @param transformer The byte data transformer
+ */
+fun File.transform(output: File, transformer: (ByteArray) -> ByteArray = ::nop) {
+ when {
+ isDirectory -> {
+ this.parallelWalk().filter { it != this && it.isFile }.forEach { file ->
+ val path = file.absolutePath.substring(this.absolutePath.length + java.io.File.separator.length)
+ file.transform(File(output, path), transformer)
+ }
+ }
+ isFile -> {
+ when (output.extension.toLowerCase()) {
+ "jar" -> {
+ JarOutputStream(output.touch().outputStream()).use { dest ->
+ JarFile(this).use { jar ->
+ jar.entries().asSequence().forEach { entry ->
+ dest.putNextEntry(JarEntry(entry.name))
+ if (!entry.isDirectory) {
+ when (entry.name.substringAfterLast('.', "")) {
+ "class" -> {
+ jar.getInputStream(entry).use { src ->
+ src.transform(transformer).redirect(dest)
+ }
+ }
+ else -> {
+ jar.getInputStream(entry).use { src ->
+ src.copyTo(dest)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ "class" -> {
+ inputStream().use {
+ it.transform(transformer).redirect(output)
+ }
+ }
+ else -> {
+ this.copyTo(output, true)
+ }
+ }
+ }
+ else -> {
+ TODO("Unexpected file: ${this.absolutePath}")
+ }
+ }
+}
+
+fun InputStream.transform(transformer: (ByteArray) -> ByteArray): ByteArray {
+ return transformer(this.readBytes())
+}
diff --git a/booster-transform-util/src/test/kotlin/com/didiglobal/booster/transform/FileTransformTest.kt b/booster-transform-util/src/test/kotlin/com/didiglobal/booster/transform/FileTransformTest.kt
new file mode 100644
index 000000000..863c069e6
--- /dev/null
+++ b/booster-transform-util/src/test/kotlin/com/didiglobal/booster/transform/FileTransformTest.kt
@@ -0,0 +1,36 @@
+package com.didiglobal.booster.transform
+
+import com.didiglobal.booster.kotlinx.file
+import com.didiglobal.booster.transform.util.Build
+import com.didiglobal.booster.transform.util.transform
+import java.io.File
+import java.nio.file.Files
+import java.util.jar.JarFile
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+val PWD: String = System.getProperty("user.dir")
+val BUILD_DIR = File(PWD, "build")
+
+class FileTransformTest {
+
+ @Test
+ fun `transform jar`() {
+ val jar = "${Build.ARTIFACT}-${Build.VERSION}.jar"
+ val src = BUILD_DIR.file("libs", jar)
+ val dest = Files.createTempDirectory(Build.GROUP).toFile().file(jar)
+
+ src.transform(dest) { it }
+
+ JarFile(src).use { a ->
+ JarFile(dest).use { b ->
+ val aEntries = a.entries().asSequence().sortedBy { it.name }.toList()
+ val bEntries = b.entries().asSequence().sortedBy { it.name }.toList()
+ assertEquals(aEntries.size, bEntries.size)
+ assertEquals(aEntries.map { it.name }, bEntries.map { it.name })
+ assertEquals(aEntries.map { it.size }, bEntries.map { it.size })
+ }
+ }
+ }
+
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..908889ad5
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,118 @@
+buildscript {
+ ext.kotlin_version = org.gradle.kotlin.dsl.KotlinDependencyExtensionsKt.embeddedKotlinVersion
+
+ repositories {
+ mavenLocal()
+ google()
+ mavenCentral()
+ jcenter()
+ maven { url 'https://plugins.gradle.org/m2/' }
+ }
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects { project ->
+ apply plugin: 'java'
+ apply plugin: 'maven'
+ apply plugin: 'signing'
+
+ group = 'com.didiglobal.booster'
+ version = '0.1.0'
+
+ repositories {
+ mavenLocal()
+ google()
+ mavenCentral()
+ jcenter()
+ }
+
+ javadoc {
+ failOnError = false
+ }
+
+ task packageJavadoc(type: Jar) {
+ classifier = 'javadoc'
+ from javadoc
+ }
+
+ task packageSources(type: Jar) {
+ classifier = 'sources'
+ from sourceSets.main.allSource
+ }
+
+ artifacts {
+ archives packageJavadoc, packageSources
+ }
+
+ signing {
+ sign configurations.archives
+ }
+
+ uploadArchives {
+ repositories {
+ mavenDeployer {
+ beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+ pom.project {
+ groupId project.group
+ artifactId project.name
+ version project.version
+ description project.description ?: ''
+
+ scm {
+ connection 'scm:git:git://github.com/didi/booster.git'
+ developerConnection 'scm:git:git@github.com:didi/booster.git'
+ url 'https://github.com/didi/booster'
+ }
+
+ licenses {
+ license {
+ name 'Apache License'
+ url 'https://www.apache.org/licenses/LICENSE-2.0'
+ }
+ }
+ }
+ pom.withXml { xml ->
+ (['git', 'log', '--format=%aN %aE'].execute() | ['sort', '-u'].execute()).with {
+ waitForOrKill(5000)
+ if (0 == exitValue()) {
+ def developers = xml.asNode().appendNode('developers')
+ text.eachLine { line ->
+ def author = line.split('\\s+')
+ if (author.length > 1) {
+ developers.appendNode('developer').with {
+ appendNode('id', author[0])
+ appendNode('email', author[1])
+ }
+ }
+ }
+ }
+ }
+ }
+ repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') {
+ authentication(userName: OSSRH_USERNAME, password: OSSRH_PASSWORD)
+ }
+ snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/') {
+ authentication(userName: OSSRH_USERNAME, password: OSSRH_PASSWORD)
+ }
+ }
+ }
+ }
+
+ test {
+ testLogging {
+ events "passed", "skipped", "failed", "standardOut", "standardError"
+ }
+ }
+}
+
+subprojects { project ->
+ rootProject.clean.dependsOn project.clean
+}
+
+dependencies {
+ implementation project(':booster-gradle-plugin')
+}
+
+
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 000000000..dfacaa314
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,35 @@
+import static org.gradle.kotlin.dsl.KotlinDependencyExtensionsKt.embeddedKotlinVersion
+
+buildscript {
+ ext.kotlin_version = embeddedKotlinVersion
+ repositories {
+ mavenLocal()
+ google()
+ mavenCentral()
+ jcenter()
+ }
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+apply plugin: 'java'
+apply plugin: 'maven'
+apply plugin: 'kotlin'
+apply plugin: 'kotlin-kapt'
+
+repositories {
+ mavenLocal()
+ google()
+ mavenCentral()
+ jcenter()
+}
+
+dependencies {
+ implementation gradleApi()
+ implementation localGroovy()
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
+ testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
+ testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
+}
diff --git a/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsGenerator.kt b/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsGenerator.kt
new file mode 100644
index 000000000..5df728d58
--- /dev/null
+++ b/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsGenerator.kt
@@ -0,0 +1,84 @@
+package com.didiglobal.booster.buildprops
+
+import org.gradle.api.internal.AbstractTask
+import org.gradle.api.plugins.JavaPluginConvention
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.IOException
+import java.util.StringTokenizer
+
+/**
+ * The task for `Build.java` source file generating
+ *
+ * @author johnsonlee
+ */
+open class BuildPropsGenerator : AbstractTask() {
+
+ val output: File
+ @OutputDirectory
+ get() = project.getGeneratedSourceDir(project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME))
+
+ @TaskAction
+ @Throws(IOException::class)
+ fun run() {
+ val pkg = mkpkg("${project.group}.${project.name}")
+ val path = "${pkg.replace(".", File.separator)}${File.separator}Build.java"
+ val revision = File(project.rootProject.projectDir, ".git${File.separator}logs${File.separator}HEAD").readText().let {
+ StringTokenizer(it).nextToken(1)
+ }
+
+ File(output, path).also {
+ it.parentFile.mkdirs()
+ it.createNewFile()
+ }.printWriter().use {
+ it.apply {
+ it.println("""
+ /**
+ * DO NOT MODIFY! This file is generated automatically.
+ */
+ package $pkg;
+
+ public interface Build {
+ String GROUP = "${project.group}";
+ String ARTIFACT = "${project.name}";
+ String VERSION = "${project.version}";
+ String REVISION = "$revision";
+ }
+ """.trimIndent())
+ }
+ }
+ }
+
+}
+
+/**
+ * Make a safe package name
+ */
+internal fun mkpkg(s: String): String {
+ return StringBuilder(s.length).apply {
+ s.forEachIndexed { i, c ->
+ if (0 == i) {
+ append(if ('.' != c && !c.isJavaIdentifierStart()) '.' else c)
+ } else {
+ append(if ('.' != c && !c.isJavaIdentifierPart()) '.' else c)
+ }
+ }
+ }.toString().split('.').let {
+ it.filterIndexed { i, s -> !(i > 0 && s == it[i - 1]) }
+ }.joinToString(".")
+}
+
+internal fun StringTokenizer.nextToken(nth: Int): String? {
+ var i = 0
+
+ while (hasMoreTokens()) {
+ val token = nextToken()
+ if (nth == i++) {
+ return token
+ }
+ }
+
+ return null
+}
diff --git a/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsPlugin.kt b/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsPlugin.kt
new file mode 100644
index 000000000..7ec0173fa
--- /dev/null
+++ b/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsPlugin.kt
@@ -0,0 +1,83 @@
+package com.didiglobal.booster.buildprops
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPluginConvention
+import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.SourceSetContainer
+import org.gradle.api.tasks.SourceTask
+import org.gradle.plugins.ide.idea.model.IdeaModel
+import org.gradle.plugins.ide.idea.model.IdeaModule
+import java.io.File
+import java.util.LinkedHashSet
+
+/**
+ * Gradle plugin for `Build.java` source code generating.
+ *
+ * @author johnsonlee
+ */
+class BuildPropsPlugin : Plugin {
+
+ override fun apply(project: Project) {
+ if (!project.plugins.hasPlugin("idea")) {
+ project.plugins.apply("idea")
+ }
+
+ val convention = project.convention
+ val javaPlugin = convention.getPlugin(JavaPluginConvention::class.java)
+ val sourceSets = javaPlugin.sourceSets
+ val buildProps = project.tasks.create("generateBuildProps", BuildPropsGenerator::class.java) {
+ it.outputs.upToDateWhen { false }
+ }
+
+ project.configureIdeaModule(sourceSets)
+ sourceSets.filter { it.name == SourceSet.MAIN_SOURCE_SET_NAME }.forEach { sourceSet ->
+ (project.tasks.findByName(sourceSet.getCompileTaskName("java"))?.dependsOn(buildProps) as? SourceTask)?.source(buildProps.output)
+ (project.tasks.findByName(sourceSet.getCompileTaskName("groovy"))?.dependsOn(buildProps) as? SourceTask)?.source(buildProps.output)
+ (project.tasks.findByName(sourceSet.getCompileTaskName("kotlin"))?.dependsOn(buildProps) as? SourceTask)?.source(buildProps.output)
+ }
+ }
+
+}
+
+internal const val PLUGIN_ID = "buildprops"
+
+internal val GENERATED_SOURCE_ROOT = "generated${File.separator}source${File.separator}$PLUGIN_ID"
+
+internal fun Project.getGeneratedSourceDir(sourceSet: SourceSet) = File(this.buildDir, GENERATED_SOURCE_ROOT + File.separator + sourceSet.name + File.separator + "java")
+
+private fun Project.configureIdeaModule(sourceSets: SourceSetContainer) {
+ val mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
+ val testSourceSet = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME)
+ val mainGeneratedSourcesDir = getGeneratedSourceDir(mainSourceSet)
+ val testGeneratedSourcesDir = getGeneratedSourceDir(testSourceSet)
+ val ideaModule = extensions.getByType(IdeaModel::class.java).module
+ ideaModule.excludeDirs = getIdeaExcludeDirs(getGeneratedSourceDirs(sourceSets), ideaModule)
+ ideaModule.sourceDirs = files(ideaModule.sourceDirs, mainGeneratedSourcesDir).files
+ ideaModule.testSourceDirs = files(ideaModule.testSourceDirs, testGeneratedSourcesDir).files
+ ideaModule.generatedSourceDirs = files(ideaModule.generatedSourceDirs, mainGeneratedSourcesDir, testGeneratedSourcesDir).files
+}
+
+private fun Project.getGeneratedSourceDirs(sourceSets: SourceSetContainer): Set = LinkedHashSet().also { excludes ->
+ sourceSets.forEach { sourceSet ->
+ var f = getGeneratedSourceDir(sourceSet)
+
+ while (f != this.projectDir) {
+ excludes.add(f)
+ f = f.parentFile
+ }
+ }
+}
+
+private fun Project.getIdeaExcludeDirs(excludes: Set, ideaModule: IdeaModule): Set = LinkedHashSet(ideaModule.excludeDirs).also { excludeDirs ->
+ if (excludes.contains(buildDir) && excludeDirs.contains(buildDir)) {
+ excludeDirs.remove(buildDir)
+ buildDir.listFiles()?.filter {
+ it.isDirectory
+ }?.forEach {
+ excludeDirs.add(it)
+ }
+ }
+
+ excludeDirs.removeAll(excludes)
+}
diff --git a/buildSrc/src/test/kotlin/com/didiglobal/booster/buildprops/BuildPropsGeneratorTest.kt b/buildSrc/src/test/kotlin/com/didiglobal/booster/buildprops/BuildPropsGeneratorTest.kt
new file mode 100644
index 000000000..426598463
--- /dev/null
+++ b/buildSrc/src/test/kotlin/com/didiglobal/booster/buildprops/BuildPropsGeneratorTest.kt
@@ -0,0 +1,14 @@
+package com.didiglobal.booster.buildprops
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class BuildPropsGeneratorTest {
+
+ @Test
+ fun test_mkpkg() {
+ val s = mkpkg("booster.booster-gradle-plugin")
+ assertEquals("booster.gradle.plugin", s)
+ }
+
+}
diff --git a/gradle/booster.gradle b/gradle/booster.gradle
new file mode 100644
index 000000000..32653d8c9
--- /dev/null
+++ b/gradle/booster.gradle
@@ -0,0 +1,39 @@
+import com.didiglobal.booster.buildprops.BuildPropsPlugin
+
+apply plugin: 'kotlin'
+apply plugin: 'kotlin-kapt'
+apply plugin: BuildPropsPlugin
+
+sourceSets {
+ main {
+ java {
+ srcDirs += []
+ }
+ kotlin {
+ srcDirs += ['src/main/kotlin', 'src/main/java']
+ }
+ }
+ test {
+ java {
+ srcDirs += []
+ }
+ kotlin {
+ srcDirs += ['src/main/kotlin', 'src/main/java']
+ }
+ }
+}
+
+compileKotlin {
+ kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8
+}
+
+compileTestKotlin {
+ kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+ compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
+ testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
+ testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718
GIT binary patch
literal 54329
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