reader/android: store logcat logs to files

This CL stores the logcat logs to files, so that they can be retrieved
whenever needed. Currently, it keeps at most 10 recent log files and
purges old logs to save storage.

Also changes the default log verbosity settings, and now it uses
SyncbaseDB by default to make it easier for other people to test.

Change-Id: I9cbfad2669491b8aeb1268c207af70233b22b0de
diff --git a/android/app/build.gradle b/android/app/build.gradle
index d683530..40bd6b4 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -88,6 +88,7 @@
     compile 'com.android.support:recyclerview-v7:23.0.1'
     compile 'com.google.android.gms:play-services-analytics:8.3.0'
     compile 'org.apache.commons:commons-csv:1.2'
+    compile 'org.apache.commons:commons-io:1.3.2'
     compile 'io.v:vanadium-android:0.5'
     compile 'io.v:baku-toolkit:0.3.0'
 
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
index fbd3664..2587d7c 100644
--- 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
@@ -14,7 +14,7 @@
  * This file is conditionally included to the project, when the "google-services.json" file does not
  * exist.
  */
-public class ReaderApplication extends Application {
+public class ReaderApplication extends BaseReaderApplication {
     /**
      * Gets the default {@link Tracker} for this {@link Application}.
      * Always returns null to disable tracking.
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
index 8fd5363..97e9879 100644
--- 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
@@ -15,7 +15,7 @@
  *
  * This file is conditionally included to the project, when the "google-services.json" file exists.
  */
-public class ReaderApplication extends Application {
+public class ReaderApplication extends BaseReaderApplication {
     private Tracker mTracker;
 
     /**
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 11e9254..0929a35 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.READ_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <application
diff --git a/android/app/src/main/java/io/v/android/apps/reader/BaseReaderApplication.java b/android/app/src/main/java/io/v/android/apps/reader/BaseReaderApplication.java
new file mode 100644
index 0000000..93917ab
--- /dev/null
+++ b/android/app/src/main/java/io/v/android/apps/reader/BaseReaderApplication.java
@@ -0,0 +1,105 @@
+// 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 android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import io.v.android.apps.reader.model.DeviceInfoFactory;
+import io.v.baku.toolkit.debug.DebugUtils;
+
+/**
+ * Base application class that contains logic which is shared whether or not the Google Analytics
+ * integration is in place.
+ *
+ * This class is responsible for saving logcat logs to the external storage, and clearing old logs
+ * upon app startup.
+ */
+public abstract class BaseReaderApplication extends Application {
+
+    private static int MAX_LOG_COUNT = 10;
+    private static final String APP_NAME = "reader";
+    private static final String TAG = BaseReaderApplication.class.getSimpleName();
+
+    private String mDeviceId;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        mDeviceId = DeviceInfoFactory.getDeviceId(this);
+
+        // Only save logcat logs in debug mode.
+        if (DebugUtils.isApkDebug(this)) {
+            startSavingLogs();
+        }
+    }
+
+    private void startSavingLogs() {
+        // Use an app-independent files directory to avoid accidentally deleting log
+        // files by clearing the app data.
+        File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
+        if (!dir.exists()) {
+            dir.mkdirs();
+        }
+
+        Log.i(TAG, "Logcat logs are saved at: " + dir.getAbsolutePath());
+
+        deleteOldLogs(dir);
+
+        // Avoid having colons in the start timestamp
+        String startTime = getTimeString();
+
+        File logcatFile = new File(dir,
+                String.format("%s-%s.log", getLogPrefix(), startTime));
+
+        try {
+            // Clear the previous logs
+            Runtime.getRuntime().exec("logcat -c");
+            Runtime.getRuntime().exec(String.format("logcat -v time -f %s", logcatFile.getCanonicalPath()));
+        } catch (IOException e) {
+            Log.e(TAG, "Could not start writing the logcat file.", e);
+        }
+    }
+
+    private void deleteOldLogs(File dir) {
+        List<File> logFiles = Arrays.asList(dir.listFiles(new FilenameFilter() {
+            @Override
+            public boolean accept(File file, String s) {
+                return s.startsWith(getLogPrefix());
+            }
+        }));
+
+        if (logFiles.size() >= MAX_LOG_COUNT) {
+            Collections.sort(logFiles);
+            Collections.reverse(logFiles);
+            for (File oldLog : logFiles.subList(0, logFiles.size() - MAX_LOG_COUNT + 1)) {
+                oldLog.delete();
+            }
+        }
+    }
+
+    private String getLogPrefix() {
+        return String.format("%s-%s-logcat", APP_NAME, mDeviceId);
+    }
+
+    private String getTimeString() {
+        SimpleDateFormat formatter = new SimpleDateFormat(
+                "yyyyMMdd-HHmmss.SSS", Locale.getDefault());
+        return formatter.format(new Date());
+    }
+
+}
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/DB.java b/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
index a5b781c..582c304 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
@@ -30,8 +30,8 @@
                     result = instance;
                     if (result == null) {
                         // uncomment either one
-                        instance = result = new FakeDB(context);
-//                        instance = result = new SyncbaseDB(context);
+//                        instance = result = new FakeDB(context);
+                        instance = result = new SyncbaseDB(context);
                     }
                 }
             }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java b/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
index 2fe4b6e..011641d 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
@@ -17,6 +17,8 @@
 import com.google.common.collect.Lists;
 import com.google.common.io.ByteStreams;
 
+import org.apache.commons.io.FileUtils;
+
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.ArrayList;
@@ -33,9 +35,13 @@
 import io.v.android.v23.V;
 import io.v.android.v23.services.blessing.BlessingCreationException;
 import io.v.android.v23.services.blessing.BlessingService;
+import io.v.baku.toolkit.VAndroidContextMixin;
+import io.v.baku.toolkit.debug.DebugUtils;
 import io.v.impl.google.naming.NamingUtil;
 import io.v.impl.google.services.syncbase.SyncbaseServer;
 import io.v.v23.InputChannels;
+import io.v.v23.OptionDefs;
+import io.v.v23.Options;
 import io.v.v23.VIterable;
 import io.v.v23.context.CancelableVContext;
 import io.v.v23.context.VContext;
@@ -113,7 +119,22 @@
         }
 
         if (mVContext == null) {
-            mVContext = V.init(mContext);
+            if (activity instanceof VAndroidContextMixin) {
+                // In case of the activity inherits from one of the baku-toolkit's base activities,
+                // retrieve the Vanadium context from there directly.
+                mVContext = ((VAndroidContextMixin) activity)
+                        .getVAndroidContextTrait().getVContext();
+            } else {
+                // Otherwise, initialize Vanadium runtime here with -vmodule=*=5 setting.
+                if (DebugUtils.isApkDebug(activity)) {
+                    Options opts = new Options();
+                    opts.set(OptionDefs.LOG_VMODULE, "*=5");
+                    mVContext = V.init(mContext, opts);
+                } else {
+                    mVContext = V.init(mContext);
+                }
+            }
+
             try {
                 mVContext = V.withListenSpec(
                         mVContext, V.getListenSpec(mVContext).withProxy("proxy"));
@@ -211,6 +232,15 @@
 
         // Prepare the syncbase storage directory.
         java.io.File storageDir = new java.io.File(mContext.getFilesDir(), "syncbase");
+
+        // Clear the contents of local syncbase DB.
+        // TODO(youngseokyoon): remove this once Syncbase can properly handle locally stored data.
+        try {
+            FileUtils.deleteDirectory(storageDir);
+        } catch (IOException e) {
+            handleError("Couldn't clear the syncbase storage directory");
+        }
+
         storageDir.mkdirs();
 
         try {
diff --git a/web/Makefile b/web/Makefile
index 9d2c4ee..a10e452 100644
--- a/web/Makefile
+++ b/web/Makefile
@@ -107,7 +107,7 @@
 	$(eval blessing := $(shell bin/principal dump --v23.credentials=./credentials -s=true))
 	$(eval email := $(subst dev.v.io:u:,,$(blessing)))
 	./bin/syncbased \
-		--v=5 \
+		--vmodule=*=5 \
 		--alsologtostderr=false \
 		--root-dir="tmp/syncbase_$(id)" \
 		--name="users/$(email)/reader/$(id)/syncbase" \
@@ -122,7 +122,7 @@
 	$(eval blessing := $(shell bin/principal dump --v23.credentials=./credentials -s=true))
 	$(eval email := $(subst dev.v.io:u:,,$(blessing)))
 	./bin/syncbased \
-		--v=5 \
+		--vmodule=*=5 \
 		--alsologtostderr=false \
 		--root-dir="tmp/cloudsync" \
 		--name="users/$(email)/reader/cloudsync" \