diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0bd19b0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+*.iml
+.gradle
+/local.properties
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+
+/.idea/*
+!/.idea/runConfigurations/
\ No newline at end of file
diff --git a/.idea/runConfigurations/dispatcher_test.xml b/.idea/runConfigurations/dispatcher_test.xml
new file mode 100644
index 0000000..fa88a12
--- /dev/null
+++ b/.idea/runConfigurations/dispatcher_test.xml
@@ -0,0 +1,14 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="dispatcher-test" type="AndroidJUnit" factoryName="Android JUnit">
+    <module name="google-analytics-dispatcher" />
+    <option name="PACKAGE_NAME" value="com.batch.android.dispatcher.googleanalytics" />
+    <option name="MAIN_CLASS_NAME" value="" />
+    <option name="METHOD_NAME" value="" />
+    <option name="TEST_OBJECT" value="package" />
+    <option name="PARAMETERS" value="" />
+    <option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
+    <method v="2">
+      <option name="Android.Gradle.BeforeRunTask" enabled="true" />
+    </method>
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..0696b04
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,29 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+        
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.5.1'
+        
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        flatDir {
+            dirs 'libs'
+        }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/google-analytics-dispatcher/.gitignore b/google-analytics-dispatcher/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/google-analytics-dispatcher/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/google-analytics-dispatcher/build.gradle b/google-analytics-dispatcher/build.gradle
new file mode 100644
index 0000000..80db5f5
--- /dev/null
+++ b/google-analytics-dispatcher/build.gradle
@@ -0,0 +1,59 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 29
+    buildToolsVersion "29.0.2"
+
+    defaultConfig {
+        minSdkVersion 15
+        targetSdkVersion 29
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles 'consumer-rules.pro'
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    flavorDimensions "target-backend"
+
+    productFlavors {
+        production {
+            dimension "target-backend"
+        }
+
+        staging {
+            dimension "target-backend"
+        }
+    }
+
+    testOptions {
+        unitTests {
+            includeAndroidResources = true
+        }
+    }
+
+}
+
+dependencies {
+    api(name:'Batch', ext:'aar')
+    //api 'com.batch.android:batch-sdk:[1.15,)' TODO remove aar and use maven repo after 1.15 release
+    api 'androidx.annotation:annotation:1.1.0'
+    api 'com.google.android.gms:play-services-analytics:17.0.0'
+
+    testImplementation 'junit:junit:4.12'
+    testImplementation 'androidx.test.ext:junit:1.1.1'
+    testImplementation 'org.mockito:mockito-core:2.25.1'
+    testImplementation 'org.robolectric:robolectric:4.3'
+    testImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+
+    // We use PowerMock to mock GoogleAnalytics in tests
+    testImplementation 'org.powermock:powermock-module-junit4:2.0.2'
+    testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.2'
+    testImplementation 'org.powermock:powermock-api-mockito2:2.0.2'
+    testImplementation 'org.powermock:powermock-classloading-xstream:2.0.2'
+}
diff --git a/google-analytics-dispatcher/consumer-rules.pro b/google-analytics-dispatcher/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/google-analytics-dispatcher/libs/Batch.aar b/google-analytics-dispatcher/libs/Batch.aar
new file mode 100644
index 0000000..3aac0f5
Binary files /dev/null and b/google-analytics-dispatcher/libs/Batch.aar differ
diff --git a/google-analytics-dispatcher/proguard-rules.pro b/google-analytics-dispatcher/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/google-analytics-dispatcher/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/google-analytics-dispatcher/src/main/AndroidManifest.xml b/google-analytics-dispatcher/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a4ca039
--- /dev/null
+++ b/google-analytics-dispatcher/src/main/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.batch.android.dispatcher.googleanalytics">
+
+    <application>
+        <service
+            android:name="com.batch.android.eventdispatcher.DispatcherDiscoveryService"
+            android:exported="false">
+            <meta-data
+                android:name="com.batch.android.eventdispatcher:com.batch.android.dispatcher.googleanalytics.GoogleAnalyticsRegistrar"
+                android:value="com.batch.android.eventdispatcher.DispatcherRegistrar" />
+        </service>
+    </application>
+
+</manifest>
+
diff --git a/google-analytics-dispatcher/src/main/java/com/batch/android/dispatcher/googleanalytics/BatchEventBuilder.java b/google-analytics-dispatcher/src/main/java/com/batch/android/dispatcher/googleanalytics/BatchEventBuilder.java
new file mode 100644
index 0000000..3af38ed
--- /dev/null
+++ b/google-analytics-dispatcher/src/main/java/com/batch/android/dispatcher/googleanalytics/BatchEventBuilder.java
@@ -0,0 +1,43 @@
+package com.batch.android.dispatcher.googleanalytics;
+
+import com.google.android.gms.analytics.HitBuilders;
+
+public class BatchEventBuilder extends HitBuilders.EventBuilder {
+
+    /**
+     * Google Analytics internal UTM tag keys
+     */
+    private static final String SOURCE = "&cs";
+    private static final String MEDIUM = "&cm";
+    private static final String CAMPAIGN = "&cn";
+    private static final String CONTENT = "&cc";
+
+    public BatchEventBuilder setCampaignSource(String source) {
+        if (source != null) {
+            this.set(SOURCE, source);
+        }
+        return this;
+    }
+
+    public BatchEventBuilder setCampaignMedium(String medium) {
+        if (medium != null) {
+            this.set(MEDIUM, medium);
+        }
+        return this;
+    }
+
+    public BatchEventBuilder setCampaignContent(String content) {
+        if (content != null) {
+            this.set(CONTENT, content);
+        }
+        return this;
+    }
+
+    public BatchEventBuilder setCampaignName(String name) {
+        if (name != null) {
+            this.set(CAMPAIGN, name);
+        }
+        return this;
+    }
+
+}
diff --git a/google-analytics-dispatcher/src/main/java/com/batch/android/dispatcher/googleanalytics/GoogleAnalyticsDispatcher.java b/google-analytics-dispatcher/src/main/java/com/batch/android/dispatcher/googleanalytics/GoogleAnalyticsDispatcher.java
new file mode 100644
index 0000000..6d0f624
--- /dev/null
+++ b/google-analytics-dispatcher/src/main/java/com/batch/android/dispatcher/googleanalytics/GoogleAnalyticsDispatcher.java
@@ -0,0 +1,207 @@
+package com.batch.android.dispatcher.googleanalytics;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+
+import com.batch.android.Batch;
+import com.batch.android.BatchEventDispatcher;
+import com.google.android.gms.analytics.GoogleAnalytics;
+import com.google.android.gms.analytics.Tracker;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Google Analytics Event Dispatcher
+ * The dispatcher should generate UTM tag from a Batch payload and send them to the Google Analytics SDK
+ * See : https://ga-dev-tools.appspot.com/campaign-url-builder/
+ * Only works for Google Analytics 360 properties, please migrate to Firebase as soon as possible.
+ * See : https://support.google.com/firebase/answer/9167112
+ */
+public class GoogleAnalyticsDispatcher implements BatchEventDispatcher {
+
+    /**
+     * UTM tag keys
+     */
+    private static final String UTM_CAMPAIGN = "utm_campaign";
+    private static final String UTM_SOURCE = "utm_source";
+    private static final String UTM_MEDIUM = "utm_medium";
+    private static final String UTM_CONTENT = "utm_content";
+
+    /**
+     * Key used to dispatch the Batch tracking Id on Google Analytics
+     */
+    private static final String BATCH_TRACKING_ID = "batch_tracking_id";
+
+    /**
+     * Event name used when logging on Google Analytics
+     */
+    private static final String NOTIFICATION_RECEIVE_NAME = "batch_notification_receive";
+    private static final String NOTIFICATION_OPEN_NAME = "batch_notification_open";
+    private static final String NOTIFICATION_DISMISS_NAME = "batch_notification_dismiss";
+    private static final String IN_APP_SHOW_NAME = "batch_in_app_show";
+    private static final String IN_APP_DISMISS_NAME = "batch_in_app_dismiss";
+    private static final String IN_APP_CLOSE_NAME = "batch_in_app_close";
+    private static final String IN_APP_AUTO_CLOSE_NAME = "batch_in_app_auto_close";
+    private static final String IN_APP_GLOBAL_TAP_NAME = "batch_in_app_global_tap";
+    private static final String IN_APP_CLICK_NAME = "batch_in_app_click";
+    private static final String UNKNOWN_EVENT_NAME = "batch_unknown";
+
+    private GoogleAnalytics googleAnalytics;
+    private Tracker tracker;
+
+    public GoogleAnalyticsDispatcher(Context context) {
+        this.googleAnalytics = GoogleAnalytics.getInstance(context);
+        this.tracker = null;
+    }
+
+    public void setTrackingId(String trackingId) {
+        if (tracker == null) {
+            tracker = googleAnalytics.newTracker(trackingId);
+        }
+    }
+
+    public void setTrackingId(@IdRes int trackingId) {
+        if (tracker == null) {
+            tracker = googleAnalytics.newTracker(trackingId);
+        }
+    }
+
+    @Override
+    public void dispatchEvent(@NonNull Batch.EventDispatcher.Type type, @NonNull Batch.EventDispatcher.Payload payload) {
+        if (tracker == null) {
+            return;
+        }
+
+        BatchEventBuilder builder = new BatchEventBuilder();
+        builder.setLabel("batch");
+        builder.setAction(getGoogleAnalyticsEventName(type));
+
+        if (type.isNotification()) {
+            buildNotificationParams(builder, payload);
+        } else if (type.isInApp()) {
+            buildInAppParams(builder, payload);
+        }
+
+        tracker.send(builder.build());
+    }
+
+    private static void buildInAppParams(BatchEventBuilder builder, Batch.EventDispatcher.Payload payload)
+    {
+        builder.setCategory("in-app");
+        builder.setCampaignName(payload.getTrackingId());
+        builder.setCampaignSource("batch");
+        builder.setCampaignMedium("in-app");
+        builder.set(BATCH_TRACKING_ID, payload.getTrackingId());
+
+        String deeplink = payload.getDeeplink();
+        if (deeplink != null) {
+            deeplink = deeplink.trim();
+            Uri uri = Uri.parse(deeplink);
+
+            String fragment = uri.getFragment();
+            if (fragment != null && !fragment.isEmpty()) {
+                Map<String, String> fragments = getFragmentMap(fragment);
+                // Copy from fragment part of the deeplink
+                builder.setCampaignContent(fragments.get(UTM_CONTENT));
+            }
+            // Copy from query parameters of the deeplink
+            builder.setCampaignContent(uri.getQueryParameter(UTM_CONTENT));
+        }
+
+        // Load from custom payload
+        builder.setCampaignName(payload.getCustomValue(UTM_CAMPAIGN));
+        builder.setCampaignSource(payload.getCustomValue(UTM_SOURCE));
+        builder.setCampaignMedium(payload.getCustomValue(UTM_MEDIUM));
+    }
+
+    private static void buildNotificationParams(BatchEventBuilder builder, Batch.EventDispatcher.Payload payload)
+    {
+        builder.setCategory("push");
+        builder.setCampaignSource("batch");
+        builder.setCampaignMedium("push");
+
+        String deeplink = payload.getDeeplink();
+        if (deeplink != null) {
+            deeplink = deeplink.trim();
+            Uri uri = Uri.parse(deeplink);
+
+            String fragment = uri.getFragment();
+            if (fragment != null && !fragment.isEmpty()) {
+                Map<String, String> fragments = getFragmentMap(fragment);
+                // Copy from fragment part of the deeplink
+                builder.setCampaignContent(fragments.get(UTM_CONTENT));
+                builder.setCampaignMedium(fragments.get(UTM_MEDIUM));
+                builder.setCampaignSource(fragments.get(UTM_SOURCE));
+                builder.setCampaignName(fragments.get(UTM_CAMPAIGN));
+            }
+            // Copy from query parameters of the deeplink
+            builder.setCampaignContent(getQueryParameterCaseInsensitive(uri, UTM_CONTENT));
+            builder.setCampaignMedium(getQueryParameterCaseInsensitive(uri, UTM_MEDIUM));
+            builder.setCampaignSource(getQueryParameterCaseInsensitive(uri, UTM_SOURCE));
+            builder.setCampaignName(getQueryParameterCaseInsensitive(uri, UTM_CAMPAIGN));
+        }
+
+        // Load from custom payload
+        builder.setCampaignName(payload.getCustomValue(UTM_CAMPAIGN));
+        builder.setCampaignSource(payload.getCustomValue(UTM_SOURCE));
+        builder.setCampaignMedium(payload.getCustomValue(UTM_MEDIUM));
+    }
+
+    private static Map<String, String> getFragmentMap(String fragment)
+    {
+        String[] params = fragment.split("&");
+        Map<String, String> map = new HashMap<>();
+        for (String param : params) {
+            String[] parts = param.split("=");
+            if (parts.length >= 2) {
+                map.put(parts[0].toLowerCase(), parts[1]);
+            }
+        }
+        return map;
+    }
+
+    private static String getQueryParameterCaseInsensitive(Uri uri, String keyFrom)
+    {
+        Set<String> keys = uri.getQueryParameterNames();
+        for (String key : keys) {
+            if (keyFrom.equalsIgnoreCase(key)) {
+                return uri.getQueryParameter(key);
+            }
+        }
+        return null;
+    }
+
+
+    private static String getGoogleAnalyticsEventName(Batch.EventDispatcher.Type type) {
+        switch (type) {
+            case NOTIFICATION_RECEIVE:
+                return NOTIFICATION_RECEIVE_NAME;
+            case NOTIFICATION_OPEN:
+                return NOTIFICATION_OPEN_NAME;
+            case NOTIFICATION_DISMISS:
+                return NOTIFICATION_DISMISS_NAME;
+            case IN_APP_SHOW:
+                return IN_APP_SHOW_NAME;
+            case IN_APP_DISMISS:
+                return IN_APP_DISMISS_NAME;
+            case IN_APP_CLOSE:
+                return IN_APP_CLOSE_NAME;
+            case IN_APP_AUTO_CLOSE:
+                return IN_APP_AUTO_CLOSE_NAME;
+            case IN_APP_GLOBAL_TAP:
+                return IN_APP_GLOBAL_TAP_NAME;
+            case IN_APP_CLICK:
+                return IN_APP_CLICK_NAME;
+        }
+        return UNKNOWN_EVENT_NAME;
+    }
+
+}
diff --git a/google-analytics-dispatcher/src/main/java/com/batch/android/dispatcher/googleanalytics/GoogleAnalyticsRegistrar.java b/google-analytics-dispatcher/src/main/java/com/batch/android/dispatcher/googleanalytics/GoogleAnalyticsRegistrar.java
new file mode 100644
index 0000000..69c41e2
--- /dev/null
+++ b/google-analytics-dispatcher/src/main/java/com/batch/android/dispatcher/googleanalytics/GoogleAnalyticsRegistrar.java
@@ -0,0 +1,34 @@
+package com.batch.android.dispatcher.googleanalytics;
+
+import android.content.Context;
+
+import com.batch.android.BatchEventDispatcher;
+import com.batch.android.eventdispatcher.DispatcherRegistrar;
+
+/**
+ * Google Analytics Registrar
+ * The class will instantiate from the SDK using reflection
+ * See the library {@link android.Manifest} for more information
+ */
+public class GoogleAnalyticsRegistrar implements DispatcherRegistrar {
+
+    /**
+     * Singleton instance
+     */
+    private static GoogleAnalyticsDispatcher instance = null;
+
+    /**
+     * Singleton accessor
+     * @param context
+     * @return
+     */
+    @Override
+    public BatchEventDispatcher getDispatcher(Context context)
+    {
+        if (instance == null) {
+            instance = new GoogleAnalyticsDispatcher(context);
+        }
+        return instance;
+    }
+
+}
diff --git a/google-analytics-dispatcher/src/main/res/values/strings.xml b/google-analytics-dispatcher/src/main/res/values/strings.xml
new file mode 100644
index 0000000..da99b73
--- /dev/null
+++ b/google-analytics-dispatcher/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">google-analytics-dispatcher</string>
+</resources>
diff --git a/google-analytics-dispatcher/src/test/java/com/batch/android/dispatcher/googleanalytics/GoogleAnalyticsDispatcherTest.java b/google-analytics-dispatcher/src/test/java/com/batch/android/dispatcher/googleanalytics/GoogleAnalyticsDispatcherTest.java
new file mode 100644
index 0000000..ad26a4e
--- /dev/null
+++ b/google-analytics-dispatcher/src/test/java/com/batch/android/dispatcher/googleanalytics/GoogleAnalyticsDispatcherTest.java
@@ -0,0 +1,497 @@
+package com.batch.android.dispatcher.googleanalytics;
+
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.batch.android.Batch;
+import com.batch.android.BatchMessage;
+import com.batch.android.BatchPushPayload;
+import com.google.android.gms.analytics.GoogleAnalytics;
+import com.google.android.gms.analytics.Tracker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.annotation.Config;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Test the Google Analytics Event Dispatcher implementation
+ * The dispatcher should respect the UTM protocol from Google tools
+ * See : https://ga-dev-tools.appspot.com/campaign-url-builder/
+ */
+@RunWith(AndroidJUnit4.class)
+@Config(sdk = Build.VERSION_CODES.O_MR1)
+@PowerMockIgnore({"org.powermock.*", "org.mockito.*", "org.robolectric.*", "android.*", "androidx.*"})
+@PrepareForTest(GoogleAnalytics.class)
+public class GoogleAnalyticsDispatcherTest {
+
+    @Rule
+    public PowerMockRule rule = new PowerMockRule();
+    private Tracker tracker;
+    private GoogleAnalyticsDispatcher googleAnalyticsDispatcher;
+
+    @Before
+    public void setUp() {
+        Context context = PowerMockito.mock(Context.class);
+        GoogleAnalytics googleAnalytics = PowerMockito.mock(GoogleAnalytics.class);
+        tracker = PowerMockito.mock(Tracker.class);
+
+        PowerMockito.mockStatic(GoogleAnalytics.class);
+        Mockito.when(GoogleAnalytics.getInstance(context)).thenReturn(googleAnalytics);
+        Mockito.when(googleAnalytics.newTracker(Mockito.anyString())).thenReturn(tracker);
+        Mockito.when(googleAnalytics.newTracker(Mockito.anyInt())).thenReturn(tracker);
+
+        googleAnalyticsDispatcher = new GoogleAnalyticsDispatcher(context);
+        googleAnalyticsDispatcher.setTrackingId(0);
+    }
+
+    @Test
+    public void testNotificationNoData() {
+
+        TestEventPayload payload = new TestEventPayload(null,
+                null,
+                new Bundle());
+
+        Map<String, String> expected = new HashMap<String, String>() {{
+            put("&t", "event"); // Type
+            put("&ea", "batch_notification_receive"); // Action
+            put("&ec", "push"); // Category
+            put("&el", "batch"); // Label
+            put("&cs", "batch"); // Campaign Source
+            put("&cm", "push"); // Campaign Medium
+        }};
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.NOTIFICATION_RECEIVE, payload);
+        Mockito.verify(tracker).send(mapEq(expected));
+    }
+
+    @Test
+    public void testNotificationDeeplinkQueryVars() {
+
+        TestEventPayload payload = new TestEventPayload(null,
+                "https://batch.com?utm_source=batchsdk&utm_medium=push-batch&utm_campaign=yoloswag&utm_content=button1",
+                new Bundle());
+
+        Map<String, String> expected = new HashMap<String, String>() {{
+            put("&t", "event"); // Type
+            put("&ea", "batch_notification_receive"); // Action
+            put("&ec", "push"); // Category
+            put("&el", "batch"); // Label
+            put("&cn", "yoloswag"); // Campaign name
+            put("&cs", "batchsdk"); // Campaign Source
+            put("&cm", "push-batch"); // Campaign Medium
+            put("&cc", "button1"); // Campaign Content
+        }};
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.NOTIFICATION_RECEIVE, payload);
+        Mockito.verify(tracker).send(mapEq(expected));
+    }
+
+    @Test
+    public void testNotificationDeeplinkQueryVarsEncode() {
+
+        TestEventPayload payload = new TestEventPayload(null,
+                "https://batch.com?utm_source=%5Bbatchsdk%5D&utm_medium=push-batch&utm_campaign=yoloswag&utm_content=button1",
+                new Bundle());
+
+        Map<String, String> expected = new HashMap<String, String>() {{
+            put("&t", "event"); // Type
+            put("&ea", "batch_notification_receive"); // Action
+            put("&ec", "push"); // Category
+            put("&el", "batch"); // Label
+            put("&cn", "yoloswag"); // Campaign name
+            put("&cs", "[batchsdk]"); // Campaign Source
+            put("&cm", "push-batch"); // Campaign Medium
+            put("&cc", "button1"); // Campaign Content
+        }};
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.NOTIFICATION_RECEIVE, payload);
+        Mockito.verify(tracker).send(mapEq(expected));
+    }
+
+    @Test
+    public void testNotificationDeeplinkFragmentVars() {
+
+        TestEventPayload payload = new TestEventPayload(null,
+                "https://batch.com#utm_source=batch-sdk&utm_medium=pushbatch01&utm_campaign=154879548754&utm_content=notif001",
+                new Bundle());
+
+        Map<String, String> expected = new HashMap<String, String>() {{
+            put("&t", "event"); // Type
+            put("&ea", "batch_notification_open"); // Action
+            put("&ec", "push"); // Category
+            put("&el", "batch"); // Label
+            put("&cn", "154879548754"); // Campaign name
+            put("&cs", "batch-sdk"); // Campaign Source
+            put("&cm", "pushbatch01"); // Campaign Medium
+            put("&cc", "notif001"); // Campaign Content
+        }};
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.NOTIFICATION_OPEN, payload);
+        Mockito.verify(tracker).send(mapEq(expected));
+    }
+
+    @Test
+    public void testNotificationDeeplinkFragmentVarsEncode() {
+
+        TestEventPayload payload = new TestEventPayload(null,
+                "https://batch.com/test#utm_source=%5Bbatch-sdk%5D&utm_medium=pushbatch01&utm_campaign=154879548754&utm_content=notif001",
+                new Bundle());
+
+        Map<String, String> expected = new HashMap<String, String>() {{
+            put("&t", "event"); // Type
+            put("&ea", "batch_notification_open"); // Action
+            put("&ec", "push"); // Category
+            put("&el", "batch"); // Label
+            put("&cn", "154879548754"); // Campaign name
+            put("&cs", "[batch-sdk]"); // Campaign Source
+            put("&cm", "pushbatch01"); // Campaign Medium
+            put("&cc", "notif001"); // Campaign Content
+        }};
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.NOTIFICATION_OPEN, payload);
+        Mockito.verify(tracker).send(mapEq(expected));
+    }
+
+    @Test
+    public void testNotificationCustomPayload() {
+
+        Bundle customPayload = new Bundle();
+        customPayload.putString("utm_medium", "654987");
+        customPayload.putString("utm_source", "jesuisuntest");
+        customPayload.putString("utm_campaign", "heinhein");
+        customPayload.putString("utm_content", "allo118218");
+        TestEventPayload payload = new TestEventPayload(null,
+                null,
+                customPayload);
+
+        Map<String, String> expected = new HashMap<String, String>() {{
+            put("&t", "event"); // Type
+            put("&ea", "batch_notification_receive"); // Action
+            put("&ec", "push"); // Category
+            put("&el", "batch"); // Label
+            put("&cn", "heinhein"); // Campaign name
+            put("&cs", "jesuisuntest"); // Campaign Source
+            put("&cm", "654987"); // Campaign Medium
+        }};
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.NOTIFICATION_RECEIVE, payload);
+        Mockito.verify(tracker).send(mapEq(expected));
+    }
+
+    @Test
+    public void testNotificationDeeplinkPriority() {
+        // priority: Custom Payload > Query vars > Fragment vars
+        Bundle customPayload = new Bundle();
+        customPayload.putString("utm_medium", "654987");
+        TestEventPayload payload = new TestEventPayload(null,
+                "https://batch.com?utm_source=batchsdk&utm_campaign=yoloswag#utm_source=batch-sdk&utm_medium=pushbatch01&utm_campaign=154879548754&utm_content=notif001",
+                customPayload);
+
+        Map<String, String> expected = new HashMap<String, String>() {{
+            put("&t", "event"); // Type
+            put("&ea", "batch_notification_open"); // Action
+            put("&ec", "push"); // Category
+            put("&el", "batch"); // Label
+            put("&cn", "yoloswag"); // Campaign name
+            put("&cs", "batchsdk"); // Campaign Source
+            put("&cm", "654987"); // Campaign Medium
+            put("&cc", "notif001"); // Campaign Content
+        }};
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.NOTIFICATION_OPEN, payload);
+        Mockito.verify(tracker).send(mapEq(expected));
+    }
+
+    @Test
+    public void testNotificationDeeplinkNonTrimmed() {
+        Bundle customPayload = new Bundle();
+        TestEventPayload payload = new TestEventPayload(null,
+                "   \n     https://batch.com?utm_source=batchsdk&utm_campaign=yoloswag     \n ",
+                customPayload);
+
+        Map<String, String> expected = new HashMap<String, String>() {{
+            put("&t", "event"); // Type
+            put("&ea", "batch_notification_open"); // Action
+            put("&ec", "push"); // Category
+            put("&el", "batch"); // Label
+            put("&cn", "yoloswag"); // Campaign name
+            put("&cs", "batchsdk"); // Campaign Source
+            put("&cm", "push"); // Campaign Medium
+        }};
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.NOTIFICATION_OPEN, payload);
+        Mockito.verify(tracker).send(mapEq(expected));
+    }
+
+    @Test
+    public void testNotificationDismissCampaign() {
+
+        Bundle customPayload = new Bundle();
+        TestEventPayload payload = new TestEventPayload(null,
+                "https://batch.com?utm_campaign=yoloswag",
+                customPayload);
+
+        Map<String, String> expected = new HashMap<String, String>() {{
+            put("&t", "event"); // Type
+            put("&ea", "batch_notification_dismiss"); // Action
+            put("&ec", "push"); // Category
+            put("&el", "batch"); // Label
+            put("&cn", "yoloswag"); // Campaign name
+            put("&cs", "batch"); // Campaign Source
+            put("&cm", "push"); // Campaign Medium
+        }};
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.NOTIFICATION_DISMISS, payload);
+        Mockito.verify(tracker).send(mapEq(expected));
+    }
+
+    @Test
+    public void testInAppNoData() {
+
+        TestEventPayload payload = new TestEventPayload(null,
+                null,
+                new Bundle());
+
+        Map<String, String> expected = new HashMap<String, String>() {{
+            put("&t", "event"); // Type
+            put("&ea", "batch_in_app_show"); // Action
+            put("&ec", "in-app"); // Category
+            put("&el", "batch"); // Label
+            put("&cs", "batch"); // Campaign Source
+            put("&cm", "in-app"); // Campaign Medium
+            put("batch_tracking_id", null);
+        }};
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.IN_APP_SHOW, payload);
+        Mockito.verify(tracker).send(mapEq(expected));
+    }
+
+    /*
+    @Test
+    public void testInAppShowUppercaseQueryVars() {
+
+        TestEventPayload payload = new TestEventPayload(null,
+                "https://batch.com?uTm_ConTENT=jesuisuncontent",
+                new Bundle());
+
+        Bundle expected = new Bundle();
+        expected.putString("batch_tracking_id", null);
+        expected.putString("medium", "in-app");
+        expected.putString("source", "batch");
+        expected.putString("campaign", null);
+        expected.putString("content", "jesuisuncontent");
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.IN_APP_SHOW, payload);
+        Mockito.verify(tracker).send(Mockito.eq("batch_in_app_show"), mapEq(expected));
+    }
+
+    @Test
+    public void testInAppShowUppercaseFragmentVars() {
+
+        TestEventPayload payload = new TestEventPayload(null,
+                "https://batch.com#UtM_CoNtEnT=jesuisuncontent",
+                new Bundle());
+
+        Bundle expected = new Bundle();
+        expected.putString("batch_tracking_id", null);
+        expected.putString("medium", "in-app");
+        expected.putString("source", "batch");
+        expected.putString("campaign", null);
+        expected.putString("content", "jesuisuncontent");
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.IN_APP_SHOW, payload);
+        Mockito.verify(tracker).send(Mockito.eq("batch_in_app_show"), mapEq(expected));
+    }
+
+    @Test
+    public void testInAppTrackingId() {
+
+        TestEventPayload payload = new TestEventPayload("jesuisunid",
+                null,
+                new Bundle());
+
+        Bundle expected = new Bundle();
+        expected.putString("medium", "in-app");
+        expected.putString("source", "batch");
+        expected.putString("campaign", "jesuisunid");
+        expected.putString("batch_tracking_id", "jesuisunid");
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.IN_APP_GLOBAL_TAP, payload);
+        Mockito.verify(tracker).send(Mockito.eq("batch_in_app_global_tap"), mapEq(expected));
+    }
+
+    @Test
+    public void testInAppDeeplinkContentQueryVars() {
+
+        TestEventPayload payload = new TestEventPayload("jesuisunid",
+                "https://batch.com?utm_content=jesuisuncontent",
+                new Bundle());
+
+        Bundle expected = new Bundle();
+        expected.putString("medium", "in-app");
+        expected.putString("source", "batch");
+        expected.putString("campaign", "jesuisunid");
+        expected.putString("batch_tracking_id", "jesuisunid");
+        expected.putString("content", "jesuisuncontent");
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.IN_APP_CLOSE, payload);
+        Mockito.verify(tracker).send(Mockito.eq("batch_in_app_close"), mapEq(expected));
+    }
+
+    @Test
+    public void testInAppDeeplinkFragmentQueryVars() {
+
+        TestEventPayload payload = new TestEventPayload("jesuisunid",
+                "https://batch.com#utm_content=jesuisuncontent00587",
+                new Bundle());
+
+        Bundle expected = new Bundle();
+        expected.putString("medium", "in-app");
+        expected.putString("source", "batch");
+        expected.putString("campaign", "jesuisunid");
+        expected.putString("batch_tracking_id", "jesuisunid");
+        expected.putString("content", "jesuisuncontent00587");
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.IN_APP_DISMISS, payload);
+        Mockito.verify(tracker).send(Mockito.eq("batch_in_app_dismiss"), mapEq(expected));
+    }
+
+    @Test
+    public void testInAppDeeplinkContentPriority() {
+
+        TestEventPayload payload = new TestEventPayload("jesuisunid",
+                "https://batch.com?utm_content=jesuisuncontent002#utm_content=jesuisuncontent015",
+                new Bundle());
+
+        Bundle expected = new Bundle();
+        expected.putString("medium", "in-app");
+        expected.putString("source", "batch");
+        expected.putString("campaign", "jesuisunid");
+        expected.putString("batch_tracking_id", "jesuisunid");
+        expected.putString("content", "jesuisuncontent002");
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.IN_APP_AUTO_CLOSE, payload);
+        Mockito.verify(tracker).send(Mockito.eq("batch_in_app_auto_close"), mapEq(expected));
+    }
+
+    @Test
+    public void testInAppDeeplinkContentNoId() {
+
+        TestEventPayload payload = new TestEventPayload(null,
+                "https://batch.com?utm_content=jesuisuncontent",
+                new Bundle());
+
+        Bundle expected = new Bundle();
+        expected.putString("medium", "in-app");
+        expected.putString("source", "batch");
+        expected.putString("campaign", null);
+        expected.putString("batch_tracking_id", null);
+        expected.putString("content", "jesuisuncontent");
+
+        googleAnalyticsDispatcher.dispatchEvent(Batch.EventDispatcher.Type.IN_APP_CLICK, payload);
+        Mockito.verify(tracker).send(Mockito.eq("batch_in_app_click"), mapEq(expected));
+    }
+     */
+
+    private static class TestEventPayload implements Batch.EventDispatcher.Payload {
+
+        private String trackingId;
+        private String deeplink;
+        private Bundle customPayload;
+
+        TestEventPayload(String trackingId,
+                         String deeplink, Bundle customPayload)
+        {
+            this.trackingId = trackingId;
+            this.deeplink = deeplink;
+            this.customPayload = customPayload;
+        }
+
+        @Nullable
+        @Override
+        public String getTrackingId()
+        {
+            return trackingId;
+        }
+
+        @Nullable
+        @Override
+        public String getDeeplink()
+        {
+            return deeplink;
+        }
+
+        @Nullable
+        @Override
+        public String getCustomValue(@NonNull String key)
+        {
+            if (customPayload == null) {
+                return null;
+            }
+            return customPayload.getString(key);
+        }
+
+        @Override
+        public boolean isPositiveAction() {
+            return false;
+        }
+
+        @Nullable
+        @Override
+        public BatchMessage getInAppPayload()
+        {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public BatchPushPayload getPushPayload()
+        {
+            return null;
+        }
+    }
+
+    private static Map<String, String> mapEq(Map<String, String> expected) {
+        return Mockito.argThat(new MapObjectMatcher(expected));
+    }
+
+    private static class MapObjectMatcher implements ArgumentMatcher<Map<String, String>>
+    {
+        Map<String, String> expected;
+
+        private MapObjectMatcher(Map<String, String> expected) {
+            this.expected = expected;
+        }
+
+        @Override
+        public boolean matches(Map<String, String> map) {
+            return equalMaps(map, expected);
+        }
+
+        private boolean equalMaps(Map<String, String> one, Map<String, String> two) {
+            if (one.size() != two.size()) {
+                return false;
+            }
+
+            return one.equals(two);
+        }
+    }
+
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..199d16e
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
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..89cb7b1
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Oct 29 14:41:07 CET 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# 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\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# 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
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+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" -a "$nonstop" = "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"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # 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
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@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
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@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=
+
+@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 Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_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=%*
+
+: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/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..75ea01a
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':google-analytics-dispatcher'
+rootProject.name='android-google-analytics-dispatcher'