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();
+ }
+}