Syncbase startup and database setup

Without J8/Rx, asyncs are super unwieldy; for simplicity, just block
sequential asyncs and do everything on a worker thread.

Change-Id: If30130d6fbdb2fafbe80190c5f72be407e8a4ad1
diff --git a/app/build.gradle b/app/build.gradle
index ed30d8b..91d201b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,6 +8,7 @@
     defaultConfig {
         applicationId "io.v.todos"
         minSdkVersion 21
+        multiDexEnabled true
         targetSdkVersion 23
         versionCode 1
         versionName "1.0"
@@ -22,7 +23,6 @@
     }
     buildTypes {
         release {
-            minifyEnabled false
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }
@@ -45,5 +45,9 @@
             'com.android.support:cardview-v7:23.1.1',
             'com.android.support:recyclerview-v7:23.1.1'
     )
-    firebaseCompile 'com.firebase:firebase-client-android:2.5.2'
+    firebaseCompile(
+            'com.firebase:firebase-client-android:2.5.2',
+            'com.google.guava:guava:19.0'
+    )
+    syncbaseCompile 'io.v:vanadium-android:2.0'
 }
diff --git a/app/src/firebase/java/io/v/todos/persistence/PersistenceFactory.java b/app/src/firebase/java/io/v/todos/persistence/PersistenceFactory.java
index 07a13c7..4d924be 100644
--- a/app/src/firebase/java/io/v/todos/persistence/PersistenceFactory.java
+++ b/app/src/firebase/java/io/v/todos/persistence/PersistenceFactory.java
@@ -4,7 +4,7 @@
 
 package io.v.todos.persistence;
 
-import android.content.Context;
+import android.app.Activity;
 
 import io.v.todos.model.ListMetadata;
 import io.v.todos.persistence.firebase.FirebaseMain;
@@ -14,22 +14,35 @@
     private PersistenceFactory(){}
 
     /**
-     * Instantiates a persistence object that can be used to manipulate todo lists.
-     *
-     * @param context an Android context, usually from an Android activity or application
+     * Indicates whether {@link #getMainPersistence(Activity, ListEventListener)} may block. This
+     * can affect whether a progress indicator is shown and whether a worker thread is used.
      */
-    public static MainPersistence getMainPersistence(Context context,
+    public static boolean mightGetMainPersistenceBlock() {
+        return false;
+    }
+
+    /**
+     * Instantiates a persistence object that can be used to manipulate todo lists.
+     */
+    public static MainPersistence getMainPersistence(Activity activity,
                                                      ListEventListener<ListMetadata> listener) {
-        return new FirebaseMain(context, listener);
+        return new FirebaseMain(activity, listener);
+    }
+
+    /**
+     * Indicates whether {@link #getTodoListPersistence(Activity, String, TodoListListener)} may
+     * block. This can affect whether a progress indicator is shown and whether a worker thread is
+     * used.
+     */
+    public static boolean mightGetTodoListPersistenceBlock() {
+        return false;
     }
 
     /**
      * Instantiates a persistence object that can be used to manipulate a todo list.
-     *
-     * @param context an Android context, usually from an Android activity or application
      */
-    public static TodoListPersistence getTodoListPersistence(Context context, String key,
+    public static TodoListPersistence getTodoListPersistence(Activity activity, String key,
                                                              TodoListListener listener) {
-        return new FirebaseTodoList(context, key, listener);
+        return new FirebaseTodoList(activity, key, listener);
     }
 }
diff --git a/app/src/main/java/io/v/todos/MainActivity.java b/app/src/main/java/io/v/todos/MainActivity.java
index ec9fcee..4d39028 100644
--- a/app/src/main/java/io/v/todos/MainActivity.java
+++ b/app/src/main/java/io/v/todos/MainActivity.java
@@ -32,6 +32,8 @@
  * @author alexfandrianto
  */
 public class MainActivity extends Activity {
+    private static final String TAG = "MainActivity";
+
     private MainPersistence mPersistence;
 
     // Snackoos are the code name for the list of todos.
@@ -46,7 +48,10 @@
 
     @Override
     protected void onDestroy() {
-        mPersistence.close();
+        if (mPersistence != null) {
+            mPersistence.close();
+            mPersistence = null;
+        }
         super.onDestroy();
     }
 
@@ -98,33 +103,43 @@
             }
         }).attachToRecyclerView(recyclerView);
 
-        mPersistence = PersistenceFactory.getMainPersistence(this,
-                new ListEventListener<ListMetadata>() {
+        new PersistenceInitializer<MainPersistence>(this) {
             @Override
-            public void onItemAdd(ListMetadata item) {
-                int position = snackoosList.insertInOrder(item);
+            protected MainPersistence initPersistence() throws Exception {
+                return PersistenceFactory.getMainPersistence(mActivity,
+                        new ListEventListener<ListMetadata>() {
+                            @Override
+                            public void onItemAdd(ListMetadata item) {
+                                int position = snackoosList.insertInOrder(item);
 
-                adapter.notifyItemInserted(position);
-                setEmptyVisiblity();
+                                adapter.notifyItemInserted(position);
+                                setEmptyVisiblity();
+                            }
+
+                            @Override
+                            public void onItemUpdate(ListMetadata item) {
+                                int start = snackoosList.findIndexByKey(item.key);
+                                int end = snackoosList.updateInOrder(item);
+
+                                adapter.notifyItemMoved(start, end);
+                                adapter.notifyItemChanged(end);
+                            }
+
+                            @Override
+                            public void onItemDelete(String key) {
+                                int position = snackoosList.removeByKey(key);
+
+                                adapter.notifyItemRemoved(position);
+                                setEmptyVisiblity();
+                            }
+                        });
             }
 
             @Override
-            public void onItemUpdate(ListMetadata item) {
-                int start = snackoosList.findIndexByKey(item.key);
-                int end = snackoosList.updateInOrder(item);
-
-                adapter.notifyItemMoved(start, end);
-                adapter.notifyItemChanged(end);
+            protected void onSuccess(MainPersistence persistence) {
+                mPersistence = persistence;
             }
-
-            @Override
-            public void onItemDelete(String key) {
-                int position = snackoosList.removeByKey(key);
-
-                adapter.notifyItemRemoved(position);
-                setEmptyVisiblity();
-            }
-        });
+        }.execute(PersistenceFactory.mightGetMainPersistenceBlock());
     }
 
     // Set the visibility based on what the adapter thinks is the visible item count.
diff --git a/app/src/main/java/io/v/todos/PersistenceInitializer.java b/app/src/main/java/io/v/todos/PersistenceInitializer.java
new file mode 100644
index 0000000..dd79802
--- /dev/null
+++ b/app/src/main/java/io/v/todos/PersistenceInitializer.java
@@ -0,0 +1,87 @@
+// Copyright 2016 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.todos;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.common.base.Throwables;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import io.v.todos.persistence.Persistence;
+
+/**
+ * Persistence initialization may or may not block. For non-blocking persistence initialization,
+ * this class simply calls {@link #initPersistence()} and
+ * {@link #onSuccess(Persistence)}/{@link #onFailure(Exception)} in a single thread. For blocking
+ * persistence initialization, it shows a {@link ProgressDialog} while initializing persistence on
+ * a worker thread. This obscures the main UI in a friendly way until persistence is ready.
+ */
+public abstract class PersistenceInitializer<T extends Persistence> {
+    private static final String TAG = PersistenceInitializer.class.getSimpleName();
+    // TODO(rosswang): https://github.com/vanadium/issues/issues/1309
+    private static final Executor sInitExecutor = Executors.newCachedThreadPool();
+
+    protected final Activity mActivity;
+
+    public PersistenceInitializer(Activity activity) {
+        mActivity = activity;
+    }
+
+    protected abstract T initPersistence() throws Exception;
+
+    protected abstract void onSuccess(T persistence);
+
+    protected void onFailure(Exception e) {
+        Toast.makeText(mActivity, R.string.err_init, Toast.LENGTH_LONG).show();
+        Log.e(TAG, Throwables.getStackTraceAsString(e));
+        mActivity.finish();
+    }
+
+    public void execute(boolean mayBlock) {
+        if (mayBlock) {
+            final ProgressDialog progress = new ProgressDialog(mActivity);
+            progress.setMessage(mActivity.getString(R.string.init_persistence));
+            progress.setCancelable(false);
+            progress.show();
+
+            new AsyncTask<Void, Void, Object>() {
+                @Override
+                protected Object doInBackground(Void... params) {
+                    try {
+                        return initPersistence();
+                    } catch (Exception e) {
+                        return e;
+                    }
+                }
+
+                @Override
+                @SuppressWarnings("unchecked")
+                protected void onPostExecute(Object o) {
+                    if (o instanceof Exception) {
+                        onFailure((Exception) o);
+                    } else {
+                        onSuccess((T) o);
+                    }
+                    progress.dismiss();
+                }
+            }.executeOnExecutor(sInitExecutor);
+        } else {
+            T persistence;
+            try {
+                persistence = initPersistence();
+            } catch (Exception e) {
+                onFailure(e);
+                return;
+            }
+            onSuccess(persistence);
+        }
+    }
+}
diff --git a/app/src/main/java/io/v/todos/TodoListActivity.java b/app/src/main/java/io/v/todos/TodoListActivity.java
index 7d22c42..ebe2e68 100644
--- a/app/src/main/java/io/v/todos/TodoListActivity.java
+++ b/app/src/main/java/io/v/todos/TodoListActivity.java
@@ -38,7 +38,7 @@
     private TodoListPersistence mPersistence;
 
     private ListSpec snackoo;
-    private DataList<Task> snackoosList = new DataList<Task>();
+    private DataList<Task> snackoosList = new DataList<>();
 
     // This adapter handle mirrors the firebase list values and generates the corresponding todo
     // item View children for a list view.
@@ -50,7 +50,10 @@
 
     @Override
     protected void onDestroy() {
-        mPersistence.close();
+        if (mPersistence != null) {
+            mPersistence.close();
+            mPersistence = null;
+        }
         super.onDestroy();
     }
 
@@ -59,7 +62,7 @@
         setContentView(R.layout.activity_main);
 
         Intent intent = getIntent();
-        String snackooKey = intent.getStringExtra(MainActivity.INTENT_SNACKOO_KEY);
+        final String snackooKey = intent.getStringExtra(MainActivity.INTENT_SNACKOO_KEY);
 
         Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
         setActionBar(toolbar);
@@ -89,61 +92,70 @@
             }
         }).attachToRecyclerView(recyclerView);
 
-        mPersistence = PersistenceFactory.getTodoListPersistence(this, snackooKey,
-                new TodoListListener() {
+        new PersistenceInitializer<TodoListPersistence>(this) {
             @Override
-            public void onUpdate(ListSpec value) {
-                snackoo = value;
-                getActionBar().setTitle(snackoo.getName());
+            protected TodoListPersistence initPersistence() throws Exception {
+                return PersistenceFactory.getTodoListPersistence(mActivity, snackooKey,
+                        new TodoListListener() {
+                            @Override
+                            public void onUpdate(ListSpec value) {
+                                snackoo = value;
+                                getActionBar().setTitle(snackoo.getName());
+                            }
+
+                            @Override
+                            public void onDelete() {
+                                finish();
+                            }
+
+                            @Override
+                            public void onUpdateShowDone(boolean showDone) {
+                                mShowDone = showDone;
+                                if (mShowDoneMenuItem != null) {
+                                    // Only interact with mShowDoneMenu if it has been inflated.
+                                    mShowDoneMenuItem.setChecked(showDone);
+                                }
+
+                                int oldSize = adapter.getItemCount();
+                                adapter.setShowDone(showDone);
+                                int newSize = adapter.getItemCount();
+                                if (newSize > oldSize) {
+                                    adapter.notifyItemRangeInserted(oldSize, newSize - oldSize);
+                                } else {
+                                    adapter.notifyItemRangeRemoved(newSize, oldSize - newSize);
+                                }
+                                setEmptyVisiblity();
+                            }
+
+                            @Override
+                            public void onItemAdd(Task item) {
+                                int position = snackoosList.insertInOrder(item);
+                                adapter.notifyItemInserted(position);
+                                setEmptyVisiblity();
+                            }
+
+                            @Override
+                            public void onItemUpdate(Task item) {
+                                int start = snackoosList.findIndexByKey(item.key);
+                                int end = snackoosList.updateInOrder(item);
+                                adapter.notifyItemMoved(start, end);
+                                adapter.notifyItemChanged(end);
+                                setEmptyVisiblity();
+                            }
+
+                            @Override
+                            public void onItemDelete(String key) {
+                                int position = snackoosList.removeByKey(key);
+                                adapter.notifyItemRemoved(position);
+                                setEmptyVisiblity();
+                            }
+                        });
             }
 
-            @Override
-            public void onDelete() {
-                finish();
+            protected void onSuccess(TodoListPersistence persistence) {
+                mPersistence = persistence;
             }
-
-            @Override
-            public void onUpdateShowDone(boolean showDone) {
-                mShowDone = showDone;
-                if (mShowDoneMenuItem != null) {
-                    // Only interact with mShowDoneMenu if it has been inflated.
-                    mShowDoneMenuItem.setChecked(showDone);
-                }
-
-                int oldSize = adapter.getItemCount();
-                adapter.setShowDone(showDone);
-                int newSize = adapter.getItemCount();
-                if (newSize > oldSize) {
-                    adapter.notifyItemRangeInserted(oldSize, newSize - oldSize);
-                } else {
-                    adapter.notifyItemRangeRemoved(newSize, oldSize - newSize);
-                }
-                setEmptyVisiblity();
-            }
-
-            @Override
-            public void onItemAdd(Task item) {
-                int position = snackoosList.insertInOrder(item);
-                adapter.notifyItemInserted(position);
-                setEmptyVisiblity();
-            }
-
-            @Override
-            public void onItemUpdate(Task item) {
-                int start = snackoosList.findIndexByKey(item.key);
-                int end = snackoosList.updateInOrder(item);
-                adapter.notifyItemMoved(start, end);
-                adapter.notifyItemChanged(end);
-                setEmptyVisiblity();
-            }
-
-            @Override
-            public void onItemDelete(String key) {
-                int position = snackoosList.removeByKey(key);
-                adapter.notifyItemRemoved(position);
-                setEmptyVisiblity();
-            }
-        });
+        }.execute(PersistenceFactory.mightGetTodoListPersistenceBlock());
     }
 
     // Set the visibility based on what the adapter thinks is the visible item count.
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index be63f07..a9a7282 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -7,4 +7,8 @@
     <string name="show_done">Show Done</string>
     <string name="set_button">Set</string>
     <string name="add_button">Add</string>
+    <string name="init_persistence">Initializing&#8230;</string>
+    <!-- Errors -->
+    <string name="err_init">Unable to initialize persistence</string>
+    <string name="err_sync">Unable to sync</string>
 </resources>
diff --git a/app/src/syncbase/java/io/v/todos/persistence/PersistenceFactory.java b/app/src/syncbase/java/io/v/todos/persistence/PersistenceFactory.java
index 9fa6855..7960ed7 100644
--- a/app/src/syncbase/java/io/v/todos/persistence/PersistenceFactory.java
+++ b/app/src/syncbase/java/io/v/todos/persistence/PersistenceFactory.java
@@ -4,30 +4,47 @@
 
 package io.v.todos.persistence;
 
-import android.content.Context;
+import android.app.Activity;
 
+import io.v.impl.google.services.syncbase.SyncbaseServer;
 import io.v.todos.model.ListMetadata;
+import io.v.todos.persistence.syncbase.SyncbaseMain;
+import io.v.v23.verror.VException;
 
 public final class PersistenceFactory {
     private PersistenceFactory(){}
 
     /**
-     * Instantiates a persistence object that can be used to manipulate todo lists.
-     *
-     * @param context an Android context, usually from an Android activity or application
+     * Indicates whether {@link #getMainPersistence(Activity, ListEventListener)} may block. This
+     * can affect whether a progress indicator is shown and whether a worker thread is used.
      */
-    public static MainPersistence getMainPersistence(Context context,
-                                                     ListEventListener<ListMetadata> listener) {
-        throw new RuntimeException("Unsupported product flavor.");
+    public static boolean mightGetMainPersistenceBlock() {
+        return !SyncbaseMain.isInitialized();
+    }
+
+    /**
+     * Instantiates a persistence object that can be used to manipulate todo lists.
+     */
+    public static MainPersistence getMainPersistence(
+            Activity activity, ListEventListener<ListMetadata> listener)
+            throws VException, SyncbaseServer.StartException {
+        return new SyncbaseMain(activity, listener);
+    }
+
+    /**
+     * Indicates whether {@link #getTodoListPersistence(Activity, String, TodoListListener)} may
+     * block. This can affect whether a progress indicator is shown and whether a worker thread is
+     * used.
+     */
+    public static boolean mightGetTodoListPersistenceBlock() {
+        return false;
     }
 
     /**
      * Instantiates a persistence object that can be used to manipulate a todo list.
-     *
-     * @param context an Android context, usually from an Android activity or application
      */
-    public static TodoListPersistence getTodoListPersistence(Context context, String key,
-                                                             TodoListListener listener) {
+    public static TodoListPersistence getTodoListPersistence(
+            Activity activity, String key, TodoListListener listener) throws VException {
         throw new RuntimeException("Unsupported product flavor.");
     }
 }
diff --git a/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseMain.java b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseMain.java
new file mode 100644
index 0000000..8c7ae05
--- /dev/null
+++ b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseMain.java
@@ -0,0 +1,64 @@
+// Copyright 2016 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.todos.persistence.syncbase;
+
+import android.app.Activity;
+
+import io.v.impl.google.services.syncbase.SyncbaseServer;
+import io.v.todos.model.ListMetadata;
+import io.v.todos.model.ListSpec;
+import io.v.todos.persistence.ListEventListener;
+import io.v.todos.persistence.MainPersistence;
+import io.v.v23.VFutures;
+import io.v.v23.syncbase.Collection;
+import io.v.v23.verror.ExistException;
+import io.v.v23.verror.VException;
+
+public class SyncbaseMain extends SyncbasePersistence implements MainPersistence {
+    public static final String MAIN_COLLECTION_NAME = "userdata";
+
+    private static final Object sMainCollectionMutex = new Object();
+    private static volatile Collection sMainCollection;
+
+    public static boolean isInitialized() {
+        return sMainCollection != null;
+    }
+
+    /**
+     * This constructor blocks until the instance is ready for use.
+     */
+    public SyncbaseMain(Activity activity, ListEventListener<ListMetadata> listener)
+            throws VException, SyncbaseServer.StartException {
+        super(activity);
+
+        synchronized (sMainCollectionMutex) {
+            if (sMainCollection == null) {
+                Collection mainCollection = getDatabase()
+                        .getCollection(mVContext, MAIN_COLLECTION_NAME);
+                try {
+                    VFutures.sync(mainCollection.create(mVContext, null));
+                } catch (ExistException e) {
+                    // This is fine.
+                }
+                sMainCollection = mainCollection;
+            }
+        }
+    }
+
+    @Override
+    public void addTodoList(ListSpec listSpec) {
+
+    }
+
+    @Override
+    public void deleteTodoList(String key) {
+
+    }
+
+    @Override
+    public void completeAllTasks(ListMetadata listMetadata) {
+
+    }
+}
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 664184b..63ecc2c 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
@@ -4,10 +4,191 @@
 
 package io.v.todos.persistence.syncbase;
 
-import io.v.todos.persistence.Persistence;
+import android.app.Activity;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
 
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.io.File;
+import java.util.concurrent.Executors;
+
+import io.v.android.libs.security.BlessingsManager;
+import io.v.android.v23.V;
+import io.v.impl.google.services.syncbase.SyncbaseServer;
+import io.v.todos.persistence.Persistence;
+import io.v.v23.VFutures;
+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.access.AccessList;
+import io.v.v23.security.access.Constants;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.syncbase.Database;
+import io.v.v23.syncbase.Syncbase;
+import io.v.v23.syncbase.SyncbaseService;
+import io.v.v23.verror.ExistException;
+import io.v.v23.verror.VException;
+
+/**
+ * TODO(rosswang): Move most of this to vanadium-android.
+ */
 public class SyncbasePersistence implements Persistence {
+    private static final String
+            TAG = "SyncbasePersistence",
+            FILENAME = "syncbase",
+            PROXY = "proxy",
+            DATABASE = "db",
+            BLESSINGS_KEY = "blessings";
+    // 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>() {
+                @Override
+                public AccessList get() {
+                    return new AccessList(ImmutableList.of(new BlessingPattern("...")),
+                            ImmutableList.<String>of());
+                }
+            });
+
+    protected static final ListeningExecutorService sExecutor =
+            MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
+
+    private static final Object sSyncbaseMutex = new Object();
+    private static VContext sVContext;
+    private static SyncbaseService sSyncbase;
+
+    private static String startSyncbaseServer(VContext vContext, Context appContext,
+                                              Permissions serverPermissions)
+            throws SyncbaseServer.StartException {
+        try {
+            vContext = V.withListenSpec(vContext, V.getListenSpec(vContext).withProxy(PROXY));
+        } catch (VException e) {
+            Log.w(TAG, "Unable to set up Vanadium proxy for Syncbase");
+        }
+
+        File storageRoot = new File(appContext.getFilesDir(), FILENAME);
+        storageRoot.mkdirs();
+
+        Log.i(TAG, "Starting Syncbase");
+        VContext serverContext = SyncbaseServer.withNewServer(vContext,
+                new SyncbaseServer.Params()
+                        .withPermissions(serverPermissions)
+                        .withStorageRootDir(storageRoot.getAbsolutePath()));
+
+        Server server = V.getServer(serverContext);
+        return "/" + server.getStatus().getEndpoints()[0];
+    }
+
+    /**
+     * Ensures that Syncbase is running. This should not be called until after the Vanadium
+     * principal has assumed blessings. The Syncbase server will run until the process is killed.
+     *
+     * @throws IllegalStateException if blessings were not attached to the principal beforehand
+     * @throws io.v.impl.google.services.syncbase.SyncbaseServer.StartException if there was an
+     * error starting the syncbase service
+     */
+    private static SyncbaseService ensureSyncbaseStarted(Context androidContext)
+            throws SyncbaseServer.StartException {
+        synchronized (sSyncbaseMutex) {
+            if (sSyncbase == null) {
+                final Context appContext = androidContext.getApplicationContext();
+                VContext singletonContext = V.init(appContext);
+                try {
+                    Blessings clientBlessings = V.getPrincipal(singletonContext)
+                            .blessingStore().defaultBlessings();
+                    if (clientBlessings == null) {
+                        throw new IllegalStateException("Blessings must be attached to the " +
+                                "Vanadium principal before Syncbase initialization.");
+                    }
+
+                    AccessList clientAcl = new AccessList(ImmutableList.of(
+                            new BlessingPattern(clientBlessings.toString())),
+                            ImmutableList.<String>of());
+
+                    Permissions permissions = new Permissions(ImmutableMap.of(
+                            Constants.RESOLVE.getValue(), OPEN_ACL.get(),
+                            Constants.READ.getValue(), clientAcl,
+                            Constants.WRITE.getValue(), clientAcl,
+                            Constants.ADMIN.getValue(), clientAcl));
+
+                    sSyncbase = Syncbase.newService(startSyncbaseServer(
+                            singletonContext, appContext, permissions));
+                } catch (SyncbaseServer.StartException | RuntimeException e) {
+                    singletonContext.cancel();
+                    throw e;
+                }
+                sVContext = singletonContext;
+            }
+        }
+        return sSyncbase;
+    }
+
+    private static final Object sDatabaseMutex = new Object();
+    private static Database sDatabase;
+
+    private static Database ensureDatabaseExists(Context androidContext) throws VException {
+        synchronized (sDatabaseMutex) {
+            if (sDatabase == null) {
+                final Database db = sSyncbase.getDatabase(sVContext, DATABASE, null);
+
+                try {
+                    VFutures.sync(db.create(sVContext, null));
+                } catch (ExistException e) {
+                    // This is fine.
+                }
+                sDatabase = db;
+            }
+        }
+        return sDatabase;
+    }
+
+    protected final Activity mActivity;
+    protected final VContext mVContext;
+
+    protected Database getDatabase() {
+        return sDatabase;
+    }
+
+    /**
+     * This constructor is blocking for simplicity.
+     */
+    public SyncbasePersistence(final Activity activity)
+            throws VException, SyncbaseServer.StartException {
+        mActivity = activity;
+        mVContext = V.init(activity);
+
+        // We might not actually have to seek blessings each time, but getBlessings does not
+        // block if we already have blessings and this has better-behaved lifecycle
+        // implications than trying to seek blessings in the static code.
+        final SettableFuture<ListenableFuture<Blessings>> blessings = SettableFuture.create();
+        if (activity.getMainLooper().getThread() == Thread.currentThread()) {
+            blessings.set(BlessingsManager.getBlessings(mVContext, activity, BLESSINGS_KEY, true));
+        } else {
+            new Handler(activity.getMainLooper()).post(new Runnable() {
+                @Override
+                public void run() {
+                    blessings.set(BlessingsManager.getBlessings(
+                            mVContext, activity, BLESSINGS_KEY, true));
+                }
+            });
+        }
+        VFutures.sync(Futures.dereference(blessings));
+        ensureSyncbaseStarted(activity);
+        ensureDatabaseExists(activity);
+    }
+
     @Override
     public void close() {
+        mVContext.cancel();
     }
 }