diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5db12d6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.gradle
+local.properties
+.idea
+.DS_Store
+build/
+reports/
+gradle.properties
+*.iml
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..d19429a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,17 @@
+# Changelog for easylauncher plugin
+
+## v0.5.0 2018/01/12
+
+* Simplify DSL
+* Add support for adaptive launcher icons
+* Add support for variant-specific configuration
+* Add ribbon to debuggable type by default
+
+## v0.4.0 2018/01/06
+
+* Add capability to disable plugin
+* Give priority to flavor over buildType
+
+## v0.3.0 2018/01/06
+
+* Initial internal release
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..675ea46
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+check:
+ ./gradlew clean check bintrayUpload
+
+publish: check
+ ./gradlew releng
+ ./gradlew -PdryRun=false --info plugin:bintrayUpload
+
+update-examples:
+ ./gradlew easylauncher
+ cp ./example-simple/build/generated/easylauncher/res/debug/mipmap-xxhdpi/ic_launcher.png ic-debug.png
+ cp ./example-custom/build/generated/easylauncher/res/localBeta/mipmap-xxhdpi/ic_launcher.png ic-beta.png
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b05419a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,134 @@
+# Easylauncher gradle plugin for Android
+
+This gradle plugin will add a different ribbon to each of your (debuggable) Android app variants. You can of course configure it as you will.
+
+![](icons/ic_launcher_debug.png) ![](ic_launcher_beta.png)
+// TODO more icons
+
+## Usage
+
+```groovy
+// in build.gradle
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.1.2'
+ classpath 'com.akaita.android:easylauncher:0.5.0'
+ }
+}
+```
+
+```groovy
+// in app/build.gradle
+apply plugin: 'com.akaita.android.easylauncher'
+
+android {
+ buildTypes {
+ debug {
+ //Debuggable, will get a default ribbon in the launcher icon
+ }
+ beta {
+ //Debuggable, will get a default ribbon in the launcher icon
+ debuggable true
+ }
+ canary {
+ //Non-debuggable, will not get any default ribbon
+ debuggable false
+ }
+ release {
+ //Non-debuggable, will not get any default ribbon
+ }
+ }
+ productFlavors {
+ local {}
+ qa {}
+ staging {}
+ production {}
+ }
+}
+```
+
+
+Optionally, customize the plugin's behaviour
+```groovy
+easylauncher {
+ // "manifest application[android:icon]" is automatically added to the list
+ iconNames "@mipmap/ic_launcher_foreground" // Traditional launcher icon
+ foregroundIconNames "@mipmap/ic_launcher_foreground" // Foreground of adaptive launcher icon
+
+ productFlavors {
+ local {}
+ qa {
+ // Add one more filter to all `qa` variants
+ filters = redRibbonFilter()
+ }
+ staging {}
+ production {}
+ }
+
+ buildTypes {
+ beta {
+ // Add two more filters to all `beta` variants
+ filters = [
+ customColorRibbonFilter("#0000FF"),
+ overlayFilter(new File("example-custom/launcherOverlay/beta.png"))
+ ]
+ }
+ canary {
+ // Remove ALL filters to `canary` variants
+ enable false
+ }
+ release {}
+ }
+
+ variants {
+ productionDebug {
+ // OVERRIDE all previous filters defined for `productionDebug` variant
+ filters = orangeRibbonFilter()
+ }
+ }
+}
+```
+
+
+## Available filters
+
+| Filter | Result |
+| - | - |
+| `grayRibbonFilter()` | ![](icons/grayRibbon.png) |
+| `greenRibbonFilter()` | ![](icons/greenRibbon.png) |
+| `yellowRibbonFilter()` | ![](icons/yellowRibbon.png) |
+| `orangeRibbonFilter()` | ![](icons/orangeRibbon.png) |
+| `redRibbonFilter()` | ![](icons/redRibbon.png) |
+| `blueRibbonFilter()` | ![](icons/blueRibbon.png) |
+| `grayscaleFilter()` | ![](icons/grayscale.png) |
+| `customColorRibbonFilter("#6600CC")` | ![](icons/customColorRibbon.png) |
+| `overlayFilter(new File("example-custom/launcherOverlay/beta.png"))` | ![](icons/overlay.png) |
+
+
+
+## Project Structure
+
+```
+plugin/ - Gradle plugin
+example-simple/ - Example Android application using easylauncher with the default behaviour
+example-custom/ - Example Android application using easylauncher with the custom configuration
+buildSrc/ - Helper module to use this plugin in example modules
+icons/ - Examples of icons generated by this plugin
+```
+
+
+## Contributing
+
+You can already see my plans for the plugin in the project's Issues section.
+
+Still, I'm open to feature-requests and suggestions.
+Of course, a PR is the best way to get something you want into the plugin ;)
+
+
+## Credits
+
+Easylauncher started as an extension of [Fuji Goro's `ribbonizer` plugin](https://github.com/maskarade/gradle-android-ribbonizer-plugin).
+As it evolved, I decided it changed enough to be worth releasing as a separate plugin.
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..3eefcb9
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.0.0
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..8d911b0
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,26 @@
+apply from: './versioning.gradle'
+apply from: './metadata.gradle'
+
+ext {
+ metadata.version = versionName
+}
+
+buildscript {
+ repositories {
+ jcenter()
+ google()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.0.1'
+ classpath 'com.novoda:bintray-release:0.8.0' // https://github.com/novoda/bintray-release
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.21"
+ classpath "org.jetbrains.kotlin:kotlin-android-extensions:1.2.21"
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ google()
+ }
+}
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 0000000..9b9e9c2
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,9 @@
+System.setProperty("java.awt.headless", "true")
+
+repositories {
+ jcenter()
+}
+
+dependencies {
+ compile project(':plugin')
+}
diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle
new file mode 100644
index 0000000..adade62
--- /dev/null
+++ b/buildSrc/settings.gradle
@@ -0,0 +1,2 @@
+include ':plugin'
+project(':plugin').projectDir = new File('../plugin')
diff --git a/example-custom/build.gradle b/example-custom/build.gradle
new file mode 100644
index 0000000..f1284de
--- /dev/null
+++ b/example-custom/build.gradle
@@ -0,0 +1,87 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'com.akaita.android.easylauncher'
+
+android {
+ compileSdkVersion 27
+ buildToolsVersion '27.0.3'
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 27
+ versionCode 1
+ versionName "1.0"
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+
+ buildTypes {
+ debug {
+ //Debuggable, will get a default ribbon in the launcher icon
+ }
+ beta {
+ //Debuggable, will get a default ribbon in the launcher icon
+ debuggable true
+ }
+ canary {
+ //Non-debuggable, will not get any default ribbon
+ debuggable false
+ }
+ release {
+ //Non-debuggable, will not get any default ribbon
+ }
+ }
+
+ productFlavors {
+ local {}
+ qa {}
+ staging {}
+ production {}
+ }
+}
+
+easylauncher {
+ iconNames "@mipmap/ic_launcher_foreground" // Traditional launcher icon
+ foregroundIconNames "@mipmap/ic_launcher_foreground" // Foreground of adaptive launcher icon
+
+ productFlavors {
+ local {}
+ qa {
+ // Add one more filter to all `qa` variants
+ filters = redRibbonFilter()
+ }
+ staging {}
+ production {}
+ }
+
+ buildTypes {
+ beta {
+ // Add two more filters to all `beta` variants
+ filters = [
+ customColorRibbonFilter("#0000FF"),
+ overlayFilter(new File("example-custom/launcherOverlay/beta.png"))
+ ]
+ }
+ canary {
+ // Remove ALL filters to `canary` variants
+ enable false
+ }
+ release {}
+ }
+
+ variants {
+ productionDebug {
+ // OVERRIDE all previous filters defined for `productionDebug` variant
+ filters = orangeRibbonFilter()
+ }
+ }
+}
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:27.0.2'
+ compile "org.jetbrains.kotlin:kotlin-stdlib:1.2.21"
+}
diff --git a/example-custom/launcherOverlay/beta.png b/example-custom/launcherOverlay/beta.png
new file mode 100644
index 0000000..9b7a525
Binary files /dev/null and b/example-custom/launcherOverlay/beta.png differ
diff --git a/example-custom/src/main/AndroidManifest.xml b/example-custom/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7ab97cd
--- /dev/null
+++ b/example-custom/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example-custom/src/main/ic_launcher-web.png b/example-custom/src/main/ic_launcher-web.png
new file mode 100644
index 0000000..ab63c32
Binary files /dev/null and b/example-custom/src/main/ic_launcher-web.png differ
diff --git a/example-custom/src/main/java/com/akaita/android/easylauncher/example/MainActivity.kt b/example-custom/src/main/java/com/akaita/android/easylauncher/example/MainActivity.kt
new file mode 100644
index 0000000..d04a2ae
--- /dev/null
+++ b/example-custom/src/main/java/com/akaita/android/easylauncher/example/MainActivity.kt
@@ -0,0 +1,22 @@
+package com.akaita.android.easylauncher.example
+
+import android.app.Activity
+import android.app.AlertDialog
+import android.os.Bundle
+import kotlinx.android.synthetic.main.activity_main.*
+
+class MainActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ button.setOnClickListener {
+ AlertDialog.Builder(this)
+ .setTitle("Hello, Android!")
+ .setMessage("This is an example app.")
+ .setPositiveButton("OK", null)
+ .show()
+ }
+ }
+}
diff --git a/example-custom/src/main/res/layout/activity_main.xml b/example-custom/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..daed97d
--- /dev/null
+++ b/example-custom/src/main/res/layout/activity_main.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/example-custom/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/example-custom/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/example-custom/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/example-custom/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/example-custom/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/example-custom/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/example-custom/src/main/res/mipmap-hdpi/ic_launcher.png b/example-custom/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..413a49e
Binary files /dev/null and b/example-custom/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/example-custom/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/example-custom/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..78fb037
Binary files /dev/null and b/example-custom/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/example-custom/src/main/res/mipmap-hdpi/ic_launcher_round.png b/example-custom/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9b13b27
Binary files /dev/null and b/example-custom/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/example-custom/src/main/res/mipmap-mdpi/ic_launcher.png b/example-custom/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..311dd79
Binary files /dev/null and b/example-custom/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/example-custom/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/example-custom/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..4f93925
Binary files /dev/null and b/example-custom/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/example-custom/src/main/res/mipmap-mdpi/ic_launcher_round.png b/example-custom/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..965194f
Binary files /dev/null and b/example-custom/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/example-custom/src/main/res/mipmap-xhdpi/ic_launcher.png b/example-custom/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..7639db2
Binary files /dev/null and b/example-custom/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/example-custom/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/example-custom/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..409eacd
Binary files /dev/null and b/example-custom/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/example-custom/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/example-custom/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..ee52b7f
Binary files /dev/null and b/example-custom/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/example-custom/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example-custom/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..6212b43
Binary files /dev/null and b/example-custom/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/example-custom/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/example-custom/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..4e55caa
Binary files /dev/null and b/example-custom/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/example-custom/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/example-custom/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b20de4a
Binary files /dev/null and b/example-custom/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/example-custom/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example-custom/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..3046ff5
Binary files /dev/null and b/example-custom/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/example-custom/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/example-custom/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..b27eb19
Binary files /dev/null and b/example-custom/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/example-custom/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/example-custom/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9bda4ae
Binary files /dev/null and b/example-custom/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/example-custom/src/main/res/values-v26/colors.xml b/example-custom/src/main/res/values-v26/colors.xml
new file mode 100644
index 0000000..25976ba
--- /dev/null
+++ b/example-custom/src/main/res/values-v26/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #1c7917
+
diff --git a/example-custom/src/main/res/values-w820dp/dimens.xml b/example-custom/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/example-custom/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/example-custom/src/main/res/values/dimens.xml b/example-custom/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/example-custom/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/example-custom/src/main/res/values/ic_launcher_background.xml b/example-custom/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..68c7691
--- /dev/null
+++ b/example-custom/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #00C89C
+
\ No newline at end of file
diff --git a/example-custom/src/main/res/values/strings.xml b/example-custom/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0d23f22
--- /dev/null
+++ b/example-custom/src/main/res/values/strings.xml
@@ -0,0 +1,9 @@
+
+
+
+ App Icon Demo
+ Hello, world!
+ Settings
+ button
+
+
diff --git a/example-custom/src/main/res/values/styles.xml b/example-custom/src/main/res/values/styles.xml
new file mode 100644
index 0000000..e0c5aae
--- /dev/null
+++ b/example-custom/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/example-simple/build.gradle b/example-simple/build.gradle
new file mode 100644
index 0000000..fbc5922
--- /dev/null
+++ b/example-simple/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'com.android.application'
+apply plugin: 'com.akaita.android.easylauncher'
+
+android {
+ compileSdkVersion 27
+ buildToolsVersion '27.0.3'
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 27
+ versionCode 1
+ versionName "1.0"
+ }
+}
diff --git a/example-simple/src/main/AndroidManifest.xml b/example-simple/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0ed4882
--- /dev/null
+++ b/example-simple/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example-simple/src/main/java/com/akaita/android/easylauncher/example/MainActivity.java b/example-simple/src/main/java/com/akaita/android/easylauncher/example/MainActivity.java
new file mode 100644
index 0000000..7a0d16d
--- /dev/null
+++ b/example-simple/src/main/java/com/akaita/android/easylauncher/example/MainActivity.java
@@ -0,0 +1,26 @@
+package com.akaita.android.easylauncher.example;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.View;
+
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ new AlertDialog.Builder(MainActivity.this)
+ .setTitle("Hello, Android!")
+ .setMessage("This is an example app.")
+ .setPositiveButton("OK", null)
+ .show();
+ }
+ });
+ }
+}
diff --git a/example-simple/src/main/res/layout/activity_main.xml b/example-simple/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..daed97d
--- /dev/null
+++ b/example-simple/src/main/res/layout/activity_main.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/example-simple/src/main/res/mipmap-hdpi/ic_launcher.png b/example-simple/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..edb98c8
Binary files /dev/null and b/example-simple/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/example-simple/src/main/res/mipmap-mdpi/ic_launcher.png b/example-simple/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..6447842
Binary files /dev/null and b/example-simple/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/example-simple/src/main/res/mipmap-xhdpi/ic_launcher.png b/example-simple/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..7bbbe37
Binary files /dev/null and b/example-simple/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/example-simple/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example-simple/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..94f6172
Binary files /dev/null and b/example-simple/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/example-simple/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example-simple/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..c3c8e70
Binary files /dev/null and b/example-simple/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/example-simple/src/main/res/values-w820dp/dimens.xml b/example-simple/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/example-simple/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/example-simple/src/main/res/values/dimens.xml b/example-simple/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/example-simple/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/example-simple/src/main/res/values/strings.xml b/example-simple/src/main/res/values/strings.xml
new file mode 100644
index 0000000..66a212e
--- /dev/null
+++ b/example-simple/src/main/res/values/strings.xml
@@ -0,0 +1,9 @@
+
+
+
+ Easylauncher simple example
+ Hello, world!
+ Settings
+ button
+
+
diff --git a/example-simple/src/main/res/values/styles.xml b/example-simple/src/main/res/values/styles.xml
new file mode 100644
index 0000000..e0c5aae
--- /dev/null
+++ b/example-simple/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/gradle.properties.sample b/gradle.properties.sample
new file mode 100644
index 0000000..aea7ef9
--- /dev/null
+++ b/gradle.properties.sample
@@ -0,0 +1,4 @@
+# configuration for bintray and signing
+
+bintrayUser=YOUR_USERNAME
+bintrayKey=YOUR_KEY
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..456ff30
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Dec 30 13:15:02 GMT 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/icons/blueRibbon.png b/icons/blueRibbon.png
new file mode 100644
index 0000000..8a0b58e
Binary files /dev/null and b/icons/blueRibbon.png differ
diff --git a/icons/customColorRibbon.png b/icons/customColorRibbon.png
new file mode 100644
index 0000000..707a184
Binary files /dev/null and b/icons/customColorRibbon.png differ
diff --git a/icons/grayRibbon.png b/icons/grayRibbon.png
new file mode 100644
index 0000000..e0890a6
Binary files /dev/null and b/icons/grayRibbon.png differ
diff --git a/icons/grayscale.png b/icons/grayscale.png
new file mode 100644
index 0000000..57c1449
Binary files /dev/null and b/icons/grayscale.png differ
diff --git a/icons/greenRibbon.png b/icons/greenRibbon.png
new file mode 100644
index 0000000..e3b9056
Binary files /dev/null and b/icons/greenRibbon.png differ
diff --git a/icons/ic_launcher_beta.png b/icons/ic_launcher_beta.png
new file mode 100644
index 0000000..3846a57
Binary files /dev/null and b/icons/ic_launcher_beta.png differ
diff --git a/icons/ic_launcher_debug.png b/icons/ic_launcher_debug.png
new file mode 100644
index 0000000..e3b9056
Binary files /dev/null and b/icons/ic_launcher_debug.png differ
diff --git a/icons/ic_launcher_qa.png b/icons/ic_launcher_qa.png
new file mode 100644
index 0000000..b35f7e7
Binary files /dev/null and b/icons/ic_launcher_qa.png differ
diff --git a/icons/orangeRibbon.png b/icons/orangeRibbon.png
new file mode 100644
index 0000000..01e77f8
Binary files /dev/null and b/icons/orangeRibbon.png differ
diff --git a/icons/overlay.png b/icons/overlay.png
new file mode 100644
index 0000000..3846a57
Binary files /dev/null and b/icons/overlay.png differ
diff --git a/icons/redRibbon.png b/icons/redRibbon.png
new file mode 100644
index 0000000..1e8854c
Binary files /dev/null and b/icons/redRibbon.png differ
diff --git a/icons/yellowRibbon.png b/icons/yellowRibbon.png
new file mode 100644
index 0000000..5d34453
Binary files /dev/null and b/icons/yellowRibbon.png differ
diff --git a/metadata.gradle b/metadata.gradle
new file mode 100644
index 0000000..e2be84f
--- /dev/null
+++ b/metadata.gradle
@@ -0,0 +1,9 @@
+ext {
+ metadata = [
+ userOrg : 'akaita',
+ groupId : 'com.akaita.android',
+ website : 'https://github.com/akaita/easylauncher',
+ repository: 'https://github.com/akaita/easylauncher.git',
+ licences : ['Apache-2.0']
+ ]
+}
diff --git a/plugin/build.gradle b/plugin/build.gradle
new file mode 100644
index 0000000..d3cf915
--- /dev/null
+++ b/plugin/build.gradle
@@ -0,0 +1,45 @@
+repositories {
+ jcenter()
+}
+
+apply plugin: 'groovy'
+
+
+sourceCompatibility = JavaVersion.VERSION_1_8
+targetCompatibility = JavaVersion.VERSION_1_8
+
+dependencies {
+ compile gradleApi()
+ compile localGroovy()
+ compile 'com.android.tools.build:gradle:2.3.3'
+
+ testCompile('org.spockframework:spock-core:1.0-groovy-2.4') {
+ exclude group:'org.codehaus.groovy'
+ }
+}
+
+if (rootProject.hasProperty('versionName') && rootProject.hasProperty('bintrayUser')) {
+ jar {
+ manifest {
+ attributes(
+ "Implementation-Version": project.file("../VERSION").text.trim(),
+ )
+ }
+ }
+
+ apply plugin: 'com.novoda.bintray-release'
+
+ publish {
+ artifactId = 'easylauncher'
+ repoName = 'android'
+ desc = 'Gradle plugin to modify Android launcher icons'
+
+ def metadata = rootProject.ext.metadata
+ userOrg = metadata.userOrg
+ groupId = metadata.groupId
+ publishVersion = metadata.version
+ website = metadata.website
+ repository = metadata.repository
+ licences = metadata.licences
+ }
+}
diff --git a/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/ColorRibbonFilter.java b/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/ColorRibbonFilter.java
new file mode 100644
index 0000000..35cafa3
--- /dev/null
+++ b/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/ColorRibbonFilter.java
@@ -0,0 +1,123 @@
+package com.akaita.android.easylauncher.filter;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.font.FontRenderContext;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+
+public class ColorRibbonFilter implements EasyLauncherFilter {
+
+ static final boolean debug = Boolean.parseBoolean(System.getenv("EASYLAUNCHER_DEBUG"));
+
+ final Color ribbonColor;
+
+ final Color labelColor;
+
+ String label;
+
+ String fontName = "Default";
+
+ int fontStyle = Font.PLAIN;
+
+ boolean largeRibbon = false;
+
+ public ColorRibbonFilter(String label, Color ribbonColor, Color labelColor) {
+ this.label = label;
+ this.ribbonColor = ribbonColor;
+ this.labelColor = labelColor;
+ }
+
+ public ColorRibbonFilter(String label, Color ribbonColor) {
+ this(label, ribbonColor, Color.WHITE);
+ }
+
+ private static int calculateMaxLabelWidth(int y) {
+ return (int) Math.sqrt(Math.pow(y, 2) * 2);
+ }
+
+ private static void drawString(Graphics2D g, String str, int x, int y) {
+ g.drawString(str, x, y);
+
+ if (debug) {
+ FontMetrics fm = g.getFontMetrics();
+ Rectangle2D bounds = g.getFont().getStringBounds(str,
+ new FontRenderContext(g.getTransform(), true, true));
+
+ g.drawRect(x, y - fm.getAscent(), (int) bounds.getWidth(), fm.getAscent());
+ }
+ }
+
+ @Override
+ public void setAdaptiveLauncherMode(boolean enable) {
+ largeRibbon = enable;
+ }
+
+ @Override
+ public void apply(BufferedImage image) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ Graphics2D g = (Graphics2D) image.getGraphics();
+
+ g.setTransform(AffineTransform.getRotateInstance(Math.toRadians(-45)));
+
+ int y = height / (largeRibbon ? 2 : 4);
+
+ // calculate the rectangle where the label is rendered
+ FontRenderContext frc = new FontRenderContext(g.getTransform(), true, true);
+ int maxLabelWidth = calculateMaxLabelWidth(y);
+ g.setFont(getFont(maxLabelWidth, frc));
+ Rectangle2D labelBounds = g.getFont().getStringBounds(label == null ? "" : label, frc);
+
+ // draw the ribbon
+ g.setColor(ribbonColor);
+ g.fillRect(-width, y, width * 2, (int) (labelBounds.getHeight()));
+
+ if (label != null) {
+ // draw the label
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setColor(labelColor);
+
+ FontMetrics fm = g.getFontMetrics();
+
+ drawString(g, label,
+ (int) -labelBounds.getWidth() / 2,
+ y + fm.getAscent());
+ }
+ g.dispose();
+ }
+
+ private Font getFont(int maxLabelWidth, FontRenderContext frc) {
+ int max = largeRibbon ? 64 : 32;
+ if (label == null) {
+ return new Font(fontName, fontStyle, max / 2);
+ }
+ int min = 0;
+ int x = max;
+
+ for (int i = 0; i < 10; i++) {
+ int m = ((max + min) / 2);
+ if (m == x) {
+ break;
+ }
+
+ Font font = new Font(fontName, fontStyle, m);
+ Rectangle2D labelBounds = font.getStringBounds(label, frc);
+ int px = (int) labelBounds.getWidth();
+
+ if (px > maxLabelWidth) {
+ max = m;
+ } else {
+ min = m;
+ }
+ x = m;
+ }
+ return new Font(fontName, fontStyle, x);
+ }
+}
diff --git a/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/EasyLauncherFilter.java b/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/EasyLauncherFilter.java
new file mode 100644
index 0000000..374805d
--- /dev/null
+++ b/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/EasyLauncherFilter.java
@@ -0,0 +1,12 @@
+package com.akaita.android.easylauncher.filter;
+
+import java.awt.image.BufferedImage;
+
+/**
+ * Created by mikel on 05/01/2018.
+ */
+
+public interface EasyLauncherFilter {
+ void setAdaptiveLauncherMode(boolean enable);
+ void apply(BufferedImage image);
+}
diff --git a/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/GrayscaleFilter.java b/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/GrayscaleFilter.java
new file mode 100644
index 0000000..f3ed5aa
--- /dev/null
+++ b/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/GrayscaleFilter.java
@@ -0,0 +1,35 @@
+package com.akaita.android.easylauncher.filter;
+
+import java.awt.image.BufferedImage;
+
+public class GrayscaleFilter implements EasyLauncherFilter {
+
+ private static int toGray(int color) {
+ int a = (color & 0xFF000000);
+ int r = (color & 0x00FF0000) >> 16;
+ int g = (color & 0x0000FF00) >> 8;
+ int b = (color & 0x000000FF);
+
+ int c = (int) ((2.0 * r + 4.0 * g + b) / 7.0);
+ return a | (c << 16) | (c << 8) | c;
+ }
+
+ @Override
+ public void setAdaptiveLauncherMode(boolean enable) {
+ // Do nothing
+ }
+
+ @Override
+ public void apply(BufferedImage image) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int color = image.getRGB(x, y);
+ image.setRGB(x, y, toGray(color));
+ }
+ }
+ }
+
+}
diff --git a/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/OverlayFilter.java b/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/OverlayFilter.java
new file mode 100644
index 0000000..9a39388
--- /dev/null
+++ b/plugin/src/main/groovy/com/akaita/android/easylauncher/filter/OverlayFilter.java
@@ -0,0 +1,57 @@
+package com.akaita.android.easylauncher.filter;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+public class OverlayFilter implements EasyLauncherFilter {
+
+ private File fgFile;
+
+ private boolean addPadding = false;
+
+ public OverlayFilter(final File fgFile) {
+ this.fgFile = fgFile;
+ }
+
+ @Override
+ public void setAdaptiveLauncherMode(boolean enable) {
+ addPadding = enable;
+ }
+
+ @Override
+ public void apply(BufferedImage image) {
+ Image fgImage = null;
+ try {
+ fgImage = ImageIO.read(fgFile);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (fgImage != null) {
+ int width = image.getWidth();
+ int height = image.getWidth();
+ float scale = Math.min(width/(float)fgImage.getWidth(null), height/(float)fgImage.getHeight(null));
+ if (addPadding)
+ scale = scale * (72f/108);
+ Image fgImageScaled = fgImage.getScaledInstance(
+ (int)(fgImage.getWidth(null) * scale),
+ (int)(fgImage.getWidth(null) * scale),
+ Image.SCALE_SMOOTH);
+
+ Graphics2D g = image.createGraphics();
+
+ //TODO allow to choose the gravity for the overlay
+ //TODO allow to choose the scaling type
+ if (addPadding)
+ g.drawImage(fgImageScaled, (int)(width * (1 - 72f/108) / 2), (int)(height * (1 - 72f/108) / 2), null);
+ else
+ g.drawImage(fgImageScaled, 0, 0, null);
+ g.dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncher.java b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncher.java
new file mode 100644
index 0000000..1fa9f96
--- /dev/null
+++ b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncher.java
@@ -0,0 +1,36 @@
+package com.akaita.android.easylauncher.plugin;
+
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.stream.Stream;
+
+import com.akaita.android.easylauncher.filter.EasyLauncherFilter;
+
+import javax.imageio.ImageIO;
+
+public class EasyLauncher {
+
+ private final File inputFile;
+
+ private final File outputFile;
+
+ private final BufferedImage image;
+
+ public EasyLauncher(File inputFile, File outputFile) throws IOException {
+ this.inputFile = inputFile;
+ this.outputFile = outputFile;
+
+ image = ImageIO.read(inputFile);
+ }
+
+ public void save() throws IOException {
+ outputFile.getParentFile().mkdirs();
+ ImageIO.write(image, "png", outputFile);
+ }
+
+ public void process(Stream filters) {
+ filters.forEach(filter -> filter.apply(image));
+ }
+}
\ No newline at end of file
diff --git a/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherConfig.java b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherConfig.java
new file mode 100644
index 0000000..288684d
--- /dev/null
+++ b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherConfig.java
@@ -0,0 +1,107 @@
+package com.akaita.android.easylauncher.plugin;
+
+import com.akaita.android.easylauncher.filter.ColorRibbonFilter;
+import com.akaita.android.easylauncher.filter.EasyLauncherFilter;
+import com.akaita.android.easylauncher.filter.GrayscaleFilter;
+import com.akaita.android.easylauncher.filter.OverlayFilter;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import java.awt.Color;
+import java.io.File;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Created by mikel on 02/01/2018.
+ */
+
+public class EasyLauncherConfig implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Nullable
+ private Boolean mEnabled = true;
+ private List mFilters = Lists.newArrayList();
+
+
+ public String name;
+ public EasyLauncherConfig(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * @see #getEnabled()
+ */
+ @NonNull
+ public EasyLauncherConfig enable(@Nullable Boolean enabled) {
+ mEnabled = enabled;
+ return this;
+ }
+
+ public EasyLauncherConfig setFilters(Iterable filters) {
+ filters(Iterables.toArray(filters, EasyLauncherFilter.class));
+ return this;
+ }
+
+ public EasyLauncherConfig setFilters(EasyLauncherFilter filter) {
+ mFilters.add(filter);
+ return this;
+ }
+
+ private void filters(EasyLauncherFilter... filters) {
+ mFilters.addAll(Arrays.asList(filters));
+ }
+
+ @Nullable
+ public List getFilters() {
+ return mFilters;
+ }
+
+ @Nullable
+ public Boolean getEnabled() {
+ return mEnabled;
+ }
+
+
+ //region Filters
+ public ColorRibbonFilter customColorRibbonFilter(String nm) {
+ return new ColorRibbonFilter(name, Color.decode(nm));
+ }
+
+ public ColorRibbonFilter grayRibbonFilter() {
+ return new ColorRibbonFilter(name, new Color(0x60, 0x60, 0x60, 0x99));
+ }
+
+ public ColorRibbonFilter greenRibbonFilter() {
+ return new ColorRibbonFilter(name, new Color(0, 0x72, 0, 0x99));
+ }
+
+ public ColorRibbonFilter orangeRibbonFilter() {
+ return new ColorRibbonFilter(name, new Color(0xff, 0x76, 0, 0x99));
+ }
+
+ public ColorRibbonFilter yellowRibbonFilter() {
+ return new ColorRibbonFilter(name, new Color(0xff, 251, 0, 0x99));
+ }
+
+ public ColorRibbonFilter redRibbonFilter() {
+ return new ColorRibbonFilter(name, new Color(0xff, 0, 0, 0x99));
+ }
+
+ public ColorRibbonFilter blueRibbonFilter() {
+ return new ColorRibbonFilter(name, new Color(0, 0, 255, 0x99));
+ }
+
+ public OverlayFilter overlayFilter(File fgFile) {
+ return new OverlayFilter(fgFile);
+ }
+
+ public GrayscaleFilter grayscaleFilter() {
+ return new GrayscaleFilter();
+ }
+
+ //endregion
+}
diff --git a/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherExtension.java b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherExtension.java
new file mode 100644
index 0000000..2ad3f11
--- /dev/null
+++ b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherExtension.java
@@ -0,0 +1,82 @@
+package com.akaita.android.easylauncher.plugin;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+class EasyLauncherExtension {
+
+ public static String NAME = "easylauncher";
+
+ Set iconNames = new HashSet<>();
+ Set foregroundIconNames = new HashSet<>();
+
+ public EasyLauncherExtension() {
+ }
+
+ public Set getIconNames() {
+ return iconNames;
+ }
+
+ /**
+ * @param resNames Names of icons. For example "@drawable/ic_launcher", "@mipmap/icon"
+ */
+ public void setIconNames(Collection resNames) {
+ iconNames = new HashSet<>(resNames);
+ }
+
+ /**
+ * @param resNames Names of icons. For example "@drawable/ic_launcher", "@mipmap/icon"
+ */
+ public void iconNames(Collection resNames) {
+ setIconNames(resNames);
+ }
+
+ /**
+ * @param resNames Names of icons. For example "@drawable/ic_launcher", "@mipmap/icon"
+ */
+ public void iconNames(String... resNames) {
+ setIconNames(Arrays.asList(resNames));
+ }
+
+ /**
+ * @param resName A name of icons. For example "@drawable/ic_launcher", "@mipmap/icon"
+ */
+ public void iconName(String resName) {
+ iconNames.add(resName);
+ }
+
+ public Set getForegroundIconNames() {
+ return foregroundIconNames;
+ }
+
+ /**
+ * @param resNames Names of icons. For example "@drawable/ic_launcher", "@mipmap/icon"
+ */
+ public void setForegroundIconNames(Collection resNames) {
+ foregroundIconNames = new HashSet<>(resNames);
+ }
+
+ /**
+ * @param resNames Names of icons. For example "@drawable/ic_launcher", "@mipmap/icon"
+ */
+ public void foregroundIconNames(Collection resNames) {
+ setForegroundIconNames(resNames);
+ }
+
+ /**
+ * @param resNames Names of icons. For example "@drawable/ic_launcher", "@mipmap/icon"
+ */
+ public void foregroundIconNames(String... resNames) {
+ setForegroundIconNames(Arrays.asList(resNames));
+ }
+
+ /**
+ * @param resName A name of icons. For example "@drawable/ic_launcher", "@mipmap/icon"
+ */
+ public void foregroundIconName(String resName) {
+ foregroundIconNames.add(resName);
+ }
+
+}
diff --git a/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherPlugin.groovy b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherPlugin.groovy
new file mode 100644
index 0000000..f6c2b11
--- /dev/null
+++ b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherPlugin.groovy
@@ -0,0 +1,132 @@
+package com.akaita.android.easylauncher.plugin
+
+import com.android.build.gradle.AppExtension
+import com.android.build.gradle.api.ApplicationVariant
+import com.akaita.android.easylauncher.filter.EasyLauncherFilter
+import com.google.common.collect.Lists
+import groovy.transform.CompileStatic
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+
+// see http://www.gradle.org/docs/current/userguide/custom_plugins.html
+
+@CompileStatic
+public class EasyLauncherPlugin implements Plugin {
+
+ static {
+ System.setProperty("java.awt.headless", "true")
+
+ // workaround for an Android Studio issue
+ try {
+ Class.forName(System.getProperty("java.awt.graphicsenv"))
+ } catch (ClassNotFoundException e) {
+ System.err.println("[WARN] java.awt.graphicsenv: " + e)
+ System.setProperty("java.awt.graphicsenv", "sun.awt.CGraphicsEnvironment")
+ }
+ try {
+ Class.forName(System.getProperty("awt.toolkit"))
+ } catch (ClassNotFoundException e) {
+ System.err.println("[WARN] awt.toolkit: " + e)
+ System.setProperty("awt.toolkit", "sun.lwawt.macosx.LWCToolkit")
+ }
+ }
+
+ @Override
+ void apply(Project project) {
+ project.extensions.add(EasyLauncherExtension.NAME, EasyLauncherExtension)
+
+ NamedDomainObjectContainer ribbonVariants =
+ project.container(EasyLauncherConfig)
+ project.extensions.add('variants', ribbonVariants)
+ NamedDomainObjectContainer ribbonBuildTypes =
+ project.container(EasyLauncherConfig)
+ project.extensions.add('buildTypes', ribbonBuildTypes)
+ NamedDomainObjectContainer ribbonProductFlavors =
+ project.container(EasyLauncherConfig)
+ project.extensions.add('productFlavors', ribbonProductFlavors)
+
+ project.afterEvaluate {
+ def android = project.extensions.findByType(AppExtension)
+ if (!android) {
+ throw new Exception(
+ "Not an Android application; did you forget `apply plugin: 'com.android.application`?")
+ }
+ def extension = project.extensions.findByType(EasyLauncherExtension)
+
+
+ def tasks = new ArrayList()
+
+ android.applicationVariants.all { ApplicationVariant variant ->
+
+ List configs = Lists.newArrayList()
+ ribbonVariants.each{
+ if (variant.name == it.name) {
+ configs.add(it)
+ }
+ }
+
+ if (configs.empty) {
+ ribbonProductFlavors.each {
+ if (variant.flavorName == it.name) {
+ configs.add(it)
+ }
+ }
+ ribbonBuildTypes.each {
+ if (variant.buildType.name == it.name) {
+ configs.add(it)
+ }
+ }
+ }
+
+ def enabled = true
+ configs.each {
+ enabled = enabled && it.getEnabled()
+ }
+
+ if (enabled) {
+ List filters = Lists.newArrayList()
+ configs.each {
+ filters.addAll(it.getFilters())
+ }
+
+ //set default ribbon
+ if (filters.empty && variant.buildType.debuggable) {
+ filters.add(new EasyLauncherConfig(variant.buildType.name).greenRibbonFilter())
+ }
+
+ def generatedResDir = getGeneratedResDir(project, variant)
+ android.sourceSets.findByName(variant.name).res.srcDir(generatedResDir)
+
+ def name = "${EasyLauncherTask.NAME}${capitalize(variant.name)}"
+ def task = project.task(name, type: EasyLauncherTask) as EasyLauncherTask
+ task.variant = variant
+ task.outputDir = generatedResDir
+ task.iconNames = new HashSet(extension.iconNames)
+ task.foregroundIconNames = new HashSet(extension.foregroundIconNames)
+ task.filters = filters
+ tasks.add(task)
+
+ def generateResources = project.
+ getTasksByName("generate${capitalize(variant.name)}Resources", false)
+ generateResources.forEach { Task t ->
+ t.dependsOn(task)
+ }
+ }
+ }
+
+ project.task(EasyLauncherTask.NAME, dependsOn: tasks);
+ }
+ }
+
+ static String capitalize(String str) {
+ return str.substring(0, 1).toUpperCase() + str.substring(1)
+ }
+
+ static File getGeneratedResDir(Project project, ApplicationVariant variant) {
+ return new File(project.buildDir,
+ "generated/easylauncher/res/${variant.name}")
+ }
+
+}
diff --git a/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherTask.groovy b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherTask.groovy
new file mode 100644
index 0000000..5767ba4
--- /dev/null
+++ b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/EasyLauncherTask.groovy
@@ -0,0 +1,120 @@
+package com.akaita.android.easylauncher.plugin
+
+import com.android.build.gradle.AppExtension
+import com.android.build.gradle.api.ApplicationVariant
+import com.android.builder.model.SourceProvider
+import com.akaita.android.easylauncher.filter.EasyLauncherFilter
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+
+import java.util.function.Function
+import java.util.stream.Stream
+
+class EasyLauncherTask extends DefaultTask {
+
+ static final String NAME = "easylauncher"
+
+ ApplicationVariant variant
+
+ //@OutputDirectory
+ File outputDir
+
+ // `iconNames` includes: "@drawable/icon", "@mipmap/ic_launcher", etc.
+ Set iconNames
+ Set foregroundIconNames
+
+ List filters = []
+
+ @TaskAction
+ public void run() {
+ if (filters.empty) {
+ return
+ }
+
+ def t0 = System.currentTimeMillis()
+
+ def names = new HashSet(iconNames)
+ names.addAll(launcherIconNames)
+
+ def foregroundNames = new HashSet(foregroundIconNames)
+
+ variant.sourceSets
+ .stream()
+ .flatMap(new Function() {
+ @Override
+ Stream apply(SourceProvider sourceProvider) {
+ return sourceProvider.resDirectories.stream()
+ }})
+ .forEach { File resDir ->
+ if (resDir == outputDir) {
+ return
+ }
+
+ names.forEach { String name ->
+ project.fileTree(
+ dir: resDir,
+ include: Resources.resourceFilePattern(name),
+ exclude: "**/*.xml",
+ ).forEach { File inputFile ->
+ info "process $inputFile"
+
+ def basename = inputFile.name
+ def resType = inputFile.parentFile.name
+ def outputFile = new File(outputDir, "${resType}/${basename}")
+ outputFile.parentFile.mkdirs()
+
+ def easyLauncher = new EasyLauncher(inputFile, outputFile)
+ easyLauncher.process(filters.stream())
+ easyLauncher.save()
+ }
+ }
+ foregroundNames.forEach { String name ->
+ project.fileTree(
+ dir: resDir,
+ include: Resources.resourceFilePattern(name),
+ exclude: "**/*.xml",
+ ).forEach { File inputFile ->
+ info "process $inputFile"
+
+ def basename = inputFile.name
+ def resType = inputFile.parentFile.name
+ def outputFile = new File(outputDir, "${resType}/${basename}")
+ outputFile.parentFile.mkdirs()
+
+ def largeRibbonFilters = filters.collect {
+ it.setAdaptiveLauncherMode(true)
+ it
+ }
+
+ def easyLauncher = new EasyLauncher(inputFile, outputFile)
+ easyLauncher.process(largeRibbonFilters.stream())
+ easyLauncher.save()
+ }
+ }
+ }
+
+ info("task finished in ${System.currentTimeMillis() - t0}ms")
+ }
+
+ void info(String message) {
+ project.logger.info("[$name] $message")
+ }
+
+ Set getLauncherIconNames() {
+ def names = new HashSet()
+ androidManifestFiles.forEach { File manifestFile ->
+ names.addAll(Resources.getLauncherIcons(manifestFile))
+ }
+ return names
+ }
+
+ Stream getAndroidManifestFiles() {
+ AppExtension android = project.extensions.findByType(AppExtension)
+
+ return ["main", variant.name, variant.buildType.name, variant.flavorName].stream()
+ .filter({ name -> !name.empty })
+ .distinct()
+ .map({ name -> project.file(android.sourceSets[name].manifest.srcFile) })
+ .filter({ manifestFile -> manifestFile.exists() })
+ }
+}
\ No newline at end of file
diff --git a/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/Resources.java b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/Resources.java
new file mode 100644
index 0000000..e061818
--- /dev/null
+++ b/plugin/src/main/groovy/com/akaita/android/easylauncher/plugin/Resources.java
@@ -0,0 +1,54 @@
+package com.akaita.android.easylauncher.plugin;
+
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import groovy.util.XmlSlurper;
+import groovy.util.slurpersupport.GPathResult;
+
+import static java.util.Collections.unmodifiableList;
+
+public class Resources {
+
+ public static String resourceFilePattern(String name) {
+ if (name.startsWith("@")) {
+ String[] pair = name.substring(1).split("/", 2);
+ String baseResType = pair[0];
+ String fileName = pair[1];
+ if (fileName == null) {
+ throw new IllegalArgumentException(
+ "Icon names does include resource types (e.g. drawable/ic_launcher): "
+ + name);
+ }
+ return baseResType + "*/" + fileName + ".*";
+ } else {
+ return name;
+ }
+ }
+
+ public static List getLauncherIcons(File manifestFile)
+ throws SAXException, ParserConfigurationException, IOException {
+ GPathResult manifestXml = new XmlSlurper().parse(manifestFile);
+ GPathResult applicationNode = (GPathResult) manifestXml.getProperty("application");
+
+ String icon = String.valueOf(applicationNode.getProperty("@android:icon"));
+ String roundIcon = String.valueOf(applicationNode.getProperty("@android:roundIcon"));
+
+ List icons = new ArrayList<>(2);
+ if (!icon.isEmpty()) {
+ icons.add(icon);
+ }
+ if (!roundIcon.isEmpty()) {
+ icons.add(roundIcon);
+ }
+
+ return unmodifiableList(icons);
+
+ }
+}
diff --git a/plugin/src/main/resources/META-INF/gradle-plugins/com.akaita.android.easylauncher.properties b/plugin/src/main/resources/META-INF/gradle-plugins/com.akaita.android.easylauncher.properties
new file mode 100644
index 0000000..7983dbb
--- /dev/null
+++ b/plugin/src/main/resources/META-INF/gradle-plugins/com.akaita.android.easylauncher.properties
@@ -0,0 +1 @@
+implementation-class=com.akaita.android.easylauncher.plugin.EasyLauncherPlugin
\ No newline at end of file
diff --git a/plugin/src/test/groovy/com/akaita/android/easylauncher/plugin/ResourcesTest.groovy b/plugin/src/test/groovy/com/akaita/android/easylauncher/plugin/ResourcesTest.groovy
new file mode 100644
index 0000000..13cf611
--- /dev/null
+++ b/plugin/src/test/groovy/com/akaita/android/easylauncher/plugin/ResourcesTest.groovy
@@ -0,0 +1,95 @@
+package com.akaita.android.easylauncher.plugin
+
+import com.akaita.android.easylauncher.plugin.Resources
+import spock.lang.Specification
+
+public class ResourcesTest extends Specification {
+ def "resourceFilePattern"() {
+ expect:
+ Resources.resourceFilePattern(resName) == pattern
+
+ where:
+ resName | pattern
+ "@drawable/ic_launcher" | "drawable*/ic_launcher.*"
+ "@mipmap/icon" | "mipmap*/icon.*"
+ }
+
+ def "getLauncherIcon without android:roundIcon"() {
+ setup:
+ def file = File.createTempFile("AndroidManifest", ".xml")
+ file.deleteOnExit()
+ file.write('''
+
+
+
+
+
+
+'''.trim())
+
+ expect:
+ Resources.getLauncherIcons(file).containsAll(["@drawable/ic_launcher"])
+ }
+
+ def "getLauncherIcon without android:icon"() {
+ setup:
+ def file = File.createTempFile("AndroidManifest", ".xml")
+ file.deleteOnExit()
+ file.write('''
+
+
+
+
+
+
+'''.trim())
+
+ expect:
+ Resources.getLauncherIcons(file).containsAll(["@drawable/ic_launcher_round"])
+ }
+
+ def "getLauncherIcon with both android:icon and android:roundIcon"() {
+ setup:
+ def file = File.createTempFile("AndroidManifest", ".xml")
+ file.deleteOnExit()
+ file.write('''
+
+
+
+
+
+
+'''.trim())
+
+ expect:
+ Resources.getLauncherIcons(file).containsAll(["@drawable/ic_launcher", "@drawable/ic_launcher_round"])
+ }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..7bacb3b
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,3 @@
+include ':plugin'
+include ':example-simple'
+include ':example-custom'
diff --git a/versioning.gradle b/versioning.gradle
new file mode 100644
index 0000000..49846cb
--- /dev/null
+++ b/versioning.gradle
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2015 FUJI Goro (gfx).
+ *
+ * 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.
+ */
+
+import java.util.regex.Pattern
+
+static int parseVersionName(String versionName) {
+ final versionParts = versionName.split(/\D/)
+ final major = versionParts[0].toInteger()
+ final minor = versionParts[1].toInteger()
+ final patchLevel = versionParts[2].toInteger()
+
+ return major * (1000 * 1000) + minor * 1000 + patchLevel;
+}
+
+void updateReadme(String oldVersion, String newVersion) {
+ def template = rootProject.file('README.md').text
+ def result = template.replaceAll(Pattern.quote(oldVersion), newVersion)
+ rootProject.file("README.md").withWriter { it << result }
+}
+
+rootProject.ext {
+ versionFile = rootProject.file("VERSION")
+ versionName = versionFile.readLines()[0].trim()
+ versionCode = parseVersionName(versionName)
+}
+
+
+task bumpMajor {
+ doLast {
+ def oldVersion = rootProject.ext.versionName
+ def versionParts = oldVersion.split(/\./)
+ versionParts[0] = (versionParts[0] as int) + 1
+ def newVersion = "${versionParts[0]}.0.0"
+ versionFile.write(newVersion + "\n")
+ updateReadme(oldVersion, newVersion)
+ rootProject.ext.versionName = newVersion
+ tasks.version.execute()
+ }
+}
+
+task bumpMinor {
+ doLast {
+ def oldVersion = rootProject.ext.versionName
+ def versionParts = oldVersion.split(/\./)
+ versionParts[1] = (versionParts[1] as int) + 1
+ def newVersion = "${versionParts[0]}.${versionParts[1]}.0"
+ versionFile.write(newVersion + "\n")
+ updateReadme(oldVersion, newVersion)
+ rootProject.ext.versionName = newVersion
+ tasks.version.execute()
+ }
+}
+
+task bumpPatch {
+ doLast {
+ def oldVersion = rootProject.ext.versionName
+ def versionParts = oldVersion.split(/\./)
+ versionParts[2] = (versionParts[2].split(/\D/)[0] as int) + 1
+ def newVersion = "${versionParts[0]}.${versionParts[1]}.${versionParts[2]}"
+ versionFile.write(newVersion + "\n")
+ updateReadme(oldVersion, newVersion)
+ rootProject.ext.versionName = newVersion
+ tasks.version.execute()
+ }
+}
+
+task version {
+ doLast {
+ println "v" + rootProject.ext.versionName
+ }
+}
+
+String shell(String command) {
+ def proc = ["sh", "-c", "cd ${rootProject.rootDir} ; ${command}"].execute()
+ if (proc.waitFor() != 0) {
+ throw new RuntimeException("Failed to run: ${command}\n${proc.err.text}")
+ } else {
+ def err = proc.err.text
+ if (err) {
+ System.err.println(err)
+ }
+ }
+ return proc.in.text;
+}
+
+task releng {
+ doLast {
+ def currentBranch = shell("git symbolic-ref --short HEAD")
+ def tag = "v" + rootProject.ext.versionName
+ println "Release engineering for ${tag} (branch=${currentBranch})"
+
+ def changes = shell("git status -s")
+ if (changes.trim()) {
+ throw new RuntimeException("There are changes not commited yet.\n${changes}")
+ }
+
+ println "> git tag ${tag}"
+ shell "git tag ${tag}"
+
+ println "> git push origin ${tag}"
+ shell "git push origin ${tag}"
+
+ println "> git push origin ${tag}"
+ shell "git push origin ${currentBranch}"
+ }
+}