reader/android: Google Analytics integration for collecting user events

This CL integrates Google Analytics to the project. In order to enable
the GA feature, google-services.json file should be downloaded and put
under android/app directory (instructions: http://goo.gl/8Rd3yj)

The ReaderApplication has two implementations, and only one of them is
conditionally added to the project source sets to avoid breaking build
when the "google-services.json" file does not exist.

The activities are refactored to have a common base class. The base
class performs several initializations needed by all activities used
in the Reader app.

The following events are sent to the Google Analytics server.
 - all the gesture events detected by the GestureDetectors
 - prev/next page button
 - link/unlink page button

Change-Id: I1ad5fab10ee92e3f877028a3ab944250e68b76f2
diff --git a/android/app/.gitignore b/android/app/.gitignore
index 796b96d..57d76e1 100644
--- a/android/app/.gitignore
+++ b/android/app/.gitignore
@@ -1 +1,3 @@
 /build
+google-services.json
+
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4346593..6e68ba9 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -23,6 +23,18 @@
 // It's going to use VDL.
 apply plugin: 'io.v.vdl'
 
+
+// Conditionally apply the google services plugin, depending on existence of the configuration file.
+// Also, add conditional source folders as source directories.
+// This is a workaround for not having conditional compilation in Java.
+if (new File(projectDir, 'google-services.json').exists()) {
+    apply plugin: 'com.google.gms.google-services'
+    android.sourceSets.main.java.srcDir { 'src/conditional/ga_enabled' }
+} else {
+    android.sourceSets.main.java.srcDir { 'src/conditional/ga_disabled' }
+}
+
+
 repositories {
     mavenCentral()
 }
@@ -69,6 +81,7 @@
     compile 'com.android.support:cardview-v7:22.2.1'
     compile 'com.android.support:recyclerview-v7:22.2.1'
     compile 'io.v:vanadium-android:0.1'
+    compile 'com.google.android.gms:play-services-analytics:8.3.0'
 }
 
 vdl {
diff --git a/android/app/src/conditional/ga_disabled/io/v/android/apps/reader/ReaderApplication.java b/android/app/src/conditional/ga_disabled/io/v/android/apps/reader/ReaderApplication.java
new file mode 100644
index 0000000..fbd3664
--- /dev/null
+++ b/android/app/src/conditional/ga_disabled/io/v/android/apps/reader/ReaderApplication.java
@@ -0,0 +1,27 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.apps.reader;
+
+import android.app.Application;
+
+import com.google.android.gms.analytics.Tracker;
+
+/**
+ * This is a subclass of {@link Application} used to provide the {@link Tracker}.
+ *
+ * This file is conditionally included to the project, when the "google-services.json" file does not
+ * exist.
+ */
+public class ReaderApplication extends Application {
+    /**
+     * Gets the default {@link Tracker} for this {@link Application}.
+     * Always returns null to disable tracking.
+     *
+     * @return null
+     */
+    public Tracker getDefaultTracker() {
+        return null;
+    }
+}
diff --git a/android/app/src/conditional/ga_enabled/io/v/android/apps/reader/ReaderApplication.java b/android/app/src/conditional/ga_enabled/io/v/android/apps/reader/ReaderApplication.java
new file mode 100644
index 0000000..8fd5363
--- /dev/null
+++ b/android/app/src/conditional/ga_enabled/io/v/android/apps/reader/ReaderApplication.java
@@ -0,0 +1,34 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.apps.reader;
+
+import android.app.Application;
+
+import com.google.android.gms.analytics.GoogleAnalytics;
+import com.google.android.gms.analytics.Tracker;
+
+/**
+ * This is a subclass of {@link Application} used to provide shared objects for this app, such as
+ * the {@link Tracker}.
+ *
+ * This file is conditionally included to the project, when the "google-services.json" file exists.
+ */
+public class ReaderApplication extends Application {
+    private Tracker mTracker;
+
+    /**
+     * Gets the default {@link Tracker} for this {@link Application}.
+     *
+     * @return tracker
+     */
+    synchronized public Tracker getDefaultTracker() {
+        if (mTracker == null) {
+            GoogleAnalytics analytics = GoogleAnalytics.getInstance(this);
+            mTracker = analytics.newTracker(R.xml.global_tracker);
+        }
+
+        return mTracker;
+    }
+}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 0c278cf..41c5550 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -2,7 +2,11 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="io.v.android.apps.reader">
 
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+
     <application
+        android:name="ReaderApplication"
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
diff --git a/android/app/src/main/java/io/v/android/apps/reader/BaseReaderActivity.java b/android/app/src/main/java/io/v/android/apps/reader/BaseReaderActivity.java
new file mode 100644
index 0000000..688cef9
--- /dev/null
+++ b/android/app/src/main/java/io/v/android/apps/reader/BaseReaderActivity.java
@@ -0,0 +1,96 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.apps.reader;
+
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.support.v4.view.GestureDetectorCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MotionEvent;
+
+import com.google.android.gms.analytics.HitBuilders;
+import com.google.android.gms.analytics.Tracker;
+
+import io.v.android.apps.reader.db.DB;
+import io.v.android.apps.reader.model.DeviceInfoFactory;
+
+/**
+ * Base activity class for all the Reader app activities. Its responsibilities include DB
+ * initialization, touch gesture detection, and google analytics tracking
+ */
+public abstract class BaseReaderActivity extends AppCompatActivity {
+    private String mDeviceId;
+    private DB mDB;
+    private Tracker mTracker;
+    private GestureDetectorCompat mGestureDetector;
+    private GestureListener mGestureListener;
+
+    protected DB getDB() {
+        return mDB;
+    }
+
+    protected Tracker getTracker() {
+        return mTracker;
+    }
+
+    protected String getDeviceId() {
+        if (mDeviceId == null) {
+            mDeviceId = DeviceInfoFactory.getDeviceId(this);
+        }
+
+        return mDeviceId;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        initTracker();
+
+        // TODO(youngseokyoon): allow screen rotation and properly handle orientation changes
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+
+        // Initialize the DB
+        mDB = DB.Singleton.get(this);
+        mDB.init(this);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (mTracker != null) {
+            String deviceId = DeviceInfoFactory.getDeviceId(this);
+            mTracker.setScreenName(String.format("%s.%s", deviceId, getClass().getSimpleName()));
+            mTracker.send(new HitBuilders.ScreenViewBuilder()
+                    .setCustomDimension(1, Long.toString(System.currentTimeMillis()))
+                    .build());
+        }
+    }
+
+    private void initTracker() {
+        // Obtain the shared Tracker instance.
+        ReaderApplication application = (ReaderApplication) getApplication();
+        mTracker = application.getDefaultTracker();
+
+        if (mTracker != null) {
+            mGestureListener = new GestureListener(mTracker, DeviceInfoFactory.getDeviceId(this));
+            mGestureDetector = new GestureDetectorCompat(this, mGestureListener);
+            mGestureDetector.setOnDoubleTapListener(mGestureListener);
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        // Forward all the touch event to the gesture detector.
+        // Implementing this in onTouchEvent is not enough, because it can only capture touch events
+        // that are not consumed by any child views.
+        if (mGestureDetector != null) {
+            mGestureDetector.onTouchEvent(ev);
+        }
+
+        return super.dispatchTouchEvent(ev);
+    }
+}
diff --git a/android/app/src/main/java/io/v/android/apps/reader/DeviceSetChooserActivity.java b/android/app/src/main/java/io/v/android/apps/reader/DeviceSetChooserActivity.java
index 4addeac..266fa2c 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/DeviceSetChooserActivity.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/DeviceSetChooserActivity.java
@@ -5,11 +5,9 @@
 package io.v.android.apps.reader;
 
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.design.widget.FloatingActionButton;
-import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.helper.ItemTouchHelper;
@@ -18,15 +16,13 @@
 import android.view.MenuItem;
 import android.view.View;
 
-import io.v.android.apps.reader.db.DB;
-
 /**
  * Activity that displays all the active device sets of this user.
  * <p/>
  * When the user clicks on one of the device sets, it starts the PdfViewerActivity with the file
  * associated with the device set.
  */
-public class DeviceSetChooserActivity extends AppCompatActivity {
+public class DeviceSetChooserActivity extends BaseReaderActivity {
 
     private static final String TAG = DeviceSetChooserActivity.class.getSimpleName();
 
@@ -35,18 +31,11 @@
     private RecyclerView mRecyclerView;
     private DeviceSetListAdapter mAdapter;
     private FloatingActionButton mButtonAddDeviceSet;
-    private DB mDB;
 
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        // TODO(youngseokyoon): allow screen rotation and properly handle orientation changes
-        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-
-        // Initialize the DB
-        mDB = DB.Singleton.get(this);
-        mDB.init(this);
-
         setContentView(R.layout.activity_device_set_chooser);
 
         mRecyclerView = (RecyclerView) findViewById(R.id.device_set_list);
@@ -104,7 +93,7 @@
             public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                 // Delete the device set on left swipe.
                 if (direction == ItemTouchHelper.LEFT) {
-                    mDB.deleteDeviceSet(
+                    getDB().deleteDeviceSet(
                             mAdapter.getDeviceSetId(viewHolder.getLayoutPosition()));
                 }
             }
@@ -149,7 +138,7 @@
         super.onActivityResult(requestCode, resultCode, data);
 
         Log.i(TAG, String.format("onActivityResult(%d, %d, data) called", requestCode, resultCode));
-        if (mDB.onActivityResult(requestCode, resultCode, data)) {
+        if (getDB().onActivityResult(requestCode, resultCode, data)) {
             return;
         }
 
diff --git a/android/app/src/main/java/io/v/android/apps/reader/GestureListener.java b/android/app/src/main/java/io/v/android/apps/reader/GestureListener.java
new file mode 100644
index 0000000..a7bc6e1
--- /dev/null
+++ b/android/app/src/main/java/io/v/android/apps/reader/GestureListener.java
@@ -0,0 +1,108 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.apps.reader;
+
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+import com.google.android.gms.analytics.HitBuilders;
+import com.google.android.gms.analytics.Tracker;
+
+/**
+ * Gesture listener implementation for sending gesture events to the Google Analytics tracker.
+ * Send all the data without filtering, so that the events are not lost.
+ */
+public class GestureListener implements GestureDetector.OnGestureListener,
+        GestureDetector.OnDoubleTapListener, ScaleGestureDetector.OnScaleGestureListener {
+
+    private static final String CATEGORY = "Touch Gesture";
+
+    private final Tracker mTracker;
+    private final String mLabel;
+
+    public GestureListener(Tracker tracker, String label) {
+        mTracker = tracker;
+        mLabel = label;
+    }
+
+    private void send(String action) {
+        mTracker.send(new HitBuilders.EventBuilder()
+                .setCustomDimension(1, Long.toString(System.currentTimeMillis()))
+                .setCategory(CATEGORY)
+                .setAction(action)
+                .setLabel(mLabel)
+                .build());
+    }
+
+    @Override
+    public boolean onDown(MotionEvent e) {
+        send("Down");
+        return true;
+    }
+
+    @Override
+    public void onShowPress(MotionEvent e) {
+        send("ShowPress");
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        send("SingleTapUp");
+        return true;
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        send("Scroll");
+        return true;
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+        send("LongPress");
+    }
+
+    @Override
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        send("Fling");
+        return true;
+    }
+
+    @Override
+    public boolean onSingleTapConfirmed(MotionEvent e) {
+        send("SingleTapConfirmed");
+        return true;
+    }
+
+    @Override
+    public boolean onDoubleTap(MotionEvent e) {
+        send("DoubleTap");
+        return true;
+    }
+
+    @Override
+    public boolean onDoubleTapEvent(MotionEvent e) {
+        send("DoubleTapEvent");
+        return true;
+    }
+
+    @Override
+    public boolean onScale(ScaleGestureDetector detector) {
+        send("Scale");
+        return true;
+    }
+
+    @Override
+    public boolean onScaleBegin(ScaleGestureDetector detector) {
+        send("ScaleBegin");
+        return true;
+    }
+
+    @Override
+    public void onScaleEnd(ScaleGestureDetector detector) {
+        send("ScaleEnd");
+    }
+}
diff --git a/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java b/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java
index 44b85a8..269c507 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java
@@ -6,12 +6,10 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.OpenableColumns;
-import android.support.v7.app.AppCompatActivity;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -21,6 +19,7 @@
 import android.widget.Button;
 import android.widget.Toast;
 
+import com.google.android.gms.analytics.HitBuilders;
 import com.google.common.io.ByteStreams;
 
 import java.io.FileOutputStream;
@@ -29,9 +28,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import io.v.android.apps.reader.db.DB;
 import io.v.android.apps.reader.db.DB.DBList;
-import io.v.android.apps.reader.model.DeviceInfoFactory;
 import io.v.android.apps.reader.model.IdFactory;
 import io.v.android.apps.reader.model.Listener;
 import io.v.android.apps.reader.vdl.DeviceMeta;
@@ -41,10 +38,12 @@
 /**
  * Activity that shows the contents of the selected pdf file.
  */
-public class PdfViewerActivity extends AppCompatActivity {
+public class PdfViewerActivity extends BaseReaderActivity {
 
     private static final String TAG = PdfViewerActivity.class.getSimpleName();
 
+    // Category string used for Google Analytics tracking.
+    private static final String CATEGORY_PAGE_NAVIGATION = "Page Navigation";
     private static final String EXTRA_DEVICE_SET_ID = "device_set_id";
 
     private PdfViewWrapper mPdfView;
@@ -52,7 +51,6 @@
     private Button mButtonNext;
     private MenuItem mMenuItemLinkPage;
 
-    private DB mDB;
     private DBList<DeviceSet> mDeviceSets;
     private DeviceSet mCurrentDS;
 
@@ -75,13 +73,6 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        // TODO(youngseokyoon): allow screen rotation and properly handle orientation changes
-        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-
-        // Initialize the DB
-        mDB = DB.Singleton.get(this);
-        mDB.init(this);
-
         setContentView(R.layout.activity_pdf_viewer);
 
         mPdfView = (PdfViewWrapper) findViewById(R.id.pdfview);
@@ -113,11 +104,11 @@
          * Suppress the start process until the DB initialization is completed.
          * onStart() method will be called again after the user selects her blessings.
          */
-        if (!mDB.isInitialized()) {
+        if (!getDB().isInitialized()) {
             return;
         }
 
-        mDeviceSets = mDB.getDeviceSetList();
+        mDeviceSets = getDB().getDeviceSetList();
         mDeviceSets.setListener(new Listener() {
             @Override
             public void notifyItemChanged(int position) {
@@ -190,16 +181,16 @@
         byte[] bytes = getBytesFromUri(fileUri);
 
         // Create a vdl File object representing this pdf file and put it in the db.
-        File vFile = mDB.storeBytes(bytes, getTitleFromUri(fileUri));
+        File vFile = getDB().storeBytes(bytes, getTitleFromUri(fileUri));
         Log.i(TAG, "vFile created: " + vFile);
         if (vFile == null) {
             Log.e(TAG, "Could not store the file content of Uri: " + fileUri.toString());
         }
-        mDB.addFile(vFile);
+        getDB().addFile(vFile);
 
         // Create a device set object and put it in the db.
         DeviceSet ds = createDeviceSet(vFile);
-        mDB.addDeviceSet(ds);
+        getDB().addDeviceSet(ds);
 
         // Join the device set.
         joinDeviceSet(ds);
@@ -237,22 +228,23 @@
     }
 
     private void toggleLinkedState(boolean checked) {
+        sendAction(checked ? "Unlink Page" : "Link Page");
+
         DeviceMeta dm = getDeviceMeta();
         if (dm == null) {
             return;
         }
 
         dm.setLinked(!checked);
-        mDB.updateDeviceSet(mCurrentDS);
+        getDB().updateDeviceSet(mCurrentDS);
     }
 
     private DeviceMeta createDeviceMeta() {
-        String deviceId = DeviceInfoFactory.getDeviceId(this);
         int page = 1;
         int zoom = 1;
         boolean linked = true;
 
-        return new DeviceMeta(deviceId, page, zoom, linked);
+        return new DeviceMeta(getDeviceId(), page, zoom, linked);
     }
 
     private DeviceSet createDeviceSet(File vFile) {
@@ -266,8 +258,8 @@
     private void joinDeviceSet(DeviceSet ds) {
         // Get the file contents from the database
         // TODO(youngseokyoon): get the blob asynchronously. right now, it's blocking the UI thread.
-        File file = mDB.getFileList().getItemById(ds.getFileId());
-        byte[] bytes = mDB.readBytes(file);
+        File file = getDB().getFileList().getItemById(ds.getFileId());
+        byte[] bytes = getDB().readBytes(file);
         if (bytes == null) {
             Toast.makeText(this, "Could not load the file contents.", Toast.LENGTH_LONG).show();
             return;
@@ -298,7 +290,7 @@
         Log.i(TAG, "Joining device set: " + ds.getId());
         DeviceMeta dm = createDeviceMeta();
         ds.getDevices().put(dm.getDeviceId(), dm);
-        mDB.updateDeviceSet(ds);
+        getDB().updateDeviceSet(ds);
 
         mCurrentDS = ds;
     }
@@ -310,13 +302,13 @@
 
         Log.i(TAG, "Leaving device set: " + mCurrentDS.getId());
         Map<String, DeviceMeta> devices = mCurrentDS.getDevices();
-        devices.remove(DeviceInfoFactory.getDeviceId(this));
+        devices.remove(getDeviceId());
 
         if (devices.isEmpty()) {
             Log.i(TAG, "Last one to leave the device set. Deleting " + mCurrentDS.getId());
-            mDB.deleteDeviceSet(mCurrentDS.getId());
+            getDB().deleteDeviceSet(mCurrentDS.getId());
         } else {
-            mDB.updateDeviceSet(mCurrentDS);
+            getDB().updateDeviceSet(mCurrentDS);
         }
 
         mCurrentDS = null;
@@ -358,19 +350,19 @@
     }
 
     private DeviceMeta getDeviceMeta(DeviceSet ds) {
-        String deviceId = DeviceInfoFactory.getDeviceId(this);
-
-        if (ds == null || !ds.getDevices().containsKey(deviceId)) {
+        if (ds == null || !ds.getDevices().containsKey(getDeviceId())) {
             return null;
         }
 
-        return ds.getDevices().get(deviceId);
+        return ds.getDevices().get(getDeviceId());
     }
 
     /**
      * Move all the linked pages to their previous pages.
      */
     private void prevPage() {
+        sendAction("Previous Page");
+
         if (mCurrentDS == null || mPdfView.getPageCount() <= 0) {
             return;
         }
@@ -383,7 +375,7 @@
                 dm.setPage(dm.getPage() - 1);
             }
 
-            mDB.updateDeviceSet(mCurrentDS);
+            getDB().updateDeviceSet(mCurrentDS);
             return;
         }
 
@@ -397,7 +389,7 @@
                 dm.setPage(dm.getPage() - 1);
             }
 
-            mDB.updateDeviceSet(mCurrentDS);
+            getDB().updateDeviceSet(mCurrentDS);
         }
     }
 
@@ -405,6 +397,8 @@
      * Move all the linked pages to their next pages.
      */
     private void nextPage() {
+        sendAction("Next Page");
+
         if (mCurrentDS == null || mPdfView.getPageCount() <= 0) {
             return;
         }
@@ -417,7 +411,7 @@
                 dm.setPage(dm.getPage() + 1);
             }
 
-            mDB.updateDeviceSet(mCurrentDS);
+            getDB().updateDeviceSet(mCurrentDS);
             return;
         }
 
@@ -431,7 +425,7 @@
                 dm.setPage(dm.getPage() + 1);
             }
 
-            mDB.updateDeviceSet(mCurrentDS);
+            getDB().updateDeviceSet(mCurrentDS);
         }
     }
 
@@ -478,6 +472,20 @@
         return result;
     }
 
+    /**
+     * Send an event to the tracker with the given action string.
+     */
+    private void sendAction(String action) {
+        if (getTracker() != null) {
+            getTracker().send(new HitBuilders.EventBuilder()
+                    .setCustomDimension(1, Long.toString(System.currentTimeMillis()))
+                    .setCategory(CATEGORY_PAGE_NAVIGATION)
+                    .setAction(action)
+                    .setLabel(getDeviceId())
+                    .build());
+        }
+    }
+
     private static void handleException(Exception e) {
         Log.e(TAG, e.getMessage(), e);
     }
@@ -487,7 +495,7 @@
         super.onActivityResult(requestCode, resultCode, data);
 
         Log.i(TAG, String.format("onActivityResult(%d, %d, data) called", requestCode, resultCode));
-        if (mDB.onActivityResult(requestCode, resultCode, data)) {
+        if (getDB().onActivityResult(requestCode, resultCode, data)) {
             return;
         }
 
diff --git a/android/build.gradle b/android/build.gradle
index 1b7886d..cfecc13 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -7,6 +7,9 @@
     dependencies {
         classpath 'com.android.tools.build:gradle:1.3.0'
 
+        // This is for sending log data to Google Analytics
+        classpath 'com.google.gms:google-services:1.5.0-beta2'
+
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }