TODOs: Fixes related to resharing and collection ids

- Collection ids used to not include the creator's blessing string
  This has been fixed. However, we have had to concatenate the two
  (blessing string and name) in order to pass things around correctly.
  The reason is that "get" takes too long to execute, so it's better
  to just have both components from the start.
- We used to show the Share menu item even though we cannot reshare.
  We no longer do this anymore.

Change-Id: I6d769165c35cfa8f19a38ff2d97668bbea3de270
diff --git a/app/src/main/java/io/v/todos/TodoListActivity.java b/app/src/main/java/io/v/todos/TodoListActivity.java
index b0a74a4..7920d69 100644
--- a/app/src/main/java/io/v/todos/TodoListActivity.java
+++ b/app/src/main/java/io/v/todos/TodoListActivity.java
@@ -110,7 +110,7 @@
 
             @Override
             public void onDelete() {
-                finish();
+                finishWithAnimation();
             }
 
             @Override
diff --git a/app/src/syncbase/java/io/v/todos/persistence/syncbase/MainListTracker.java b/app/src/syncbase/java/io/v/todos/persistence/syncbase/MainListTracker.java
index 30fe0e1..bf4ad58 100644
--- a/app/src/syncbase/java/io/v/todos/persistence/syncbase/MainListTracker.java
+++ b/app/src/syncbase/java/io/v/todos/persistence/syncbase/MainListTracker.java
@@ -25,6 +25,7 @@
 import io.v.v23.InputChannelCallback;
 import io.v.v23.InputChannels;
 import io.v.v23.context.VContext;
+import io.v.v23.services.syncbase.Id;
 import io.v.v23.syncbase.ChangeType;
 import io.v.v23.syncbase.Collection;
 import io.v.v23.syncbase.Database;
@@ -48,9 +49,9 @@
     public final Collection collection;
     public final ListenableFuture<Void> watchFuture;
 
-    public MainListTracker(VContext vContext, Database database, final String listId,
+    public MainListTracker(VContext vContext, Database database, final Id listId,
                            ListEventListener<ListMetadata> listener) {
-        collection = database.getCollection(vContext, listId);
+        collection = database.getCollection(listId);
         mListener = listener;
         InputChannel<WatchChange> watch = database.watch(vContext,
                 ImmutableList.of(Util.rowPrefixPattern(collection.id(), "")));
@@ -71,15 +72,19 @@
             @Override
             public void onFailure(@NonNull Throwable t) {
                 if (t instanceof NoExistException && mListExistsLocally) {
-                    Log.d(TAG, listId + " destroyed");
-                    mListener.onItemDelete(listId);
+                    Log.d(TAG, getNameFromId() + " destroyed");
+                    mListener.onItemDelete(getNameFromId());
                 }
             }
         });
     }
 
+    private String getNameFromId() {
+        return SyncbasePersistence.convertIdToString(collection.id());
+    }
+
     public ListMetadata getListMetadata() {
-        return new ListMetadata(collection.id().getName(), mListSpec, mNumCompletedTasks,
+        return new ListMetadata(getNameFromId(), mListSpec, mNumCompletedTasks,
                 mIsTaskCompleted.size());
     }
 
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 7e79171..b957fe8 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
@@ -63,31 +63,30 @@
             @Override
             public ListenableFuture<Void> onNext(WatchChange change) {
                 try {
-                    final String listId = change.getRowName();
+                    final String listIdStr = change.getRowName();
+                    final Id listId = convertStringToId(listIdStr);
 
                     if (change.getChangeType() == ChangeType.DELETE_CHANGE) {
                         // (this is idempotent)
-                        Log.d(TAG, listId + " removed from index");
-                        deleteTodoList(listId);
+                        Log.d(TAG, listIdStr + " removed from index");
+                        deleteTodoList(listIdStr);
                     } else {
-                        final String ownerBlessing = SyncbasePersistence.castFromSyncbase(
-                                change.getValue(), String.class);
                         // If we are tracking this list already, don't bother doing anything.
                         // This might happen if a same-user device did a simultaneous put into the
                         // userdata collection.
-                        if (mTaskTrackers.get(listId) != null) {
+                        if (mTaskTrackers.get(listIdStr) != null) {
                             return null;
                         }
 
-                        mIdGenerator.registerId(change.getRowName().substring(LISTS_PREFIX.length()));
+                        mIdGenerator.registerId(listId.getName().substring(LISTS_PREFIX.length()));
 
-                        Log.d(TAG, "Found a list id from userdata watch: " + listId + " with owner: "
-                                + ownerBlessing);
-                        trap(joinWithBackoff(listId, ownerBlessing));
+                        Log.d(TAG, "Found a list id from userdata watch: " + listId.getName() +
+                                " with owner: " + listId.getBlessing());
+                        trap(joinWithBackoff(listId));
 
-                        MainListTracker listTracker = new MainListTracker(getVContext(), getDatabase(),
-                                listId, listener);
-                        mTaskTrackers.put(listId, listTracker);
+                        MainListTracker listTracker = new MainListTracker(getVContext(),
+                                getDatabase(), listId, listener);
+                        mTaskTrackers.put(listIdStr, listTracker);
 
                         // If the watch fails with NoExistException, the collection has been deleted.
                         Futures.addCallback(listTracker.watchFuture, new SyncTrappingCallback<Void>() {
@@ -95,7 +94,7 @@
                             public void onFailure(@NonNull Throwable t) {
                                 if (t instanceof NoExistException) {
                                     // (this is idempotent)
-                                    trap(getUserCollection().delete(getVContext(), listId));
+                                    trap(getUserCollection().delete(getVContext(), listIdStr));
                                 } else {
                                     super.onFailure(t);
                                 }
@@ -113,7 +112,8 @@
     @Override
     public String addTodoList(final ListSpec listSpec) {
         final String listName = LISTS_PREFIX + mIdGenerator.generateTailId();
-        final Collection listCollection = getDatabase().getCollection(getVContext(), listName);
+        final Id listId = new Id(getPersonalBlessingsString(), listName);
+        final Collection listCollection = getDatabase().getCollection(listId);
 
         Futures.addCallback(listCollection.create(getVContext(), null),
                 new SyncTrappingCallback<Void>() {
@@ -122,36 +122,36 @@
                         // These can happen in any order.
                         trap(listCollection.put(getVContext(),
                                 SyncbaseTodoList.LIST_METADATA_ROW_NAME, listSpec));
-                        trap(rememberTodoList(listName));
+                        trap(rememberTodoList(listId));
                         // TODO(alexfandrianto): Syncgroup creation is slow if you specify a cloud
                         // and are offline. https://github.com/vanadium/issues/issues/1326
                         trap(createListSyncgroup(listCollection.id()));
                     }
                 });
-        return listName;
+        return convertIdToString(listId);
     }
 
-    private ListenableFuture<SyncgroupSpec> joinListSyncgroup(String listId, String ownerBlessing) {
+    private ListenableFuture<SyncgroupSpec> joinListSyncgroup(Id listId) {
         SyncgroupMemberInfo memberInfo = getDefaultMemberInfo();
-        String sgName = computeListSyncgroupName(listId);
-        return getDatabase().getSyncgroup(new Id(ownerBlessing, sgName)).join(getVContext(),
+        String sgName = computeListSyncgroupName(listId.getName());
+        return getDatabase().getSyncgroup(new Id(listId.getBlessing(), sgName)).join(getVContext(),
                 CLOUD_NAME, Arrays.asList(CLOUD_BLESSING), memberInfo);
     }
 
-    private ListenableFuture<SyncgroupSpec> joinWithBackoff(String listId, String ownerBlessing) {
-        return joinWithBackoff(listId, ownerBlessing, 0, DEFAULT_MAX_JOIN_ATTEMPTS);
+    private ListenableFuture<SyncgroupSpec> joinWithBackoff(Id listId) {
+        return joinWithBackoff(listId, 0, DEFAULT_MAX_JOIN_ATTEMPTS);
     }
 
-    private ListenableFuture<SyncgroupSpec> joinWithBackoff(final String listId, final String
-            ownerBlessing, final int numTimes, final int limit) {
+    private ListenableFuture<SyncgroupSpec> joinWithBackoff(final Id listId, final int numTimes,
+                                                            final int limit) {
         final String debugString = (numTimes + 1) + "/" + limit + " for: " + listId;
         Log.d(TAG, "Join attempt " + debugString);
         if (numTimes + 1 == limit) { // final attempt!
-            return joinListSyncgroup(listId, ownerBlessing);
+            return joinListSyncgroup(listId);
         }
         final long delay = RETRY_DELAY * (1 << numTimes);
         return Futures.catchingAsync(
-                joinListSyncgroup(listId, ownerBlessing),
+                joinListSyncgroup(listId),
                 SyncgroupJoinFailedException.class,
                 new AsyncFunction<SyncgroupJoinFailedException, SyncgroupSpec>() {
                     public ListenableFuture<SyncgroupSpec> apply(@Nullable
@@ -168,8 +168,7 @@
                                 // If this errors, then we will not get another chance to
                                 // see this syncgroup until the app is restarted.
                                 try {
-                                    return joinWithBackoff(listId, ownerBlessing,
-                                            numTimes + 1, limit).get();
+                                    return joinWithBackoff(listId, numTimes + 1, limit).get();
                                 } catch (InterruptedException | ExecutionException e) {
                                     return null;
                                 }
@@ -180,8 +179,8 @@
     }
 
     private ListenableFuture<Void> createListSyncgroup(Id id) {
-        String listId = id.getName();
-        final String sgName = computeListSyncgroupName(listId);
+        String listName = id.getName();
+        final String sgName = computeListSyncgroupName(listName);
         Permissions permissions =
                 computePermissionsFromBlessings(getPersonalBlessings());
 
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 80b812b..e99d341 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
@@ -380,8 +380,8 @@
 
                 try {
                     Log.d(TAG, "Trying to join the syncgroup: " + sgName);
-                    VFutures.sync(sgHandle.join(getAppVContext(), CLOUD_NAME, Arrays.asList(CLOUD_BLESSING),
-                            memberInfo));
+                    VFutures.sync(sgHandle.join(getAppVContext(), CLOUD_NAME,
+                            Arrays.asList(CLOUD_BLESSING), memberInfo));
                     Log.d(TAG, "JOINED the syncgroup: " + sgName);
                 } catch (SyncgroupJoinFailedException e) {
                     Log.w(TAG, "Failed join. Trying to create the syncgroup: " + sgName, e);
@@ -417,6 +417,16 @@
         return LIST_COLLECTION_SYNCGROUP_SUFFIX + listId;
     }
 
+    private static String BLESSING_NAME_SEPARATOR = "___";
+    public static String convertIdToString(Id id) {
+        // Put the name first since it has a useful prefix for watch to switch on.
+        return id.getName() + BLESSING_NAME_SEPARATOR + id.getBlessing();
+    }
+    public static Id convertStringToId(String idString) {
+        String[] parts = idString.split(BLESSING_NAME_SEPARATOR);
+        return new Id(parts[1], parts[0]);
+    }
+
     private static volatile boolean sInitialized;
 
     public static boolean isInitialized() {
@@ -564,26 +574,22 @@
         }
     }
 
-    public static void acceptSharedTodoList(final String listId, final String owner) {
+    public static void acceptSharedTodoList(final Id listId) {
         sExecutor.submit(new Callable<Void>() {
             @Override
             public Void call() throws VException {
-                Boolean exists = VFutures.sync(sUserCollection.getRow(listId).exists
-                        (getAppVContext()));
+                Boolean exists = VFutures.sync(sUserCollection.getRow(convertIdToString(listId)).
+                        exists(getAppVContext()));
                 if (!exists) {
-                    VFutures.sync(rememberTodoList(listId, owner));
+                    VFutures.sync(rememberTodoList(listId));
                 }
                 return null;
             }
         });
     }
 
-    protected static ListenableFuture<Void> rememberTodoList(String listId) {
-        return rememberTodoList(listId, getPersonalBlessingsString());
-    }
-
-    protected static ListenableFuture<Void> rememberTodoList(String listId, String owner) {
-        return sUserCollection.put(getAppVContext(), listId, owner);
+    protected static ListenableFuture<Void> rememberTodoList(Id listId) {
+        return sUserCollection.put(getAppVContext(), convertIdToString(listId), "");
     }
 
     public static ListenableFuture<Void> watchUserCollection(InputChannelCallback<WatchChange>
@@ -593,11 +599,10 @@
         return InputChannels.withCallback(watch, callback);
     }
 
-    public static Timer watchSharedTo(final String listId, final Function<List<BlessingPattern>,
-            Void>
-            callback) {
-        final Syncgroup sgHandle = sDatabase.getSyncgroup(new Id(getPersonalBlessingsString(),
-                computeListSyncgroupName(listId)));
+    public static Timer watchSharedTo(final Id listId, final Function<List<BlessingPattern>,
+            Void> callback) {
+        final Syncgroup sgHandle = sDatabase.getSyncgroup(new Id(listId.getBlessing(),
+                computeListSyncgroupName(listId.getName())));
 
         Timer timer = new Timer();
         timer.scheduleAtFixedRate(new TimerTask() {
diff --git a/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseTodoList.java b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseTodoList.java
index 1acdc78..2d3f73a 100644
--- a/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseTodoList.java
+++ b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseTodoList.java
@@ -9,6 +9,7 @@
 import android.app.FragmentTransaction;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
+import android.util.Log;
 
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
@@ -89,13 +90,22 @@
     /**
      * This assumes that the collection for this list already exists.
      */
-    public SyncbaseTodoList(Activity activity, Bundle savedInstanceState, String listId,
+    public SyncbaseTodoList(Activity activity, Bundle savedInstanceState, String listIdStr,
                             TodoListListener listener)
             throws VException, SyncbaseServer.StartException {
         super(activity, savedInstanceState);
         mListener = listener;
 
-        mList = getDatabase().getCollection(getVContext(), listId);
+        Log.w(TAG, "syncbase todo list: " + listIdStr);
+        Id listId = convertStringToId(listIdStr);
+        Log.w(TAG, "after: " + listId.toString());
+
+        // Only show the share menu if you are the owner of this list.
+        if (!listId.getBlessing().equals(getPersonalBlessingsString())) {
+            mShareListMenuFragment.hideShareMenuItem();
+        }
+
+        mList = getDatabase().getCollection(listId);
         InputChannel<WatchChange> listWatch = getDatabase().watch(getVContext(),
                 ImmutableList.of(Util.rowPrefixPattern(mList.id(), "")));
         ListenableFuture<Void> listWatchFuture = InputChannels.withCallback(listWatch,
@@ -118,7 +128,7 @@
             }
         });
 
-        mMemberTimer = watchSharedTo(listId, new Function<List<BlessingPattern>, Void>() {
+        mMemberTimer = watchSharedTo(mList.id(), new Function<List<BlessingPattern>, Void>() {
             @Override
             public Void apply(List<BlessingPattern> patterns) {
                 // Analyze these patterns to construct the emails, and fire the listener!
@@ -202,7 +212,7 @@
     }
 
     private Syncgroup getListSyncgroup() {
-        return getDatabase().getSyncgroup(new Id(getPersonalBlessingsString(),
+        return getDatabase().getSyncgroup(new Id(mList.id().getBlessing(),
                 computeListSyncgroupName(mList.id().getName())));
     }
 
diff --git a/app/src/syncbase/java/io/v/todos/sharing/ShareListMenuFragment.java b/app/src/syncbase/java/io/v/todos/sharing/ShareListMenuFragment.java
index 70cc498..c8b5d56 100644
--- a/app/src/syncbase/java/io/v/todos/sharing/ShareListMenuFragment.java
+++ b/app/src/syncbase/java/io/v/todos/sharing/ShareListMenuFragment.java
@@ -32,6 +32,8 @@
     public SyncbaseTodoList persistence;
 
     private List<String> mSharedTo = new ArrayList<>();
+    private Menu mMenu;
+    private boolean mShouldHide;
 
     public void setSharedTo(List<String> sharedTo) {
         mSharedTo = sharedTo;
@@ -60,9 +62,21 @@
         setHasOptionsMenu(true);
     }
 
+    public void hideShareMenuItem() {
+        mShouldHide = true;
+        if (mMenu != null) {
+            mMenu.findItem(R.id.action_share).setVisible(false);
+        }
+    }
+
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        mMenu = menu;
         inflater.inflate(R.menu.menu_share, menu);
+        if (mShouldHide) {
+            // In case the menu was added after the hide flag was set, hide the menu item now.
+            hideShareMenuItem();
+        }
     }
 
     @Override
diff --git a/app/src/syncbase/java/io/v/todos/sharing/Sharing.java b/app/src/syncbase/java/io/v/todos/sharing/Sharing.java
index 2cf3fbf..69f8a74 100644
--- a/app/src/syncbase/java/io/v/todos/sharing/Sharing.java
+++ b/app/src/syncbase/java/io/v/todos/sharing/Sharing.java
@@ -31,7 +31,9 @@
 import io.v.v23.discovery.Discovery;
 import io.v.v23.discovery.Update;
 import io.v.v23.security.BlessingPattern;
+import io.v.v23.services.syncbase.Id;
 import io.v.v23.syncbase.ChangeType;
+import io.v.v23.syncbase.Syncbase;
 import io.v.v23.syncbase.WatchChange;
 import io.v.v23.verror.VException;
 
@@ -78,12 +80,13 @@
         return SyncbasePersistence.getAppContext().getPackageName();
     }
 
+    // TODO(alexfandrianto): Make this "presence" and "invitation" once everyone migrates over.
     public static String getPresenceInterface() {
-        return getRootInterface() + ".presence";
+        return getRootInterface() + ".presence2";
     }
 
     public static String getInvitationInterface() {
-        return getRootInterface() + ".invitation";
+        return getRootInterface() + ".invitation2";
     }
 
     /**
@@ -104,8 +107,9 @@
                             if (listName == null) {
                                 return null;
                             }
+                            String owner = result.getAttribute(OWNER_KEY);
                             Log.d("SHARING", "Noticed advertised list: " + listName + " by: " +
-                                    result.getAttribute(OWNER_KEY));
+                                    owner);
 
                             // TODO(alexfandrianto): Remove hack.
                             // https://github.com/vanadium/issues/issues/1328
@@ -119,8 +123,7 @@
                             if (!result.isLost()) {
                                 Log.d(TAG, "...and will accept it.");
 
-                                SyncbasePersistence.acceptSharedTodoList(listName, result
-                                        .getAttribute(OWNER_KEY));
+                                SyncbasePersistence.acceptSharedTodoList(new Id(owner, listName));
                             }
                             return null;
                         }
@@ -163,16 +166,16 @@
             @Override
             public ListenableFuture<Void> onNext(WatchChange change) {
                 try {
-                    final String listId = change.getRowName();
+                    final String listIdStr = change.getRowName();
+                    final Id listId = SyncbasePersistence.convertStringToId(listIdStr);
 
                     if (change.getChangeType() == ChangeType.DELETE_CHANGE) {
-                        VContext ctx = sAdContextMap.remove(listId);
+                        VContext ctx = sAdContextMap.remove(listIdStr);
                         if (ctx != null) { // TODO(alexfandrianto): ctx might be null if ad failed?
                             ctx.cancel(); // Stop advertising the list; it's been deleted.
                         }
                     } else {
-                        final String owner = SyncbasePersistence.castFromSyncbase(change.getValue(),
-                                String.class);
+                        final String owner = listId.getBlessing();
                         if (!owner.equals(SyncbasePersistence.getPersonalBlessingsString())) {
                             return Futures.immediateFuture((Void) null);
                         }
@@ -196,7 +199,7 @@
 
                                 // Advertise to the remaining patterns.
                                 if (filteredPatterns.size() > 0) {
-                                    Log.d(TAG, "Must advertise for " + listId + " to " +
+                                    Log.d(TAG, "Must advertise for " + listIdStr + " to " +
                                             filteredPatterns.toString());
                                     advertiseList(vContext, listId, filteredPatterns);
                                 }
@@ -221,26 +224,27 @@
      * @param listId      The list to be advertised
      * @param patterns    Blessings that the advertisement should target
      */
-    private static void advertiseList(VContext baseContext, String listId, List<BlessingPattern>
+    private static void advertiseList(VContext baseContext, Id listId, List<BlessingPattern>
             patterns) {
         if (baseContext.isCanceled()) {
             Log.w(TAG, "Base context was canceled; cannot advertise");
             return;
         }
         // Swap out the ad context...
-        VContext oldAdContext = sAdContextMap.remove(listId);
+        String key = SyncbasePersistence.convertIdToString(listId);
+        VContext oldAdContext = sAdContextMap.remove(key);
         if (oldAdContext != null) {
             oldAdContext.cancel();
         }
         VContext newAdContext = baseContext.withCancel();
-        sAdContextMap.put(listId, newAdContext);
+        sAdContextMap.put(key, newAdContext);
 
 
         try {
             Advertisement ad = new Advertisement();
             ad.setInterfaceName(Sharing.getInvitationInterface());
-            ad.getAddresses().add(listId);
-            ad.getAttributes().put(OWNER_KEY, SyncbasePersistence.getPersonalBlessingsString());
+            ad.getAddresses().add(listId.getName());
+            ad.getAttributes().put(OWNER_KEY, listId.getBlessing());
 
             // TODO(alexfandrianto): Remove hack. https://github.com/vanadium/issues/issues/1328
             for (BlessingPattern pattern : patterns) {