reader/android: Basic Syncbase setup

Change-Id: I2332f5d13a3a1f16109a5104d175373f38686cf5
diff --git a/android/app/app.iml b/android/app/app.iml
index 401ded5..027b640 100644
--- a/android/app/app.iml
+++ b/android/app/app.iml
@@ -75,6 +75,7 @@
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/21.0.3/jars" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3/jars" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.joanzapata.pdfview/android-pdfview/1.0.4/jars" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/io.v/vanadium-android/0.1-SNAPSHOT/jars" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
@@ -89,13 +90,18 @@
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
       <excludeFolder url="file://$MODULE_DIR$/build/outputs" />
       <excludeFolder url="file://$MODULE_DIR$/build/tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/build/vdltool" />
     </content>
     <orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" exported="" name="guava-18.0" level="project" />
+    <orderEntry type="library" exported="" name="joda-time-2.7" level="project" />
     <orderEntry type="library" exported="" name="recyclerview-v7-21.0.3" level="project" />
     <orderEntry type="library" exported="" name="support-v4-21.0.3" level="project" />
     <orderEntry type="library" exported="" name="android-pdfview-1.0.4" level="project" />
     <orderEntry type="library" exported="" name="support-annotations-21.0.3" level="project" />
+    <orderEntry type="library" exported="" name="vanadium-0.1-SNAPSHOT" level="project" />
+    <orderEntry type="library" exported="" name="vanadium-android-0.1-SNAPSHOT" level="project" />
     <orderEntry type="library" exported="" name="cardview-v7-21.0.3" level="project" />
   </component>
 </module>
\ No newline at end of file
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 0bd1f37..9750c0d 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,9 +1,44 @@
+buildscript {
+    repositories {
+        mavenCentral()
+        maven {
+            url 'https://maven.v.io/'
+        }
+    }
+    dependencies {
+        // This introduces the Android plugin to make building Android
+        // applications easier.
+        classpath 'com.android.tools.build:gradle:1.3.0'
+        // We are going to define a custom VDL service. The Vanadium
+        // Gradle plugin makes that easier, so let's use that.
+        classpath 'io.v:gradle-plugin:0.1-SNAPSHOT'
+        // Use the Android SDK manager, which will automatically download
+        // the required Android SDK.
+        classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0'
+    }
+}
+
+// Make our lives easier by automatically downloading the appropriate Android SDK.
+apply plugin: 'android-sdk-manager'
+// It's an Android application.
 apply plugin: 'com.android.application'
 
+repositories {
+    mavenCentral()
+    maven {
+        url 'https://maven.v.io/'
+    }
+}
+
 android {
     compileSdkVersion 21
     buildToolsVersion "21.1.2"
 
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
     defaultConfig {
         applicationId "io.v.android.apps.reader"
         minSdkVersion 21
@@ -24,4 +59,6 @@
     compile 'com.android.support:cardview-v7:21+'
     compile 'com.android.support:recyclerview-v7:21+'
     compile 'com.joanzapata.pdfview:android-pdfview:1.0.4@aar'
+    compile 'io.v:vanadium:0.1-SNAPSHOT'
+    compile 'io.v:vanadium-android:0.1-SNAPSHOT'
 }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/DB.java b/android/app/src/main/java/io/v/android/apps/reader/DB.java
index f6fa2a0..e680a3a 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/DB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/DB.java
@@ -4,7 +4,9 @@
 
 package io.v.android.apps.reader;
 
+import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
 
 /**
  * Borrowed the idea of having a DB interface from syncslides.
@@ -25,7 +27,8 @@
                     result = instance;
                     if (result == null) {
                         // TODO(youngseokyoon): Replace this with a syncbase DB.
-                        instance = result = new FakeDB(context);
+//                        instance = result = new FakeDB(context);
+                        instance = result = new SyncbaseDB(context);
                     }
                 }
             }
@@ -35,6 +38,23 @@
     }
 
     /**
+     * Perform initialization steps.  This method must be called early in the lifetime
+     * of the activity.  As part of the initialization, it might send an intent to
+     * another activity.
+     *
+     * @param activity implements onActivityResult() to call into DB.onActivityResult.
+     */
+    void init(Activity activity);
+
+    /**
+     * If init() sent an intent to another Activity, the result must be forwarded
+     * from our app's activity to this method.
+     *
+     * @return true if the requestCode matches an intent sent by this implementation.
+     */
+    boolean onActivityResult(int requestCode, int resultCode, Intent data);
+
+    /**
      * Represents a PDF file and its metadata.
      *
      * TODO(youngseokyoon): update this interface, once the syncbase DB schema is defined.
diff --git a/android/app/src/main/java/io/v/android/apps/reader/FakeDB.java b/android/app/src/main/java/io/v/android/apps/reader/FakeDB.java
index c78d431..97e94f4 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/FakeDB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/FakeDB.java
@@ -4,7 +4,9 @@
 
 package io.v.android.apps.reader;
 
+import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -75,6 +77,16 @@
         }
     }
 
+    public void init(Activity activity) {
+        // Nothing to do.
+    }
+
+    @Override
+    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+        // Nothing to do.
+        return false;
+    }
+
     @Override
     public PdfFileList getPdfFileList() {
         return mPdfFileList;
diff --git a/android/app/src/main/java/io/v/android/apps/reader/PdfChooserActivity.java b/android/app/src/main/java/io/v/android/apps/reader/PdfChooserActivity.java
index 580f494..83c499d 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/PdfChooserActivity.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/PdfChooserActivity.java
@@ -23,9 +23,15 @@
 
     private RecyclerView mRecyclerView;
     private PdfListAdapter mAdapter;
+    private DB mDB;
 
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        // Initialize the DB
+        mDB = DB.Singleton.get(this);
+        mDB.init(this);
+
         setContentView(R.layout.activity_pdf_chooser);
 
         mRecyclerView = (RecyclerView) findViewById(R.id.pdf_list);
@@ -61,7 +67,9 @@
     protected void onStop() {
         super.onStop();
 
-        mAdapter.stop();
+        if (mAdapter != null) {
+            mAdapter.stop();
+        }
         mAdapter = null;
     }
 
@@ -86,4 +94,13 @@
 
         return super.onOptionsItemSelected(item);
     }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (mDB.onActivityResult(requestCode, resultCode, data)) {
+            return;
+        }
+        // Any other activity results would be handled here.
+    }
 }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/SyncbaseDB.java b/android/app/src/main/java/io/v/android/apps/reader/SyncbaseDB.java
new file mode 100644
index 0000000..c49c684
--- /dev/null
+++ b/android/app/src/main/java/io/v/android/apps/reader/SyncbaseDB.java
@@ -0,0 +1,227 @@
+// 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.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.File;
+
+import io.v.android.libs.security.BlessingsManager;
+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.impl.google.services.syncbase.SyncbaseServer;
+import io.v.v23.context.VContext;
+import io.v.v23.rpc.Server;
+import io.v.v23.security.BlessingPattern;
+import io.v.v23.security.Blessings;
+import io.v.v23.security.VPrincipal;
+import io.v.v23.security.access.AccessList;
+import io.v.v23.security.access.Constants;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.syncbase.Syncbase;
+import io.v.v23.syncbase.SyncbaseApp;
+import io.v.v23.syncbase.SyncbaseService;
+import io.v.v23.syncbase.nosql.Database;
+import io.v.v23.syncbase.nosql.Table;
+import io.v.v23.verror.VException;
+import io.v.v23.vom.VomUtil;
+
+/**
+ * A class representing the syncbase instance.
+ */
+public class SyncbaseDB implements DB {
+
+    private static final String TAG = SyncbaseDB.class.getSimpleName();
+
+    /**
+     * The intent result code for when we get blessings from the account manager.
+     * The value must not conflict with any other blessing result codes.
+     */
+    private static final int BLESSING_REQUEST = 200;
+    private static final String SYNCBASE_APP = "reader";
+    private static final String SYNCBASE_DB = "db";
+    private static final String FILES_TABLE = "files";
+
+    private Permissions mPermissions;
+    private Context mContext;
+    private VContext vContext;
+    private Table mFiles;
+
+    SyncbaseDB(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void init(Activity activity) {
+        if (vContext != null) {
+            // Already initialized.
+            return;
+        }
+
+        vContext = V.init(mContext);
+        try {
+            vContext = V.withListenSpec(
+                    vContext, V.getListenSpec(vContext).withProxy("proxy"));
+        } catch (VException e) {
+            handleError("Couldn't setup vanadium proxy: " + e.getMessage());
+        }
+
+        AccessList acl = new AccessList(
+                ImmutableList.of(new BlessingPattern("...")), ImmutableList.<String>of());
+        mPermissions = new Permissions(ImmutableMap.of(
+                Constants.READ.getValue(), acl,
+                Constants.WRITE.getValue(), acl,
+                Constants.ADMIN.getValue(), acl,
+                Constants.RESOLVE.getValue(), acl,
+                Constants.DEBUG.getValue(), acl));
+        getBlessings(activity);
+    }
+
+    private void getBlessings(Activity activity) {
+        Blessings blessings = null;
+        try {
+            // See if there are blessings stored in shared preferences.
+            blessings = BlessingsManager.getBlessings(mContext);
+        } catch (VException e) {
+            handleError("Error getting blessings from shared preferences " + e.getMessage());
+        }
+        if (blessings == null) {
+            // Request new blessings from the account manager via an intent.  This intent
+            // will call back to onActivityResult() which will continue with
+            // configurePrincipal().
+            refreshBlessings(activity);
+            return;
+        }
+        configurePrincipal(blessings);
+    }
+
+    private void refreshBlessings(Activity activity) {
+        Intent intent = BlessingService.newBlessingIntent(mContext);
+        activity.startActivityForResult(intent, BLESSING_REQUEST);
+    }
+
+    @Override
+    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == BLESSING_REQUEST) {
+            try {
+                byte[] blessingsVom = BlessingService.extractBlessingReply(resultCode, data);
+                Blessings blessings = (Blessings) VomUtil.decode(blessingsVom, Blessings.class);
+                BlessingsManager.addBlessings(mContext, blessings);
+                Toast.makeText(mContext, "Success", Toast.LENGTH_SHORT).show();
+                configurePrincipal(blessings);
+            } catch (BlessingCreationException e) {
+                handleError("Couldn't create blessing: " + e.getMessage());
+            } catch (VException e) {
+                handleError("Couldn't derive blessing: " + e.getMessage());
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void configurePrincipal(Blessings blessings) {
+        try {
+            VPrincipal p = V.getPrincipal(vContext);
+            p.blessingStore().setDefaultBlessings(blessings);
+            p.blessingStore().set(blessings, new BlessingPattern("..."));
+            p.addToRoots(blessings);
+        } catch (VException e) {
+            handleError(String.format(
+                    "Couldn't set local blessing %s: %s", blessings, e.getMessage()));
+            return;
+        }
+        setupSyncbase(blessings);
+    }
+
+    private void setupSyncbase(Blessings blessings) {
+        // Prepare the syncbase storage directory.
+        File storageDir = new File(mContext.getFilesDir(), "syncbase");
+        storageDir.mkdirs();
+
+        try {
+            vContext = SyncbaseServer.withNewServer(
+                    vContext,
+                    new SyncbaseServer.Params()
+                            .withPermissions(mPermissions)
+                            .withStorageRootDir(storageDir.getAbsolutePath()));
+        } catch (SyncbaseServer.StartException e) {
+            handleError("Couldn't start syncbase server");
+            return;
+        }
+
+        try {
+            Server syncbaseServer = V.getServer(vContext);
+            String serverName = "/" + syncbaseServer.getStatus().getEndpoints()[0];
+            SyncbaseService service = Syncbase.newService(serverName);
+
+            Toast.makeText(mContext, "serverName: " + serverName, Toast.LENGTH_LONG).show();
+
+            SyncbaseApp app = service.getApp(SYNCBASE_APP);
+            if (!app.exists(vContext)) {
+                app.create(vContext, mPermissions);
+            }
+
+            Database db = app.getNoSqlDatabase(SYNCBASE_DB, null);
+            if (!db.exists(vContext)) {
+                db.create(vContext, mPermissions);
+            }
+
+            mFiles = db.getTable(FILES_TABLE);
+            if (!mFiles.exists(vContext)) {
+                mFiles.create(vContext, mPermissions);
+            }
+
+            // TODO(youngseokyoon): remove this temporary test.
+            testSyncbaseTable();
+        } catch (VException e) {
+            handleError("Couldn't setup syncbase service: " + e.getMessage());
+        }
+    }
+
+    private void testSyncbaseTable() throws VException {
+        mFiles.put(vContext, "testKey", "testValue", String.class);
+        String result = (String) mFiles.get(vContext, "testKey", String.class);
+        Log.d(TAG, "TestResult: " + result);
+    }
+
+    // TODO(youngseokyoon): Remove once the list is implemented properly.
+    private static class EmptyPdfFileList implements DB.PdfFileList {
+        @Override
+        public int getItemCount() {
+            return 0;
+        }
+
+        @Override
+        public PdfFile getPdfFile(int position) {
+            return null;
+        }
+
+        @Override
+        public void setListener(Listener listener) {
+        }
+
+        @Override
+        public void discard() {
+        }
+    }
+
+    @Override
+    public PdfFileList getPdfFileList() {
+        return new EmptyPdfFileList();
+    }
+
+    private void handleError(String msg) {
+        Log.e(TAG, msg);
+        Toast.makeText(mContext, msg, Toast.LENGTH_LONG).show();
+    }
+}