Enable remote inspection of syncbase/firebase.

This commit hooks up the Vanadium RemoteInspectors to the syncbase
server and also provides a means to send instructions for
debugging/inspecting the persistence layer (syncbase or firebase)
via email.

Of course, the way firebase is setup in the app right now, there are no
credentials required as the data store is accessible to all.

The syncbase setup provides a token that gives access to the debug
services for 1 day.

Change-Id: Id43affa50bee6b4cf774ce228e67cafe27b3d411
diff --git a/app/src/firebase/java/io/v/todos/persistence/firebase/FirebasePersistence.java b/app/src/firebase/java/io/v/todos/persistence/firebase/FirebasePersistence.java
index b2af391..f6f9e34 100644
--- a/app/src/firebase/java/io/v/todos/persistence/firebase/FirebasePersistence.java
+++ b/app/src/firebase/java/io/v/todos/persistence/firebase/FirebasePersistence.java
@@ -43,4 +43,9 @@
 
         mFirebase = new Firebase(FIREBASE_EXAMPLE_URL);
     }
+
+    @Override
+    public String debugDetails() {
+        return FIREBASE_EXAMPLE_URL;
+    }
 }
diff --git a/app/src/main/java/io/v/todos/MainActivity.java b/app/src/main/java/io/v/todos/MainActivity.java
index 543cc12..8a92762 100644
--- a/app/src/main/java/io/v/todos/MainActivity.java
+++ b/app/src/main/java/io/v/todos/MainActivity.java
@@ -179,7 +179,11 @@
         if (id == R.id.action_settings) {
             return true;
         }
+        if (id == R.id.action_debug) {
+            sharePersistenceDebugDetails();
+            return true;
+        }
 
         return super.onOptionsItemSelected(item);
     }
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/v/todos/TodoListActivity.java b/app/src/main/java/io/v/todos/TodoListActivity.java
index dffe1ec..092e00a 100644
--- a/app/src/main/java/io/v/todos/TodoListActivity.java
+++ b/app/src/main/java/io/v/todos/TodoListActivity.java
@@ -227,6 +227,9 @@
             case R.id.action_edit:
                 initiateTodoListEdit();
                 return true;
+            case R.id.action_debug:
+                sharePersistenceDebugDetails();
+                return true;
             case R.id.action_share:
                 // TODO(alexfandrianto): We should figure out who is near us.
                 List<String> fakeNearby = new ArrayList<>();
diff --git a/app/src/main/java/io/v/todos/TodosAppActivity.java b/app/src/main/java/io/v/todos/TodosAppActivity.java
index 40fc6ad..76cc575 100644
--- a/app/src/main/java/io/v/todos/TodosAppActivity.java
+++ b/app/src/main/java/io/v/todos/TodosAppActivity.java
@@ -7,11 +7,13 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.widget.RecyclerView;
 import android.view.View;
 import android.widget.TextView;
+import android.widget.Toast;
 import android.widget.Toolbar;
 
 import io.v.todos.persistence.Persistence;
@@ -80,4 +82,23 @@
                     });
         }
     }
+
+    /**
+     * Share debugging information for the persistence layer.
+     */
+    protected void sharePersistenceDebugDetails() {
+        if (mPersistence == null) {
+            return;
+        }
+        final Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.setType("message/rfc822");
+        intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.email_debug_subject));
+        intent.putExtra(Intent.EXTRA_TEXT, mPersistence.debugDetails());
+
+        if (intent.resolveActivity(getPackageManager()) != null) {
+            startActivity(intent);
+        } else {
+            Toast.makeText(this, getString(R.string.no_email_client), Toast.LENGTH_LONG).show();
+        }
+    }
 }
diff --git a/app/src/main/java/io/v/todos/persistence/Persistence.java b/app/src/main/java/io/v/todos/persistence/Persistence.java
index 7b74e2d..ce26385 100644
--- a/app/src/main/java/io/v/todos/persistence/Persistence.java
+++ b/app/src/main/java/io/v/todos/persistence/Persistence.java
@@ -6,4 +6,12 @@
 
 public interface Persistence extends AutoCloseable {
     void close();
+
+    /**
+     * debugDetails provides information useful for debugging the state of the Persistence object.
+     *
+     * @return textual content to be shared with a human being to describe or inspect the state
+     * of the object.
+     */
+    String debugDetails();
 }
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
index aaafc2e..0be017d 100644
--- a/app/src/main/res/menu/menu_main.xml
+++ b/app/src/main/res/menu/menu_main.xml
@@ -7,4 +7,9 @@
         android:orderInCategory="100"
         android:title="@string/action_settings"
         app:showAsAction="never" />
+    <item
+        android:id="@+id/action_debug"
+        android:orderInCategory="102"
+        android:title="@string/action_debug"
+        app:showAsAction="never" />
 </menu>
diff --git a/app/src/main/res/menu/menu_task.xml b/app/src/main/res/menu/menu_task.xml
index 4efce1c..8540c76 100644
--- a/app/src/main/res/menu/menu_task.xml
+++ b/app/src/main/res/menu/menu_task.xml
@@ -22,4 +22,9 @@
         android:orderInCategory="103"
         android:title="@string/action_share"
         app:showAsAction="never" />
+    <item
+        android:id="@+id/action_debug"
+        android:orderInCategory="104"
+        android:title="@string/action_debug"
+        app:showAsAction="never" />
 </menu>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fbc37bc..839ae8d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,6 @@
 <resources>
     <string name="action_settings">Settings</string>
+    <string name="action_debug">Debug</string>
     <string name="action_edit">Edit</string>
     <string name="action_share">Share</string>
     <string name="show_done">Show Done</string>
@@ -9,6 +10,8 @@
     <string name="no_lists">No todo lists.\nPress \'+\' to add lists.</string>
     <string name="no_tasks">No tasks.\nPress \'+\' to add tasks.</string>
     <string name="just_now">Just now</string>
+    <string name="email_debug_subject">Debug the persistence layer</string>
+    <string name="no_email_client">An app to share emails is not installed, cannot send debug request</string>
     <!-- For Sharing Menu -->
     <string name="sharing_already">Sharing With</string>
     <string name="sharing_possible">Nearby</string>
diff --git a/app/src/mock/java/io/v/todos/persistence/PersistenceFactory.java b/app/src/mock/java/io/v/todos/persistence/PersistenceFactory.java
index 3d90b66..2619945 100644
--- a/app/src/mock/java/io/v/todos/persistence/PersistenceFactory.java
+++ b/app/src/mock/java/io/v/todos/persistence/PersistenceFactory.java
@@ -65,6 +65,11 @@
         @Override
         public void close() {
         }
+
+        @Override
+        public String debugDetails() {
+            return "";
+        }
     }
 
     static class MockTodoListPersistence implements TodoListPersistence {
@@ -99,5 +104,10 @@
         @Override
         public void close() {
         }
+
+        @Override
+        public String debugDetails() {
+            return "";
+        }
     }
 }
diff --git a/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbasePersistence.java b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbasePersistence.java
index 742f5b3..455f637 100644
--- a/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbasePersistence.java
+++ b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbasePersistence.java
@@ -23,13 +23,16 @@
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.SettableFuture;
 
+import org.joda.time.DateTime;
 import org.joda.time.Duration;
+import org.joda.time.format.DateTimeFormat;
 
 import java.io.File;
 import java.util.concurrent.Executors;
 
 import javax.annotation.Nullable;
 
+import io.v.android.inspectors.RemoteInspectors;
 import io.v.android.VAndroidContext;
 import io.v.android.VAndroidContexts;
 import io.v.android.security.BlessingsManager;
@@ -84,10 +87,10 @@
     public static final String
             USER_COLLECTION_NAME = "userdata",
             MOUNTPOINT = "/ns.dev.v.io:8101/tmp/todos/users/",
-            CLOUD_NAME = MOUNTPOINT + "cloud";
+            CLOUD_NAME = MOUNTPOINT + "cloud",
+            // TODO(alexfandrianto): This shouldn't be me running the cloud.
+            CLOUD_BLESSING = "dev.v.io:u:alexfandrianto@google.com";
 
-    // TODO(alexfandrianto): This shouldn't be me running the cloud.
-    public static final String CLOUD_BLESSING = "dev.v.io:u:alexfandrianto@google.com";
     // BlessingPattern initialization has to be deferred until after V23 init due to native binding.
     private static final Supplier<AccessList> OPEN_ACL = Suppliers.memoize(
             new Supplier<AccessList>() {
@@ -104,6 +107,7 @@
     private static final Object sSyncbaseMutex = new Object();
     private static VContext sVContext;
     private static SyncbaseService sSyncbase;
+    private static RemoteInspectors sRemoteInspectors;
 
     private static String startSyncbaseServer(VContext vContext, Context appContext,
                                               Permissions serverPermissions)
@@ -126,7 +130,16 @@
         VContext serverContext = SyncbaseServer.withNewServer(vContext, params);
 
         Server server = V.getServer(serverContext);
-        return "/" + server.getStatus().getEndpoints()[0];
+        try {
+            sRemoteInspectors = new RemoteInspectors(serverContext);
+        } catch (VException e) {
+            Log.w(TAG, "Unable to start remote inspection service:" + e);
+        }
+        // TODO(ashankar): This is not a good idea. For one, endpoints of a service may change
+        // as the device changes networks. But I believe in a few weeks (end of May 2016) we'll
+        // switch to a mode where there are no "local RPCs" between the syncbase client and the
+        // server, so this will hopefully go away before it matters.
+        return server.getStatus().getEndpoints()[0].name();
     }
 
     /**
@@ -455,4 +468,19 @@
         ensureUserSyncgroupExists();
         sInitialized = true;
     }
+
+    @Override
+    public String debugDetails() {
+        synchronized (sSyncbaseMutex) {
+            if (sRemoteInspectors == null) {
+                return "Syncbase has not been initialized";
+            }
+            final String timestamp = DateTimeFormat.forPattern("yyyy-MM-dd").print(new DateTime());
+            try {
+                return sRemoteInspectors.invite("invited-on-"+timestamp, Duration.standardDays(1));
+            } catch (VException e) {
+                return "Unable to setup remote inspection: " + e;
+            }
+        }
+    }
 }