TODOs: Faster Start and Restart

The app has been noticeably slow to start up and restart on occasion.

Reason 1: Cannot connect to cloud syncbase to set up its database.
- A shorter timeout alleviates this issue. In the long run, we don't
  have to do this in the app, anyway.

Reason 2: Thread.sleep instead of delayed scheduling of syncgroup join
- If the app ever has lists it could not join, Thread.sleep would
  delay every single join attempt by 2 seconds. This stacks up fast.
  Further, all the work was done on the UI thread, which froze the UI.
- The problem was made a little worse by the cross user collection
  sharing bug.

Change-Id: I4c98e877c1b94cfaf1f9ef84be814c8a13dc5ff5
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
index e0f8479..d487d33 100644
--- a/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseMain.java
+++ b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseMain.java
@@ -10,6 +10,7 @@
 import android.util.Log;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.AsyncFunction;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -18,6 +19,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
 
 import javax.annotation.Nullable;
 
@@ -82,24 +84,25 @@
                     mIdGenerator.registerId(change.getRowName().substring(LISTS_PREFIX.length()));
 
                     Log.d(TAG, "Found a list id from userdata watch: " + listId);
-                    // TODO(alexfandrianto): Exponential backoff on joining this list syncgroup.
-                    Futures.addCallback(joinListSyncgroup(listId),
-                            new TrappingCallback<SyncgroupSpec>(getErrorReporter()) {
-                        @Override
-                        public void onFailure(@NonNull Throwable t) {
-                            if (t instanceof SyncgroupJoinFailedException) {
-                                // Let's try again...
-                                try {
-                                    Thread.sleep(2000);
-                                } catch (InterruptedException e) {}
+                    Futures.catchingAsync(joinListSyncgroup(listId),
+                            SyncgroupJoinFailedException.class, new
+                                    AsyncFunction<SyncgroupJoinFailedException, SyncgroupSpec>() {
+                        public ListenableFuture<SyncgroupSpec> apply(@Nullable
+                                                                     SyncgroupJoinFailedException
+                                                                             input) throws
+                                Exception {
+                            Log.d(TAG, "Join failed. Sleeping and trying again: " + listId);
+                            return sExecutor.schedule(new Callable<SyncgroupSpec>() {
 
+                                @Override
+                                public SyncgroupSpec call() throws Exception {
+                                    Log.d(TAG, "Sleep done. Trying again: " + listId);
 
-                                // If this errors, then we will not get another chance to see this
-                                // syncgroup until the app is restarted.
-                                trap(joinListSyncgroup(listId));
-                            } else {
-                                super.onFailure(t);
-                            }
+                                    // If this errors, then we will not get another chance to see
+                                    // this syncgroup until the app is restarted.
+                                    return joinListSyncgroup(listId).get();
+                                }
+                            }, RETRY_DELAY, TimeUnit.MILLISECONDS);
                         }
                     });
 
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 c8680ed..4ca22e7 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
@@ -19,10 +19,12 @@
 import com.google.common.util.concurrent.FutureCallback;
 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.ListeningScheduledExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.SettableFuture;
 
+import org.joda.time.Duration;
+
 import java.io.File;
 import java.util.concurrent.Executors;
 
@@ -76,6 +78,9 @@
             LIST_COLLECTION_SYNCGROUP_SUFFIX = "/%%sync/list_",
             DEFAULT_BLESSING_STRING = "dev.v.io:o:608941808256-43vtfndets79kf5hac8ieujto8837660" +
                     ".apps.googleusercontent.com:";
+    protected static final long
+            SHORT_TIMEOUT = 2500,
+            RETRY_DELAY = 2000;
     public static final String
             USER_COLLECTION_NAME = "userdata",
             MOUNTPOINT = "/ns.dev.v.io:8101/tmp/todos/users/",
@@ -93,8 +98,8 @@
                 }
             });
 
-    protected static final ListeningExecutorService sExecutor =
-            MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
+    protected static final ListeningScheduledExecutorService sExecutor =
+            MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(10));
 
     private static final Object sSyncbaseMutex = new Object();
     private static VContext sVContext;
@@ -247,7 +252,8 @@
                 SyncbaseService cloudService = Syncbase.newService(CLOUD_NAME);
                 Database db = cloudService.getDatabase(sVContext, DATABASE, null);
                 try {
-                    VFutures.sync(db.create(sVContext, null));
+                    VFutures.sync(db.create(sVContext.withTimeout(Duration.millis(SHORT_TIMEOUT))
+                            , null));
                 } catch (ExistException e) {
                     // This is acceptable. No need to do it again.
                 } catch (Exception e) {
@@ -427,6 +433,7 @@
                 }
             });
         }
+
         VFutures.sync(Futures.dereference(blessings));
         ensureSyncbaseStarted(activity);
         ensureDatabaseExists();