reader/android: fix a few CSV logging issues
- separate UserActionLogger class to reuse it in different places
- write each line atomically
- changed the location and name of the log files
- page navigation events are also logged
Change-Id: Id1f95ea816c30797d9c8b32c5d51a423d75c6af0
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 41c5550..11e9254 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:name="ReaderApplication"
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
index d2d74e2..474dbc4 100644
--- 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
@@ -24,6 +24,7 @@
private String mDeviceId;
private DB mDB;
private Tracker mTracker;
+ private UserActionLogger mLogger;
private GestureDetectorCompat mGestureDetector;
private GestureListener mGestureListener;
@@ -35,6 +36,10 @@
return mTracker;
}
+ protected UserActionLogger getLogger() {
+ return mLogger;
+ }
+
protected String getDeviceId() {
if (mDeviceId == null) {
mDeviceId = DeviceInfoFactory.getDeviceId(this);
@@ -71,7 +76,9 @@
}
private void initTracker() {
- mGestureListener = new GestureListener(this, DeviceInfoFactory.getDeviceId(this));
+ // TODO(youngseokyoon): consolidate the Tracker into UserActionLogger
+ mLogger = UserActionLogger.getInstance(this);
+ mGestureListener = new GestureListener(this);
mGestureDetector = new GestureDetectorCompat(this, mGestureListener);
mGestureDetector.setOnDoubleTapListener(mGestureListener);
}
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
index 5aba732..8b4aa25 100644
--- 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
@@ -4,17 +4,10 @@
package io.v.android.apps.reader;
-import android.util.Log;
+import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
-import android.content.Context;
-
-import java.io.BufferedWriter;
-import java.io.FileWriter;
-import java.io.File;
-import java.io.IOException;
-import java.sql.Timestamp;
/**
* Gesture listener implementation for sending gesture events to the Google Analytics tracker.
@@ -23,122 +16,78 @@
public class GestureListener implements GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener, ScaleGestureDetector.OnScaleGestureListener {
- private static final String TAG = GestureListener.class.getSimpleName();
- private static final String CATEGORY = "Touch Gesture";
- private static BufferedWriter buffer;
+ private UserActionLogger mLogger;
- private final String mDeviceId;
-
- public GestureListener(Context context, String deviceId) {
- mDeviceId = deviceId;
-
- if (buffer == null) {
- File directory = context.getFilesDir();
- String basename = String.format("reader-%s.log", now());
- File file = new File(directory, basename);
-
- try {
- buffer = new BufferedWriter(new FileWriter(file));
- buffer.write("DEVICE ID");
- buffer.write(",");
- buffer.write("ACTION");
- buffer.write(",");
- buffer.write("TIMESTAMP");
- buffer.newLine();
- buffer.flush();
- } catch (IOException e) {
- handleException(e);
- }
- }
- }
-
- private void send(String action) {
- try {
- buffer.write(mDeviceId);
- buffer.write(",");
- buffer.write(action);
- buffer.write(",");
- buffer.write(now().toString());
- buffer.newLine();
- } catch (IOException e) {
- handleException(e);
- }
- }
-
- private Timestamp now() {
- return new Timestamp(System.currentTimeMillis());
+ public GestureListener(Context context) {
+ mLogger = UserActionLogger.getInstance(context);
}
@Override
public boolean onDown(MotionEvent e) {
- send("Down");
+ mLogger.writeAction("Down");
return true;
}
@Override
public void onShowPress(MotionEvent e) {
- send("ShowPress");
+ mLogger.writeAction("ShowPress");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
- send("SingleTapUp");
+ mLogger.writeAction("SingleTapUp");
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- send("Scroll");
+ mLogger.writeAction("Scroll");
return true;
}
@Override
public void onLongPress(MotionEvent e) {
- send("LongPress");
+ mLogger.writeAction("LongPress");
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- send("Fling");
+ mLogger.writeAction("Fling");
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
- send("SingleTapConfirmed");
+ mLogger.writeAction("SingleTapConfirmed");
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
- send("DoubleTap");
+ mLogger.writeAction("DoubleTap");
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
- send("DoubleTapEvent");
+ mLogger.writeAction("DoubleTapEvent");
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
- send("Scale");
+ mLogger.writeAction("Scale");
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
- send("ScaleBegin");
+ mLogger.writeAction("ScaleBegin");
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
- send("ScaleEnd");
- }
-
- private static void handleException(Exception e) {
- Log.e(TAG, e.getMessage(), e);
+ mLogger.writeAction("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 269c507..b39f77a 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
@@ -228,7 +228,7 @@
}
private void toggleLinkedState(boolean checked) {
- sendAction(checked ? "Unlink Page" : "Link Page");
+ writeAction(checked ? "Unlink Page" : "Link Page");
DeviceMeta dm = getDeviceMeta();
if (dm == null) {
@@ -361,7 +361,7 @@
* Move all the linked pages to their previous pages.
*/
private void prevPage() {
- sendAction("Previous Page");
+ writeAction("Previous Page");
if (mCurrentDS == null || mPdfView.getPageCount() <= 0) {
return;
@@ -397,7 +397,7 @@
* Move all the linked pages to their next pages.
*/
private void nextPage() {
- sendAction("Next Page");
+ writeAction("Next Page");
if (mCurrentDS == null || mPdfView.getPageCount() <= 0) {
return;
@@ -475,7 +475,7 @@
/**
* Send an event to the tracker with the given action string.
*/
- private void sendAction(String action) {
+ private void writeAction(String action) {
if (getTracker() != null) {
getTracker().send(new HitBuilders.EventBuilder()
.setCustomDimension(1, Long.toString(System.currentTimeMillis()))
@@ -484,6 +484,10 @@
.setLabel(getDeviceId())
.build());
}
+
+ if (getLogger() != null) {
+ getLogger().writeAction(action);
+ }
}
private static void handleException(Exception e) {
diff --git a/android/app/src/main/java/io/v/android/apps/reader/UserActionLogger.java b/android/app/src/main/java/io/v/android/apps/reader/UserActionLogger.java
new file mode 100644
index 0000000..3126f28
--- /dev/null
+++ b/android/app/src/main/java/io/v/android/apps/reader/UserActionLogger.java
@@ -0,0 +1,111 @@
+// 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.Context;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import io.v.android.apps.reader.model.DeviceInfoFactory;
+
+/**
+ * A utility class for logging user actions, such as touch gestures and button presses.
+ * Writes the user actions into a CSV file.
+ */
+public class UserActionLogger {
+
+ private static final String TAG = GestureListener.class.getSimpleName();
+
+ private static volatile UserActionLogger instance;
+
+ private String mDeviceId;
+ private BufferedWriter mWriter;
+
+ /**
+ * Singleton accessor of the UserActionLogger class.
+ */
+ public static UserActionLogger getInstance(Context context) {
+ UserActionLogger result = instance;
+ if (instance == null) {
+ synchronized (UserActionLogger.class) {
+ result = instance;
+ if (result == null) {
+ instance = result = new UserActionLogger(context);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private UserActionLogger(Context context) {
+ mDeviceId = DeviceInfoFactory.getDeviceId(context);
+
+ // Use an app-independent files directory to avoid accidentally deleting log
+ // files by clearing the app data.
+ File directory = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOCUMENTS);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+
+ // Avoid having colons in the start timestamp
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ "yyyyMMdd-HHmmss.SSS", Locale.getDefault());
+ String startTime = formatter.format(new Date());
+
+ String basename = String.format("reader-%s.log", startTime);
+ File file = new File(directory, basename);
+
+ try {
+ mWriter = new BufferedWriter(new FileWriter(file));
+ mWriter.write("DEVICE ID");
+ mWriter.write(",");
+ mWriter.write("ACTION");
+ mWriter.write(",");
+ mWriter.write("TIMESTAMP");
+ mWriter.newLine();
+ mWriter.flush();
+ } catch (IOException e) {
+ handleException(e);
+ mWriter = null;
+ }
+ }
+
+ /**
+ * Writes the given action to the CSV file.
+ *
+ * @param action name of the user action.
+ */
+ public synchronized void writeAction(String action) {
+ if (mWriter == null) {
+ return;
+ }
+
+ try {
+ mWriter.write(mDeviceId);
+ mWriter.write(",");
+ mWriter.write(action);
+ mWriter.write(",");
+ mWriter.write(Long.toString(System.currentTimeMillis()));
+ mWriter.newLine();
+ mWriter.flush();
+ } catch (IOException e) {
+ handleException(e);
+ }
+ }
+
+ private static void handleException(Exception e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+}