diff options
author | David Herman <davidherman@google.com> | 2015-05-18 14:43:09 -0700 |
---|---|---|
committer | David Herman <davidherman@google.com> | 2015-05-18 21:53:28 -0700 |
commit | 6c7a27a09e5b36c4e49505feab5d3438c38ebe27 (patch) | |
tree | a3c441366d4287dba08dc98f68507c9a4dd8bc2a | |
parent | 239b16337ed8c82ad2ba94753c4642f37e018412 (diff) | |
download | services-6c7a27a09e5b36c4e49505feab5d3438c38ebe27.tar.gz |
Analytics Service implementation
Change-Id: I5f8042e713a7aa4ad2beab478e1b1a3ad61c9259
-rw-r--r-- | google-services.iml | 11 | ||||
-rw-r--r-- | lib/google-api-services-analytics-v3-rev115-1.20.0.jar | bin | 0 -> 276071 bytes | |||
-rw-r--r-- | resources/analytics/AnalyticsTrackers.java.ftl | 74 | ||||
-rw-r--r-- | resources/analytics/AndroidManifest.xml | 10 | ||||
-rw-r--r-- | resources/analytics/app_tracker.xml.ftl | 16 | ||||
-rw-r--r-- | resources/analytics/recipe.xml | 9 | ||||
-rw-r--r-- | resources/analytics/service.xml | 76 | ||||
-rw-r--r-- | src/com/google/services/GoogleServiceCreators.java | 22 | ||||
-rw-r--r-- | src/com/google/services/creators/AnalyticsServiceCreator.java | 207 |
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 Binary files differnew file mode 100644 index 0000000..37ff6b1 --- /dev/null +++ b/lib/google-api-services-analytics-v3-rev115-1.20.0.jar 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); } } |