Merge "java: default to vom version 81"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0d8e3e5..ec0881a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -13,8 +13,8 @@
 ## Contributing code
 
 Please read the [contribution
-guidelines](https://github.com/vanadium/docs/blob/master/contributing/README.md)
-before sending patches.
+guidelines](https://vanadium.github.io/community/contributing.html) before
+sending patches.
 
 **We do not accept GitHub pull requests.** (We use
 [Gerrit](https://www.gerritcodereview.com/) instead for code reviews.)
@@ -25,7 +25,7 @@
 ## Testing changes
 
 Typical users of the Vanadium for Java libraries will use a dependency manager
-such as Maven or Gradle to bring Vanadium into their projects. 
+such as Maven or Gradle to bring Vanadium into their projects.
 
 For example, in an Android project, the user may do something like:
 
@@ -134,5 +134,3 @@
 ```
 (cd ${yourProject}; ./gradlew assembleDebug)
 ```
-
-
diff --git a/android-lib/build.gradle b/android-lib/build.gradle
index 9b21120..1f0511b 100644
--- a/android-lib/build.gradle
+++ b/android-lib/build.gradle
@@ -86,7 +86,7 @@
         if (!System.getenv().containsKey('JIRI_ROOT')) {
             throw new InvalidUserDataException("JIRI_ROOT is not set. "
                     + "Please follow the Vanadium installation instructions at "
-                    + "https://github.com/vanadium/docs/blob/master/installation.md")
+                    + "https://vanadium.github.io/installation/")
         }
 
         result.jiriRoot = new File(System.getenv()['JIRI_ROOT'])
@@ -95,7 +95,7 @@
             throw new InvalidUserDataException(
                     result.jiriBin.toString() + " does not exist or is not an executable file. "
                     + "Please follow the Vanadium installation instructions at "
-                    + "https://github.com/vanadium/docs/blob/master/installation.md")
+                    + "https://vanadium.github.io/installation/")
         }
         return result
     }
diff --git a/gradle-plugin/README.md b/gradle-plugin/README.md
index f25c5b1..339d20d 100644
--- a/gradle-plugin/README.md
+++ b/gradle-plugin/README.md
@@ -3,7 +3,7 @@
 
 This plugin provides users of [Gradle](https://gradle.org/) with a convenient
 way to generate Java
-[VDL](https://github.com/vanadium/docs/blob/master/designdocs/vdl-spec.md)
+[VDL](https://vanadium.github.io/designdocs/vdl-spec.html)
 wrappers and to use those wrappers in a Java or Android project. It automates
 several tasks that projects using VDL commonly have to perform.
 
diff --git a/lib/build.gradle b/lib/build.gradle
index 4209e7a..72d3cb4 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -96,7 +96,7 @@
         if (!System.getenv().containsKey('JIRI_ROOT')) {
             throw new InvalidUserDataException("JIRI_ROOT is not set. "
                     + "Please follow the Vanadium installation instructions at "
-                    + "https://github.com/vanadium/docs/blob/master/installation.md")
+                    + "https://vanadium.github.io/installation/")
         }
 
         result.jiriRoot = new File(System.getenv()['JIRI_ROOT'])
@@ -105,7 +105,7 @@
             throw new InvalidUserDataException(
                     result.jiriBin.toString() + " does not exist or is not an executable file. "
                     + "Please follow the Vanadium installation instructions at "
-                    + "https://github.com/vanadium/docs/blob/master/installation.md")
+                    + "https://vanadium.github.io/installation/")
         }
         return result
     }
diff --git a/lib/src/main/java/io/v/v23/discovery/VDiscovery.java b/lib/src/main/java/io/v/v23/discovery/VDiscovery.java
index d2a06dd..234e952 100644
--- a/lib/src/main/java/io/v/v23/discovery/VDiscovery.java
+++ b/lib/src/main/java/io/v/v23/discovery/VDiscovery.java
@@ -75,7 +75,7 @@
      *     v.Attrs["a"] = "v1" OR v.Attrs["a"] = "v2"
      * </pre></blockquote><p>
      * You can find the {@code SyncQL} tutorial at:
-     *     https://github.com/vanadium/docs/blob/master/tutorials/syncql-tutorial.md
+     *     https://vanadium.github.io/tutorials/syncbase/syncql-tutorial.html
      *
      * @param context  a context that will be used to stop the scan;  scan will end when the context
      *                 is cancelled or timed out
diff --git a/lib/src/main/java/io/v/v23/namespace/Namespace.java b/lib/src/main/java/io/v/v23/namespace/Namespace.java
index 1329385..2513516 100644
--- a/lib/src/main/java/io/v/v23/namespace/Namespace.java
+++ b/lib/src/main/java/io/v/v23/namespace/Namespace.java
@@ -50,10 +50,10 @@
      * {@code context} gets canceled.
      *
      * @param context a client context
-     * @param name a Vanadium name, see also <a href="https://github.com/vanadium/docs/blob/master/glossary.md#object-name">the
+     * @param name a Vanadium name, see also <a href="https://vanadium.github.io/glossary.html#object-name">the
      *             Name entry</a> in the glossary
      * @param server an object address, see also
-     *               <a href="https://github.com/vanadium/docs/blob/master/concepts/naming.md#object-names">the Object names</a>
+     *               <a href="https://vanadium.github.io/concepts/naming.html#object-names">the Object names</a>
      *               section of the Naming Concepts document
      * @param ttl the duration for which the mount should live
      * @param options options to pass to the implementation as described above, or {@code null}
@@ -86,10 +86,10 @@
      * {@code context} gets canceled.
      *
      * @param context a client context
-     * @param name a Vanadium name, see also <a href="https://github.com/vanadium/docs/blob/master/glossary.md#object-name">the
+     * @param name a Vanadium name, see also <a href="https://vanadium.github.io/glossary.html#object-name">the
      *             Name entry</a> in the glossary
      * @param server an object address, see also
-     *               <a href="https://github.com/vanadium/docs/blob/master/concepts/naming.md#object-names">the Object names</a>
+     *               <a href="https://vanadium.github.io/concepts/naming.html#object-names">the Object names</a>
      *               section of the Naming Concepts document
      * @param options options to pass to the implementation as described above, or {@code null}
      */
@@ -121,7 +121,7 @@
      *
      * @param context a client context
      * @param name the Vanadium name to delete, see also
-     *             <a href="https://github.com/vanadium/docs/blob/master/glossary.md#object-name">the Name entry</a> in the
+     *             <a href="https://vanadium.github.io/glossary.html#object-name">the Name entry</a> in the
      *             glossary
      * @param deleteSubtree whether the entire tree rooted at {@code name} should be deleted
      * @param options options to pass to the implementation as described above, or {@code null}
@@ -154,7 +154,7 @@
      *
      * @param context a client context
      * @param name the Vanadium name to resolve, see also
-     *             <a href="https://github.com/vanadium/docs/blob/master/glossary.md#object-name">the Name entry</a> in the
+     *             <a href="https://vanadium.github.io/glossary.html#object-name">the Name entry</a> in the
      *             glossary
      * @param options options to pass to the implementation as described above, or {@code null}
      * @return a new {@link ListenableFuture} whose result is the {@link MountEntry} to which the
@@ -187,7 +187,7 @@
      *
      * @param context a client context
      * @param name the Vanadium name to resolve, see also
-     *             <a href="https://github.com/vanadium/docs/blob/master/glossary.md#object-name">the Name entry</a> in the
+     *             <a href="https://vanadium.github.io/glossary.html#object-name">the Name entry</a> in the
      *             glossary
      * @param options options to pass to the implementation as described above, or {@code null}
      * @return a new {@link ListenableFuture} whose result is the {@link MountEntry} of the
@@ -205,7 +205,7 @@
      * This is a non-blocking method.
      *
      * @param context a client context
-     * @param name a Vanadium name, see also <a href="https://github.com/vanadium/docs/blob/master/glossary.md#object-name">the
+     * @param name a Vanadium name, see also <a href="https://vanadium.github.io/glossary.html#object-name">the
      *             Name entry</a> in the glossary
      * @return {@code true} iff resolution information for the name was successfully flushed
      */
@@ -251,7 +251,7 @@
      * @param roots the roots that will be used to turn relative paths into absolute paths, or
      *              {@code null} to clear the currently configured set of roots. Each entry should
      *              be a Vanadium name, see also
-     *              <a href="https://github.com/vanadium/docs/blob/master/glossary.md#object-name">
+     *              <a href="https://vanadium.github.io/glossary.html#object-name">
      *              the Name entry</a> in the glossary
      */
     void setRoots(List<String> roots) throws VException;
diff --git a/lib/src/main/java/io/v/v23/rpc/package-info.java b/lib/src/main/java/io/v/v23/rpc/package-info.java
index 0b2036d..7060e5b 100644
--- a/lib/src/main/java/io/v/v23/rpc/package-info.java
+++ b/lib/src/main/java/io/v/v23/rpc/package-info.java
@@ -5,7 +5,7 @@
 /**
  * Package rpc defines interfaces for communication via remote procedure call.
  * <p><ul>
- *   <li>Concept: <a href="https://github.com/vanadium/docs/blob/master/concepts/rpc.md">https://github.com/vanadium/docs/blob/master/concepts/rpc.md</a>.</li>
+ *   <li>Concept: <a href="https://vanadium.github.io/concepts/rpc.html">https://vanadium.github.io/concepts/rpc.html</a>.</li>
  *   <li>Tutorial: (forthcoming)</li>
  * </ul><p>
  * There are two actors in the system, clients and servers.  {@link io.v.v23.rpc.Client}s invoke
diff --git a/lib/src/main/java/io/v/v23/security/BlessingRoots.java b/lib/src/main/java/io/v/v23/security/BlessingRoots.java
index 75bd0cf..ba43846 100644
--- a/lib/src/main/java/io/v/v23/security/BlessingRoots.java
+++ b/lib/src/main/java/io/v/v23/security/BlessingRoots.java
@@ -12,7 +12,7 @@
 /**
  * The set of authoritative public keys for roots of blessings.
  * <p>
- * See also: <a href="https://github.com/vanadium/docs/blob/master/glossary.md#blessing-root">https://github.com/vanadium/docs/blob/master/glossary.md#blessing-root</a>.
+ * See also: <a href="https://vanadium.github.io/glossary.html#blessing-root">https://vanadium.github.io/glossary.html#blessing-root</a>.
  */
 public interface BlessingRoots {
     /**
diff --git a/lib/src/main/java/io/v/v23/security/Blessings.java b/lib/src/main/java/io/v/v23/security/Blessings.java
index a900c18..1076480 100644
--- a/lib/src/main/java/io/v/v23/security/Blessings.java
+++ b/lib/src/main/java/io/v/v23/security/Blessings.java
@@ -28,7 +28,7 @@
  * {@link Blessings} objects are immutable and multiple threads may invoke methods on
  * them simultaneously.
  * <p>
- * See also: <a href="https://github.com/vanadium/docs/blob/master/glossary.md#blessing">https://github.com/vanadium/docs/blob/master/glossary.md#blessing</a>.
+ * See also: <a href="https://vanadium.github.io/glossary.html#blessing">https://vanadium.github.io/glossary.html#blessing</a>.
  */
 public final class Blessings implements Serializable {
     private static final long serialVersionUID = 1L;
diff --git a/lib/src/main/java/io/v/v23/security/Discharge.java b/lib/src/main/java/io/v/v23/security/Discharge.java
index bdb23cd..9fcc9ed 100644
--- a/lib/src/main/java/io/v/v23/security/Discharge.java
+++ b/lib/src/main/java/io/v/v23/security/Discharge.java
@@ -12,7 +12,7 @@
  * <p>
  * {@code Discharge} objects are immutable and are therefore threadsafe.
  * <p>
- * @see <a href="https://github.com/vanadium/docs/blob/master/glossary.md#discharge">the Discharge glossary entry</a>
+ * @see <a href="https://vanadium.github.io/glossary.html#discharge">the Discharge glossary entry</a>
  */
 public class Discharge {
     private final WireDischarge wire;
diff --git a/lib/src/main/java/io/v/v23/security/VPrincipal.java b/lib/src/main/java/io/v/v23/security/VPrincipal.java
index d0d3eec..ad6f794 100644
--- a/lib/src/main/java/io/v/v23/security/VPrincipal.java
+++ b/lib/src/main/java/io/v/v23/security/VPrincipal.java
@@ -14,7 +14,7 @@
  * <p>
  * Multiple goroutines may invoke methods on a {@link VPrincipal} simultaneously.
  * <p>
- * See also: <a href="https://github.com/vanadium/docs/blob/master/glossary.md#principal">https://github.com/vanadium/docs/blob/master/glossary.md#principal</a>.
+ * See also: <a href="https://vanadium.github.io/glossary.html#principal">https://vanadium.github.io/glossary.html#principal</a>.
  */
 public interface VPrincipal {
     /**
diff --git a/lib/src/main/java/io/v/v23/security/package-info.java b/lib/src/main/java/io/v/v23/security/package-info.java
index 57e1681..4c1961e 100644
--- a/lib/src/main/java/io/v/v23/security/package-info.java
+++ b/lib/src/main/java/io/v/v23/security/package-info.java
@@ -5,7 +5,7 @@
 /**
  * Package security defines types and utilities associated with security.
  * <p><ul>
- *   <li>Concept: <a href="https://github.com/vanadium/docs/blob/master/concepts/security.md">https://github.com/vanadium/docs/blob/master/concepts/security.md</a></li>
+ *   <li>Concept: <a href="https://vanadium.github.io/concepts/security.html">https://vanadium.github.io/concepts/security.html</a></li>
  *   <li>Tutorial: (forthcoming)</li>
  * </ul><p>
  * The primitives and APIs defined in this package enable bi-directional,
diff --git a/projects/moments/app/src/main/java/io/v/moments/ifc/Advertiser.java b/projects/moments/app/src/main/java/io/v/moments/ifc/Advertiser.java
index 3c25a03..36b70b0 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ifc/Advertiser.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ifc/Advertiser.java
@@ -4,14 +4,19 @@
 
 package io.v.moments.ifc;
 
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.ListenableFuture;
+
 /**
  * Something needing to advertise itself will want an implementation of this.
  */
 public interface Advertiser {
     /**
-     * Synchronously start advertising.
+     * Asynchronously start advertising.  Callback executed on success or
+     * failure of advertising startup.  The future returned on successful
+     * startup should be given a callback to handle advertising shutdown.
      */
-    void advertiseStart();
+    void advertiseStart(FutureCallback<ListenableFuture<Void>> callback);
 
     /**
      * True if advertiseStop could usefully be called.
diff --git a/projects/moments/app/src/main/java/io/v/moments/lib/V23Manager.java b/projects/moments/app/src/main/java/io/v/moments/lib/V23Manager.java
index ba3a4d9..9d13e03 100644
--- a/projects/moments/app/src/main/java/io/v/moments/lib/V23Manager.java
+++ b/projects/moments/app/src/main/java/io/v/moments/lib/V23Manager.java
@@ -14,6 +14,7 @@
 
 import org.joda.time.Duration;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import io.v.android.libs.security.BlessingsManager;
@@ -25,7 +26,6 @@
 import io.v.v23.discovery.Service;
 import io.v.v23.discovery.Update;
 import io.v.v23.discovery.VDiscovery;
-import io.v.v23.rpc.ListenSpec;
 import io.v.v23.rpc.Server;
 import io.v.v23.security.BlessingPattern;
 import io.v.v23.security.Blessings;
@@ -38,6 +38,7 @@
 public class V23Manager {
     private static final String TAG = "V23Manager";
     private static final String BLESSINGS_KEY = "BlessingsKey";
+    private static final List<BlessingPattern> NO_PATTERNS = new ArrayList<>();
     private Context mAndroidCtx;
     private VContext mV23Ctx = null;
     private VDiscovery mDiscovery = null;
@@ -45,42 +46,16 @@
     // Singleton.
     private V23Manager() {
     }
-    
-    public VContext advertise(final Service service, List<BlessingPattern> patterns) {
+
+    public VContext advertise(final Service service, FutureCallback<ListenableFuture<Void>> callback) {
         if (mDiscovery == null) {
-            Log.d(TAG, "Discovery not ready.");
+            callback.onFailure(new IllegalStateException("Discovery not ready."));
             return null;
         }
-        VContext context = mV23Ctx.withCancel();
+        VContext context = mV23Ctx.withTimeout(Duration.standardMinutes(5));
         final ListenableFuture<ListenableFuture<Void>> fStart =
-                mDiscovery.advertise(context, service, patterns);
-        Futures.addCallback(fStart, new FutureCallback<ListenableFuture<Void>>() {
-            @Override
-            public void onSuccess(ListenableFuture<Void> result) {
-                Log.d(TAG, "Started advertising with ID = " +
-                        service.getInstanceId());
-                Futures.addCallback(
-                        result, new FutureCallback<Void>() {
-                            @Override
-                            public void onSuccess(Void result) {
-                                Log.d(TAG, "Stopped advertising.");
-                            }
-
-                            @Override
-                            public void onFailure(Throwable t) {
-                                if (!(t instanceof  java.util.concurrent.CancellationException)) {
-                                    Log.d(TAG, "Failure to gracefully stop advertising.", t);
-                                }
-                            }
-                        }
-                );
-            }
-
-            @Override
-            public void onFailure(Throwable t) {
-                Log.d(TAG, "Failure to start advertising.", t);
-            }
-        });
+                mDiscovery.advertise(context, service, NO_PATTERNS);
+        Futures.addCallback(fStart, callback);
         Log.d(TAG, "Back from V.getDiscovery.advertise");
         return context;
     }
@@ -93,14 +68,14 @@
         VContext context = mV23Ctx.withCancel();
         Log.d(TAG, "Calling V.getDiscovery.scan with q=" + query);
         final ListenableFuture<Void> fStart =
-            InputChannels.withCallback(mDiscovery.scan(context, query),
-                new InputChannelCallback<Update>() {
-                    @Override
-                    public ListenableFuture<Void> onNext(Update result) {
-                        listener.scanUpdateReceived(result);
-                        return Futures.immediateFuture(null);
-                    }
-                });
+                InputChannels.withCallback(mDiscovery.scan(context, query),
+                        new InputChannelCallback<Update>() {
+                            @Override
+                            public ListenableFuture<Void> onNext(Update result) {
+                                listener.scanUpdateReceived(result);
+                                return Futures.immediateFuture(null);
+                            }
+                        });
         Futures.addCallback(fStart, new FutureCallback<Void>() {
             @Override
             public void onSuccess(Void result) {
@@ -151,20 +126,9 @@
         mAndroidCtx = null;
     }
 
-    private VContext getListenContext() throws VException {
-        final boolean useProxy = false;
-        // Disabled while debugging network performance / visibility issues.
-        if (useProxy) {
-            ListenSpec spec = V.getListenSpec(mV23Ctx).withProxy("proxy");
-            Log.d(TAG, "listenSpec = " + spec.toString() + " p=" + spec.getProxy());
-            return V.withListenSpec(mV23Ctx, spec);
-        }
-        return mV23Ctx;
-    }
-
     public VContext makeServerContext(String mountName, Object server) throws VException {
         return V.withNewServer(
-                getListenContext(),
+                mV23Ctx.withCancel(),
                 mountName,
                 server,
                 VSecurity.newAllowEveryoneAuthorizer());
diff --git a/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserFactory.java b/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserFactory.java
index b23ef86..522ce76 100644
--- a/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserFactory.java
+++ b/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserFactory.java
@@ -9,15 +9,14 @@
 
 import io.v.moments.ifc.Advertiser;
 import io.v.moments.ifc.IdSet;
-import io.v.moments.lib.Id;
 import io.v.moments.ifc.Moment;
-import io.v.moments.ifc.MomentFactory;
+import io.v.moments.lib.Id;
 import io.v.moments.lib.V23Manager;
 
 /**
  * Makes advertisers.  Keeps a record of all of them for the life of the app.
- * Can use this record to reject local advertisements when scanning, or to
- * shut down all advertising.
+ * Can use this record to reject local advertisements when scanning, or to shut
+ * down all advertising.
  */
 public class AdvertiserFactory implements IdSet {
     private final V23Manager mV23Manager;
diff --git a/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserImpl.java b/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserImpl.java
index 3efa5c5..f178285 100644
--- a/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserImpl.java
+++ b/projects/moments/app/src/main/java/io/v/moments/model/AdvertiserImpl.java
@@ -7,6 +7,7 @@
 import android.graphics.Bitmap;
 import android.util.Log;
 
+import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -25,7 +26,6 @@
 import io.v.v23.discovery.Service;
 import io.v.v23.naming.Endpoint;
 import io.v.v23.rpc.ServerCall;
-import io.v.v23.security.BlessingPattern;
 import io.v.v23.verror.VException;
 
 /**
@@ -34,7 +34,6 @@
 public class AdvertiserImpl implements Advertiser {
     static final String NO_MOUNT_NAME = "";
     private static final String TAG = "AdvertiserImpl";
-    private static final List<BlessingPattern> NO_PATTERNS = new ArrayList<>();
     private final V23Manager mV23Manager;
     private final Moment mMoment;
 
@@ -54,7 +53,7 @@
 
     @Override
     public boolean isAdvertising() {
-        return mAdvCtx != null;
+        return mAdvCtx != null || mServerCtx != null;
     }
 
     @Override
@@ -63,16 +62,20 @@
     }
 
     @Override
-    public void advertiseStart() {
+    public void advertiseStart(FutureCallback<ListenableFuture<Void>> callback) {
+        Log.d(TAG, "Entering advertiseStart.");
         if (isAdvertising()) {
-            throw new IllegalStateException("Already advertising.");
+            callback.onFailure(new IllegalStateException("Already advertising."));
+            return;
         }
         Log.d(TAG, "Starting service for moment " + mMoment);
         try {
             mServerCtx = mV23Manager.makeServerContext(
                     NO_MOUNT_NAME, new MomentServer());
         } catch (VException e) {
-            throw new IllegalStateException("Unable to start service.", e);
+            mServerCtx = null;
+            callback.onFailure(new IllegalStateException("Unable to start service.", e));
+            return;
         }
         List<String> addresses = new ArrayList<>();
         Endpoint[] points = mV23Manager.getServer(mServerCtx).getStatus().getEndpoints();
@@ -81,24 +84,25 @@
         }
         Attributes attrs = mMoment.makeAttributes();
         Log.d(TAG, "Starting advertisement of moment " + mMoment);
-        mAdvCtx = mV23Manager.advertise(
-                makeAdvertisement(attrs, addresses),
-                NO_PATTERNS);
+        Service service = makeAdvertisement(attrs, addresses);
+        mAdvCtx = mV23Manager.advertise(service, callback);
+        Log.d(TAG, "Exiting advertiseStart.");
     }
 
     @Override
     public void advertiseStop() {
-        if (!isAdvertising()) {
-            throw new IllegalStateException("Not advertising.");
+        Log.d(TAG, "Entering advertiseStop");
+        if (mAdvCtx != null) {
+            Log.d(TAG, "Cancelling advertising.");
+            mAdvCtx.cancel();
+            mAdvCtx = null;
         }
-        Log.d(TAG, "Stopping advertisement of " + mMoment);
-        Log.d(TAG, "Cancelling advertising context.");
-        mAdvCtx.cancel();
-        mAdvCtx = null;
-        Log.d(TAG, "Cancelling server context.");
-        mServerCtx.cancel();
-        mServerCtx = null;
-        Log.d(TAG, "Advertising stopped.");
+        if (mServerCtx != null) {
+            Log.d(TAG, "Cancelling service.");
+            mServerCtx.cancel();
+            mServerCtx = null;
+        }
+        Log.d(TAG, "Exiting advertiseStop");
     }
 
     /**
diff --git a/projects/moments/app/src/main/java/io/v/moments/ux/MainActivity.java b/projects/moments/app/src/main/java/io/v/moments/ux/MainActivity.java
index dbc7e89..2e6e21b 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ux/MainActivity.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ux/MainActivity.java
@@ -218,7 +218,7 @@
         // serialExecutor is used to start/stop advertisements in the UX.
         MomentAdapter adapter = new MomentAdapter(
                 mRemoteMoments, mLocalMoments,
-                mAdvertiserFactory, mSerialExecutor, mHandler);
+                mAdvertiserFactory, mHandler);
 
         // Lets the adapter speed up a bit.
         adapter.setHasStableIds(true);
@@ -329,23 +329,6 @@
             Log.d(TAG, "Loading moments from prefs.");
             mStateStore.prefsLoad(mLocalMoments);
         }
-        for (final Moment moment : mLocalMoments) {
-            if (moment.getDesiredAdState().equals(Moment.AdState.ON)) {
-                mSerialExecutor.submit(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            mAdvertiserFactory.getOrMake(moment).advertiseStart();
-                            Log.d(TAG, "Started advertising " + moment.getCaption());
-                        } catch (Exception e) {
-                            e.printStackTrace();
-                            toast("Unable to advertise - see log.");
-                        }
-                    }
-                });
-
-            }
-        }
         if (mShouldBeScanning && !isScanning()) {
             startScanning();
         }
diff --git a/projects/moments/app/src/main/java/io/v/moments/ux/MomentAdapter.java b/projects/moments/app/src/main/java/io/v/moments/ux/MomentAdapter.java
index 0101fe9..4edd069 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ux/MomentAdapter.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ux/MomentAdapter.java
@@ -11,12 +11,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import java.util.concurrent.ExecutorService;
-
 import io.v.moments.R;
 import io.v.moments.ifc.ListObserver;
 import io.v.moments.ifc.Moment;
-import io.v.moments.ifc.Moment.Kind;
 import io.v.moments.lib.ObservedList;
 import io.v.moments.model.AdvertiserFactory;
 
@@ -28,18 +25,15 @@
     private final ObservedList<Moment> mRemoteMoments;
     private final ObservedList<Moment> mLocalMoments;
     private final AdvertiserFactory mAdvertiserFactory;
-    private final ExecutorService mExecutor;
     private final Handler mHandler;
 
     public MomentAdapter(ObservedList<Moment> remoteMoments,
                          ObservedList<Moment> localMoments,
                          AdvertiserFactory advertiserFactory,
-                         ExecutorService executor,
                          Handler handler) {
         mRemoteMoments = remoteMoments;
         mLocalMoments = localMoments;
         mAdvertiserFactory = advertiserFactory;
-        mExecutor = executor;
         mHandler = handler;
     }
 
@@ -73,7 +67,7 @@
         Context context = parent.getContext();
         LayoutInflater inflater = LayoutInflater.from(context);
         View view = inflater.inflate(R.layout.item_moment, parent, false);
-        return new MomentHolder(view, context, mExecutor, mHandler);
+        return new MomentHolder(view, context, mHandler);
     }
 
     private boolean isRemote(int position) {
diff --git a/projects/moments/app/src/main/java/io/v/moments/ux/MomentHolder.java b/projects/moments/app/src/main/java/io/v/moments/ux/MomentHolder.java
index 96abfef..1227608 100644
--- a/projects/moments/app/src/main/java/io/v/moments/ux/MomentHolder.java
+++ b/projects/moments/app/src/main/java/io/v/moments/ux/MomentHolder.java
@@ -16,15 +16,18 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import java.util.concurrent.ExecutorService;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
 
 import io.v.moments.R;
 import io.v.moments.ifc.Advertiser;
 import io.v.moments.ifc.Moment;
-import static io.v.moments.ifc.Moment.AdState;
 import io.v.moments.ifc.Moment.Kind;
 import io.v.moments.ifc.Moment.Style;
 
+import static io.v.moments.ifc.Moment.AdState;
+
 /**
  * Holds the views comprising a Moment for a RecyclerView.
  */
@@ -35,15 +38,11 @@
     private final SwitchCompat advertiseButton;
     private final ImageView imageView;
     private final Context mContext;
-    private final ExecutorService mExecutor;
     private final Handler mHandler;
 
-    public MomentHolder(
-            View itemView, Context context,
-            ExecutorService executor, Handler handler) {
+    public MomentHolder(View itemView, Context context, Handler handler) {
         super(itemView);
         mContext = context;
-        mExecutor = executor;
         mHandler = handler;
         authorTextView = (TextView) itemView.findViewById(R.id.moment_author);
         captionTextView = (TextView) itemView.findViewById(R.id.moment_caption);
@@ -56,22 +55,18 @@
      */
     public void bind(Moment moment, Advertiser advertiser) {
         final Kind kind = advertiser == null ? Kind.REMOTE : Kind.LOCAL;
-        Log.d(TAG, "Holder: binding " + kind + " " + moment);
+        Log.d(TAG, "Binding " + kind + " " + moment);
         authorTextView.setText(moment.getAuthor());
         captionTextView.setText(moment.getCaption());
         if (moment.hasPhoto(kind, Style.THUMB)) {
             imageView.setImageBitmap(moment.getPhoto(kind, Style.THUMB));
             imageView.setOnClickListener(showPhoto(moment, kind));
-        } else {
-            Log.d(TAG, kind.toString() + " " + Style.THUMB + " not ready.");
         }
         if (moment.hasPhoto(kind, Style.FULL)) {
             if (!moment.hasPhoto(kind, Style.THUMB)) {
                 throw new IllegalStateException(Style.THUMB.toString() +
                         " should be ready if " + Style.FULL + " is ready.");
             }
-        } else {
-            Log.d(TAG, kind.toString() + " " + Style.FULL + " not ready.");
         }
         if (kind.equals(Kind.REMOTE)) {
             advertiseButton.setVisibility(View.INVISIBLE);
@@ -79,9 +74,10 @@
             advertiseButton.setText("");
             advertiseButton.setVisibility(View.VISIBLE);
             advertiseButton.setEnabled(true);
-            advertiseButton.setChecked(moment.getDesiredAdState().equals(AdState.ON));
             advertiseButton.setOnCheckedChangeListener(
                     toggleAdvertising(moment, advertiser));
+            advertiseButton.setChecked(
+                    moment.getDesiredAdState().equals(AdState.ON));
         }
     }
 
@@ -94,12 +90,10 @@
         return new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                Log.d(TAG, "Clicked moment.");
                 Intent intent = ShowPhotoActivity.makeIntent(
                         mContext, moment.getOrdinal(), kind);
                 mContext.startActivity(intent);
             }
-
         };
     }
 
@@ -109,34 +103,89 @@
             @Override
             public void onCheckedChanged(CompoundButton button, boolean isChecked) {
                 if (isChecked) {
-                    moment.setDesiredAdState(AdState.ON);
-                    mExecutor.submit(new Runnable() {
-                        @Override
-                        public void run() {
-                            try {
-                                advertiser.advertiseStart();
-                                toast("Started advertising " + moment.getCaption());
-                            } catch (Exception e) {
-                                e.printStackTrace();
-                                toast("Had problem starting advertising.");
-                            }
-                        }
-                    });
+                    handleStartAdvertising(moment, advertiser);
                 } else {
-                    moment.setDesiredAdState(AdState.OFF);
-                    mExecutor.submit(new Runnable() {
-                        @Override
-                        public void run() {
-                            try {
-                                advertiser.advertiseStop();
-                                toast("Stopped advertising " + moment.getCaption());
-                            } catch (Exception e) {
-                                e.printStackTrace();
-                                toast("Had problem stopping advertising.");
+                    handleStopAdvertising(moment, advertiser);
+                }
+            }
+        };
+    }
+
+    private void handleStartAdvertising(final Moment moment, final Advertiser advertiser) {
+        moment.setDesiredAdState(AdState.ON);
+        advertiser.advertiseStart(makeAdvertiseCallback(moment));
+    }
+
+    private void handleStopAdvertising(final Moment moment, final Advertiser advertiser) {
+        moment.setDesiredAdState(AdState.OFF);
+        if (advertiser.isAdvertising()) {
+            advertiser.advertiseStop();
+            toast("Stopped advertising " + moment.getCaption());
+        } else {
+            Log.d(TAG, "handleStopAdvertising called, but not advertising.");
+        }
+    }
+
+    private FutureCallback<ListenableFuture<Void>> makeAdvertiseCallback(final Moment moment) {
+        return new FutureCallback<ListenableFuture<Void>>() {
+            private void assureStopped() {
+                moment.setDesiredAdState(AdState.OFF);
+                if (advertiseButton.isChecked()) {
+                    advertiseButton.setChecked(false);
+                }
+            }
+
+            @Override
+            public void onSuccess(ListenableFuture<Void> result) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        moment.setDesiredAdState(AdState.ON);
+                        advertiseButton.setChecked(true);
+                    }
+                });
+                Futures.addCallback(
+                        result, new FutureCallback<Void>() {
+                            @Override
+                            public void onSuccess(Void result) {
+                                mHandler.post(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        assureStopped();
+                                    }
+                                });
+                            }
+
+                            @Override
+                            public void onFailure(final Throwable t) {
+                                mHandler.post(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        assureStopped();
+                                        if (t instanceof java.util.concurrent.CancellationException) {
+                                            // At the time of writing, the only way advertising ends
+                                            // is by throwing this exception, so this is actually
+                                            // a non-exceptional success case.
+                                        } else {
+                                            Log.d(TAG, "Failure to gracefully stop advertising.", t);
+
+                                        }
+                                    }
+                                });
                             }
                         }
-                    });
-                }
+                );
+            }
+
+            @Override
+            public void onFailure(final Throwable t) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        assureStopped();
+                        Log.d(TAG, "Failure to start advertising " + moment, t);
+                    }
+                });
             }
         };
     }