summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Herman <davidherman@google.com>2015-05-18 14:43:09 -0700
committerDavid Herman <davidherman@google.com>2015-05-18 21:53:28 -0700
commit6c7a27a09e5b36c4e49505feab5d3438c38ebe27 (patch)
treea3c441366d4287dba08dc98f68507c9a4dd8bc2a
parent239b16337ed8c82ad2ba94753c4642f37e018412 (diff)
downloadservices-6c7a27a09e5b36c4e49505feab5d3438c38ebe27.tar.gz
Analytics Service implementation
Change-Id: I5f8042e713a7aa4ad2beab478e1b1a3ad61c9259
-rw-r--r--google-services.iml11
-rw-r--r--lib/google-api-services-analytics-v3-rev115-1.20.0.jarbin0 -> 276071 bytes
-rw-r--r--resources/analytics/AnalyticsTrackers.java.ftl74
-rw-r--r--resources/analytics/AndroidManifest.xml10
-rw-r--r--resources/analytics/app_tracker.xml.ftl16
-rw-r--r--resources/analytics/recipe.xml9
-rw-r--r--resources/analytics/service.xml76
-rw-r--r--src/com/google/services/GoogleServiceCreators.java22
-rw-r--r--src/com/google/services/creators/AnalyticsServiceCreator.java207
9 files changed, 422 insertions, 3 deletions
diff --git a/google-services.iml b/google-services.iml
index 5b7575c..b74bb22 100644
--- a/google-services.iml
+++ b/google-services.iml
@@ -26,5 +26,16 @@
<orderEntry type="module" module-name="annotations" />
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
<orderEntry type="library" scope="TEST" name="fest" level="project" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/lib/google-api-services-analytics-v3-rev115-1.20.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="library" name="google-api-java-client" level="project" />
+ <orderEntry type="module" module-name="platform-api" />
</component>
</module> \ No newline at end of file
diff --git a/lib/google-api-services-analytics-v3-rev115-1.20.0.jar b/lib/google-api-services-analytics-v3-rev115-1.20.0.jar
new file mode 100644
index 0000000..37ff6b1
--- /dev/null
+++ b/lib/google-api-services-analytics-v3-rev115-1.20.0.jar
Binary files differ
diff --git a/resources/analytics/AnalyticsTrackers.java.ftl b/resources/analytics/AnalyticsTrackers.java.ftl
new file mode 100644
index 0000000..0f6d274
--- /dev/null
+++ b/resources/analytics/AnalyticsTrackers.java.ftl
@@ -0,0 +1,74 @@
+package ${packageName};
+
+import android.content.Context;
+
+import com.google.android.gms.analytics.GoogleAnalytics;
+import com.google.android.gms.analytics.Tracker;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A collection of Google Analytics trackers. Fetch the tracker you need using
+ * {@code AnalyticsTrackers.getInstance().get(...)}
+ * <p/>
+ * This code was generated by Android Studio but can be safely modified by
+ * hand at this point.
+ * <p/>
+ * TODO: Call {@link #initialize(Context)} from an entry point in your app
+ * before using this!
+ */
+public final class AnalyticsTrackers {
+
+ public enum Target {
+ APP,
+ // Add more trackers here if you need, and update the code in #get(Target) below
+ }
+
+ private static AnalyticsTrackers sInstance;
+
+ public static synchronized void initialize(Context context) {
+ if (sInstance != null) {
+ throw new IllegalStateException("Extra call to initialize analytics trackers");
+ }
+
+ sInstance = new AnalyticsTrackers(context);
+ }
+
+ public static synchronized AnalyticsTrackers getInstance() {
+ if (sInstance == null) {
+ throw new IllegalStateException("Call initialize() before getInstance()");
+ }
+
+ return sInstance;
+ }
+
+ private final Map<Target, Tracker> mTrackers = new HashMap<Target, Tracker>();
+ private final Context mContext;
+
+ /**
+ * Use {@link #getInstance()} instead.
+ */
+ private AnalyticsTrackers(Context context) {
+ mContext = context.getApplicationContext();
+ }
+
+ public synchronized Tracker get(Target target) {
+ if (!mTrackers.containsKey(target)) {
+ Tracker tracker;
+ switch (target) {
+ case APP:
+ tracker = GoogleAnalytics.getInstance(mContext).newTracker(R.xml.app_tracker);
+<#if google.analytics.adsEnabled>
+ tracker.enableAdvertisingIdCollection(true);
+</#if>
+ break;
+ default:
+ throw new IllegalArgumentException("Unhandled analytics target " + target);
+ }
+ mTrackers.put(target, tracker);
+ }
+
+ return mTrackers.get(target);
+ }
+}
diff --git a/resources/analytics/AndroidManifest.xml b/resources/analytics/AndroidManifest.xml
new file mode 100644
index 0000000..e1d892d
--- /dev/null
+++ b/resources/analytics/AndroidManifest.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+ <application>
+ <meta-data android:name="com.google.android.gms.version"
+ android:value="@integer/google_play_services_version" />
+ </application>
+</manifest>
diff --git a/resources/analytics/app_tracker.xml.ftl b/resources/analytics/app_tracker.xml.ftl
new file mode 100644
index 0000000..9162f40
--- /dev/null
+++ b/resources/analytics/app_tracker.xml.ftl
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- End current session if app sleeps for a period of time -->
+ <integer name="ga_sessionTimeout">300</integer>
+
+ <!-- Enable automatic Activity measurement -->
+ <bool name="ga_autoActivityTracking">true</bool>
+
+ <!-- The property id associated with this analytics tracker -->
+ <string name="ga_trackingId">${google.analytics.propertyId}</string>
+
+ <!--
+ See Project Structure -> Analytics -> Google Analytics -> Learn More
+ to learn more about configuring this file.
+ -->
+</resources> \ No newline at end of file
diff --git a/resources/analytics/recipe.xml b/resources/analytics/recipe.xml
index 23637bd..1b16309 100644
--- a/resources/analytics/recipe.xml
+++ b/resources/analytics/recipe.xml
@@ -1,4 +1,13 @@
<?xml version="1.0"?>
<recipe>
<dependency mavenUrl="com.google.android.gms:play-services-analytics:${google.play.version}"/>
+
+ <merge from="AndroidManifest.xml"
+ to="src/main/AndroidManifest.xml"/>
+
+ <instantiate from="app_tracker.xml.ftl"
+ to="src/main/res/xml/app_tracker.xml"/>
+
+ <instantiate from="AnalyticsTrackers.java.ftl"
+ to="src/main/java/${slashedPackageName(packageName)}/AnalyticsTrackers.java"/>
</recipe> \ No newline at end of file
diff --git a/resources/analytics/service.xml b/resources/analytics/service.xml
index 61bfa7a..cc9b885 100644
--- a/resources/analytics/service.xml
+++ b/resources/analytics/service.xml
@@ -4,7 +4,81 @@
category="Analytics"
name="Google Analytics"
description="Lets you measure user interactions with your app across various devices and environments."
- learnMore="https://developers.google.com/analytics/devguides/collection/android/v4/"
+ learnMore="https://developers.google.com/analytics/"
icon="logo_analytics_color_2x_web_32dp.png"
execute="recipe.xml">
+
+ <uiGrid colDefinitions="100px,200px" visible="${google.isLoggedOut}">
+ <uiItem
+ row="0" col="0"
+ type="label"
+ text="Account:"/>
+
+ <uiItem
+ row="0" col="1"
+ type="button"
+ text="Login to Google"
+ action="${google.login()}"/>
+ </uiGrid>
+
+ <uiGrid colDefinitions="100px,Fit,Fit" visible="${google.analytics.hasNoAccount}">
+ <uiItem
+ row="0" col="0"
+ type="label"
+ text="Accounts:"/>
+
+ <uiItem
+ row="0" col="1"
+ type="button"
+ text="Create Analytics Project"
+ action="${google.analytics.createProject()}"/>
+
+ <uiItem
+ row="0" col="2"
+ type="button"
+ text="Refresh"
+ action="${google.analytics.refreshProjects()}"/>
+ </uiGrid>
+
+
+ <uiGrid colDefinitions="100px,250px" visible="${google.analytics.hasAccount}">
+ <uiItem
+ row="0" col="0"
+ type="label"
+ text="Accounts:"/>
+
+ <uiItem
+ row="0" col="1"
+ type="pulldown"
+ list="${google.analytics.accounts}"
+ index="${google.analytics.accountIndex}"/>
+
+ <uiItem
+ row="1" col="0"
+ type="label"
+ text="Properties:"/>
+
+ <uiItem
+ row="1" col="1"
+ type="pulldown"
+ list="${google.analytics.properties}"
+ index="${google.analytics.propertyIndex}"/>
+ </uiGrid>
+
+ <uiGrid colDefinitions="100px,200px">
+ <uiItem
+ row="0" col="0"
+ type="label"
+ text="Property Id:"/>
+
+ <uiItem
+ row="0" col="1"
+ type="input"
+ text="${google.analytics.propertyId}"/>
+ </uiGrid>
+
+ <uiItem
+ type="checkbox"
+ text="Enable Advertising ID"
+ checked="${google.analytics.adsEnabled}"/>
</service>
diff --git a/src/com/google/services/GoogleServiceCreators.java b/src/com/google/services/GoogleServiceCreators.java
index f75c5b3..9e58830 100644
--- a/src/com/google/services/GoogleServiceCreators.java
+++ b/src/com/google/services/GoogleServiceCreators.java
@@ -18,12 +18,17 @@ package com.google.services;
import com.android.tools.idea.structure.services.DeveloperServiceCreator;
import com.android.tools.idea.structure.services.DeveloperServiceCreators;
import com.android.tools.idea.structure.services.ServiceContext;
+import com.android.tools.idea.ui.properties.core.ObservableBool;
import com.android.tools.idea.ui.properties.core.StringValueProperty;
import com.google.common.collect.ImmutableSet;
+import com.google.gct.login.GoogleLogin;
+import com.google.gct.login.InvalidThreadTypeException;
import com.google.services.creators.*;
import java.util.Collection;
+import static com.android.tools.idea.ui.properties.expressions.bool.BooleanExpressions.not;
+
/**
* Extension for Android Studio which provides helpers that initialize Google developer services.
*/
@@ -35,7 +40,22 @@ public final class GoogleServiceCreators implements DeveloperServiceCreators {
public static void initializeGoogleContext(ServiceContext serviceContext) {
// TODO: Get this value from maven?
serviceContext.putValue("google.play.version", new StringValueProperty("7.3.0"));
- serviceContext.putValue("google.isLoggedIn", GoogleServiceLoginListener.getInstance().loggedIn());
+ ObservableBool loggedIn = GoogleServiceLoginListener.getInstance().loggedIn();
+ serviceContext.putValue("google.isLoggedIn", loggedIn);
+ serviceContext.putValue("google.isLoggedOut", not(loggedIn));
+
+ serviceContext.putAction("google.login", new Runnable() {
+ @Override
+ public void run() {
+ try {
+ GoogleLogin.promptToLogIn();
+ }
+ catch (InvalidThreadTypeException e) {
+ // Never happens - we always call this on the dispatch thread
+ throw new RuntimeException(e);
+ }
+ }
+ });
}
@Override
diff --git a/src/com/google/services/creators/AnalyticsServiceCreator.java b/src/com/google/services/creators/AnalyticsServiceCreator.java
index 62a7d4f..662c3e9 100644
--- a/src/com/google/services/creators/AnalyticsServiceCreator.java
+++ b/src/com/google/services/creators/AnalyticsServiceCreator.java
@@ -17,10 +17,107 @@ package com.google.services.creators;
import com.android.tools.idea.structure.services.DeveloperServiceCreator;
import com.android.tools.idea.structure.services.ServiceContext;
+import com.android.tools.idea.ui.properties.BindingsManager;
+import com.android.tools.idea.ui.properties.InvalidationListener;
+import com.android.tools.idea.ui.properties.Observable;
+import com.android.tools.idea.ui.properties.collections.ObservableList;
+import com.android.tools.idea.ui.properties.core.BoolValueProperty;
+import com.android.tools.idea.ui.properties.core.IntValueProperty;
+import com.android.tools.idea.ui.properties.core.ObservableBool;
+import com.android.tools.idea.ui.properties.core.StringValueProperty;
+import com.android.tools.idea.ui.properties.expressions.bool.BooleanExpression;
+import com.android.tools.idea.ui.properties.expressions.bool.BooleanExpressions;
+import com.android.tools.idea.ui.properties.expressions.list.AbstractMapExpression;
+import com.android.tools.idea.ui.properties.expressions.list.SizeExpression;
+import com.android.tools.idea.ui.properties.expressions.string.IsEmptyExpression;
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.analytics.Analytics;
+import com.google.api.services.analytics.model.Account;
+import com.google.api.services.analytics.model.Accounts;
+import com.google.api.services.analytics.model.Webproperties;
+import com.google.api.services.analytics.model.Webproperty;
+import com.google.gct.login.CredentialedUser;
+import com.google.gct.login.GoogleLogin;
import com.google.services.GoogleServiceCreators;
+import com.google.services.GoogleServiceLoginListener;
+import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
+import java.util.concurrent.Callable;
+
public final class AnalyticsServiceCreator extends DeveloperServiceCreator {
+
+ private final ObservableBool myLoggedIn = GoogleServiceLoginListener.getInstance().loggedIn();
+ private final ObservableList<Account> myAccounts = new ObservableList<Account>();
+ private final ObservableList<Webproperty> myProperties = new ObservableList<Webproperty>();
+ private final IntValueProperty myAccountIndex = new IntValueProperty(-1);
+ private final IntValueProperty myPropertyIndex = new IntValueProperty(-1);
+ private final StringValueProperty myPropertyId = new StringValueProperty();
+ private final BoolValueProperty myAdsEnabled = new BoolValueProperty();
+ private final ObservableList<String> myAccountNames = new ObservableList<String>();
+ private final ObservableList<String> myPropertyNames = new ObservableList<String>();
+
+ @SuppressWarnings("FieldCanBeLocal") // Declared as field to prevent garbage collection
+ private final BindingsManager myBindings = new BindingsManager();
+
+ @SuppressWarnings("FieldCanBeLocal") // Declared as field to prevent weak reference collection
+ private final InvalidationListener myLoginListener = new InvalidationListener() {
+ @Override
+ protected void onInvalidated(@NotNull Observable sender) {
+ if (myLoggedIn.get()) {
+ refreshProjects();
+ }
+ else {
+ myAccounts.clear();
+ myProperties.clear();
+ myAccountIndex.set(-1);
+ myPropertyIndex.set(-1);
+ }
+ }
+ };
+
+ public AnalyticsServiceCreator() {
+ myLoggedIn.addWeakListener(myLoginListener);
+
+ // Map accounts to account names
+ myBindings.bindList(myAccountNames, new AbstractMapExpression<Account, String>(myAccounts) {
+ @NotNull
+ @Override
+ protected String transform(@NotNull Account account) {
+ return account.getName();
+ }
+ });
+
+ // Map properties to property names
+ myBindings.bindList(myPropertyNames, new AbstractMapExpression<Webproperty, String>(myProperties) {
+ @NotNull
+ @Override
+ protected String transform(@NotNull Webproperty property) {
+ return property.getName();
+ }
+ });
+
+ myAccountIndex.addListener(new InvalidationListener() {
+ @Override
+ protected void onInvalidated(@NotNull Observable sender) {
+ if (myAccountIndex.get() >= 0) {
+ refreshProperties();
+ }
+ }
+ });
+
+ myPropertyIndex.addListener(new InvalidationListener() {
+ @Override
+ protected void onInvalidated(@NotNull Observable sender) {
+ if (myPropertyIndex.get() >= 0) {
+ //noinspection ConstantConditions
+ myPropertyId.set(myProperties.get(myPropertyIndex.get()).getId());
+ }
+ }
+ });
+ }
+
@NotNull
@Override
protected String getResourceRoot() {
@@ -30,11 +127,119 @@ public final class AnalyticsServiceCreator extends DeveloperServiceCreator {
@NotNull
@Override
protected String[] getResources() {
- return new String[]{"logo_analytics_color_2x_web_32dp.png", "recipe.xml", "service.xml"};
+ return new String[]{"AnalyticsTrackers.java.ftl", "AndroidManifest.xml", "app_tracker.xml.ftl", "logo_analytics_color_2x_web_32dp.png",
+ "recipe.xml", "service.xml"};
}
@Override
protected void initializeContext(@NotNull ServiceContext serviceContext) {
GoogleServiceCreators.initializeGoogleContext(serviceContext);
+
+ serviceContext.putValue("google.analytics.accounts", myAccountNames);
+ serviceContext.putValue("google.analytics.accountIndex", myAccountIndex);
+ serviceContext.putValue("google.analytics.properties", myPropertyNames);
+ serviceContext.putValue("google.analytics.propertyIndex", myPropertyIndex);
+ serviceContext.putValue("google.analytics.hasNoAccount", new SizeExpression(myAccounts).isEqualTo(0).and(myLoggedIn));
+ serviceContext.putValue("google.analytics.hasAccount", new SizeExpression(myAccounts).isGreaterThan(0));
+
+ serviceContext.putAction("google.analytics.refreshProjects", new Runnable() {
+ @Override
+ public void run() {
+ refreshProjects();
+ }
+ });
+ serviceContext.putAction("google.analytics.createProject", new Runnable() {
+ @Override
+ public void run() {
+ browse("https://www.google.com/analytics/web/#management/Settings/");
+ }
+ });
+
+ serviceContext.putWatchedValue("google.analytics.propertyId", myPropertyId);
+ serviceContext.putWatchedValue("google.analytics.adsEnabled", myAdsEnabled);
+
+ serviceContext.setBeforeShownCallback(new Runnable() {
+ @Override
+ public void run() {
+ if (myLoggedIn.get()) {
+ refreshProjects();
+ }
+ }
+ });
+
+ serviceContext.setIsValidCallback(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return !myPropertyId.get().isEmpty();
+ }
+ });
+ }
+
+ private void refreshProjects() {
+ final CredentialedUser user = GoogleLogin.getInstance().getActiveUser();
+ assert user != null;
+
+ Callable<Accounts> fetchAccounts = new Callable<Accounts>() {
+ @Override
+ public Accounts call() throws Exception {
+ Analytics analytics =
+ new Analytics.Builder(new NetHttpTransport(), new JacksonFactory(), user.getCredential()).setApplicationName("Android Studio")
+ .build();
+
+ Accounts accounts = analytics.management().accounts().list().execute();
+ if (accounts != null && accounts.getItems().size() > 0) {
+ return accounts;
+ }
+
+ return null;
+ }
+ };
+
+ Dispatchable<Accounts> onAccountsFetched = new Dispatchable<Accounts>() {
+ @Override
+ public void dispatch(@NotNull Accounts accounts) {
+ myAccounts.setAll(accounts.getItems());
+ myAccountIndex.set(0);
+ }
+ };
+
+ runInBackground(fetchAccounts, onAccountsFetched);
+ }
+
+ private void refreshProperties() {
+ final Account account = myAccounts.get(myAccountIndex.get());
+ assert account != null;
+
+ final CredentialedUser user = GoogleLogin.getInstance().getActiveUser();
+ assert user != null;
+
+ myPropertyIndex.set(-1);
+
+ Callable<Webproperties> fetchProperties = new Callable<Webproperties>() {
+ @Override
+ public Webproperties call() throws Exception {
+ Analytics analytics =
+ new Analytics.Builder(new NetHttpTransport(), new JacksonFactory(), user.getCredential()).setApplicationName("Android Studio")
+ .build();
+
+ String id = account.getId();
+ Webproperties properties = analytics.management().webproperties().list(id).execute();
+ if (properties != null && properties.getItems().size() > 0) {
+ return properties;
+ }
+
+ return null;
+ }
+ };
+
+ Dispatchable<Webproperties> onPropertiesFetched = new Dispatchable<Webproperties>() {
+ @Override
+ public void dispatch(@NotNull Webproperties properties) {
+ myProperties.setAll(properties.getItems());
+ myPropertyIndex.set(0);
+ }
+ };
+
+ runInBackground(fetchProperties, onPropertiesFetched);
}
}