Merge changes from topic 'baku'

* changes:
  Making APIs consistent
  Baku - Factoring out sync error handlers
  Baku - improving docs and easing composition
  Baku Toolkit - Adding ID-list bindings
  Baku - Splitting collection adapter components
diff --git a/projects/moments/app/build.gradle b/projects/moments/app/build.gradle
index 1f61306..02488e2 100644
--- a/projects/moments/app/build.gradle
+++ b/projects/moments/app/build.gradle
@@ -1,13 +1,12 @@
 
 buildscript {
     repositories {
-        mavenCentral()
         jcenter()
     }
     dependencies {
         classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0'
         classpath 'com.android.tools.build:gradle:1.3.0'
-        classpath 'io.v:gradle-plugin:1.0'
+        classpath 'io.v:gradle-plugin:1.4'
     }
 }
 
@@ -51,7 +50,7 @@
     compile 'com.android.support:appcompat-v7:23.1.1'
     compile 'com.android.support:design:23.1.1'
     compile 'com.android.support:support-v4:23.1.1'
-    compile 'io.v:vanadium-android:1.3'
+    compile 'io.v:vanadium-android:1.5'
 }
 
 vdl {
diff --git a/projects/moments/app/src/androidTest/java/io/v/momentshare/ApplicationTest.java b/projects/moments/app/src/androidTest/java/io/v/momentshare/ApplicationTest.java
deleted file mode 100644
index 76e44df..0000000
--- a/projects/moments/app/src/androidTest/java/io/v/momentshare/ApplicationTest.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.moments;
-
-import android.app.Application;
-import android.test.ApplicationTestCase;
-
-/**
- * <a href="http://d.android.com/tools/testing/testing_android.html">Testing
- * Fundamentals</a>
- */
-public class ApplicationTest extends ApplicationTestCase<Application> {
-    public ApplicationTest() {
-        super(Application.class);
-    }
-}
diff --git a/projects/moments/app/src/main/java/io/v/moments/lib/DiscoveredList.java b/projects/moments/app/src/main/java/io/v/moments/lib/DiscoveredList.java
index 9a0172a..c4cd093 100644
--- a/projects/moments/app/src/main/java/io/v/moments/lib/DiscoveredList.java
+++ b/projects/moments/app/src/main/java/io/v/moments/lib/DiscoveredList.java
@@ -17,7 +17,7 @@
  * List that updates itself in response to found or lost advertisements.
  */
 public class DiscoveredList<T extends HasId> extends ObservedList<T> implements ScanListener {
-    private final String TAG = "DiscoveredList";
+    private static final String TAG = "DiscoveredList";
 
     private final Handler mHandler;
 
diff --git a/projects/moments/app/src/main/java/io/v/moments/lib/Id.java b/projects/moments/app/src/main/java/io/v/moments/lib/Id.java
index b23af89..31d8317 100644
--- a/projects/moments/app/src/main/java/io/v/moments/lib/Id.java
+++ b/projects/moments/app/src/main/java/io/v/moments/lib/Id.java
@@ -4,7 +4,7 @@
 
 package io.v.moments.lib;
 
-import java.util.UUID;
+import java.util.Random;
 
 /**
  * Consolidates different id styles under one type.
@@ -12,18 +12,21 @@
  * Discovery demands strings, android RecyclerView demands longs.
  */
 public class Id implements Comparable<Id> {
-    private UUID mId;
+    private Long mId;
+    private static final Random RANDOM = new Random();
 
-    private Id(UUID id) {
+    private Id(Long id) {
         mId = id;
     }
 
     public static Id makeRandom() {
-        return new Id(UUID.randomUUID());
+        long lng = RANDOM.nextLong();
+        // Keep it positive to assure roundtrip to string works simply.
+        return new Id(lng < 0 ? -lng : lng);
     }
 
     public static Id fromString(String id) {
-        return new Id(UUID.fromString(id));
+        return new Id(Long.parseLong(id, 16));
     }
 
     public boolean equals(Object obj) {
@@ -38,11 +41,11 @@
     }
 
     public String toString() {
-        return mId.toString();
+        return Long.toHexString(mId);
     }
 
     public Long toLong() {
-        return mId.getLeastSignificantBits();
+        return mId;
     }
 
     @Override
diff --git a/projects/moments/app/src/main/java/io/v/moments/lib/PermissionManager.java b/projects/moments/app/src/main/java/io/v/moments/lib/PermissionManager.java
index cdfff97..983d1a7 100644
--- a/projects/moments/app/src/main/java/io/v/moments/lib/PermissionManager.java
+++ b/projects/moments/app/src/main/java/io/v/moments/lib/PermissionManager.java
@@ -39,7 +39,6 @@
     private final int mRequestCode;
     private final String[] mPerms;
     private final int mAndroidLevel;
-    private boolean mIsRequestInProgress;
 
     PermissionManager(int androidLevel, Activity activity, int requestCode, String[] perms) {
         mAndroidLevel = androidLevel;
@@ -52,13 +51,6 @@
         this(Build.VERSION.SDK_INT, activity, requestCode, perms);
     }
 
-    /**
-     * This helps to avoid stacking requests for permissions.
-     */
-    public synchronized boolean isRequestInProgress() {
-        return mIsRequestInProgress;
-    }
-
     public boolean haveAllPermissions() {
         if (mAndroidLevel < Build.VERSION_CODES.M) {
             // Pre-M, the system asked for perms before startup.
@@ -76,16 +68,11 @@
         if (haveAllPermissions()) {
             return;
         }
-        if (mIsRequestInProgress) {
-            throw new IllegalStateException("Request in progress.");
-        }
-        mIsRequestInProgress = true;
         mActivity.requestPermissions(mPerms, mRequestCode);
     }
 
     public synchronized boolean granted(
             int requestCode, String[] permissions, int[] results) {
-        mIsRequestInProgress = false;
         if (requestCode != mRequestCode) {
             return false;
         }
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 acf9967..1d6b66b 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
@@ -8,6 +8,7 @@
 import android.content.Context;
 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;
 
@@ -35,13 +36,10 @@
  * Various static V23 utilities gathered in an injectable class.
  */
 public class V23Manager {
-    private static final String BLESSINGS_KEY = "BlessingsKey";
-
-    public static final int BLESSING_REQUEST = 201;
     private static final String TAG = "V23Manager";
+    private static final String BLESSINGS_KEY = "BlessingsKey";
     private Context mAndroidCtx;
     private VContext mV23Ctx = null;
-    private Blessings mBlessings = null;
 
     // Singleton.
     private V23Manager() {
@@ -51,17 +49,46 @@
         return V.getDiscovery(mV23Ctx);
     }
 
-    public VContext advertise(Service service, List<BlessingPattern> patterns) {
+    public VContext advertise(final Service service, List<BlessingPattern> patterns) {
         VContext context = mV23Ctx.withCancel();
-        Log.d(TAG, "Calling V.getDiscovery.advertise");
-        V.getDiscovery(mV23Ctx).advertise(context, service, patterns);
+        final ListenableFuture<ListenableFuture<Void>> fStart =
+                V.getDiscovery(mV23Ctx).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);
+            }
+        });
         Log.d(TAG, "Back from V.getDiscovery.advertise");
         return context;
     }
 
     public VContext scan(String query, final ScanListener listener) {
         VContext context = mV23Ctx.withCancel();
-        InputChannels.withCallback(V.getDiscovery(mV23Ctx).scan(context, query),
+        Log.d(TAG, "Calling V.getDiscovery.scan with q=" + query);
+        final ListenableFuture<Void> fStart =
+            InputChannels.withCallback(V.getDiscovery(mV23Ctx).scan(context, query),
                 new InputChannelCallback<Update>() {
                     @Override
                     public ListenableFuture<Void> onNext(Update result) {
@@ -69,48 +96,39 @@
                         return Futures.immediateFuture(null);
                     }
                 });
+        Futures.addCallback(fStart, new FutureCallback<Void>() {
+            @Override
+            public void onSuccess(Void result) {
+                Log.d(TAG, "Scan started.");
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                Log.d(TAG, "Failure to start scan.", t);
+            }
+        });
         return context;
     }
 
-    public synchronized ListenableFuture<Blessings> init(Context androidCtx, Activity activity) {
+    public synchronized void init(Activity activity, FutureCallback<Blessings> future) {
         Log.d(TAG, "init");
         if (mAndroidCtx != null) {
-            if (mAndroidCtx == androidCtx) {
+            if (mAndroidCtx == activity.getApplicationContext()) {
                 Log.d(TAG, "Initialization already started.");
-                return null;
+                return;
             } else {
                 Log.d(TAG, "Initialization with new context.");
                 shutdown();
             }
         }
-        mAndroidCtx = androidCtx;
+        mAndroidCtx = activity.getApplicationContext();
         // Must call V.init before attempting to load blessings, so that proper
         // code is loaded.
         mV23Ctx = V.init(mAndroidCtx);
-        return BlessingsManager.getBlessings(mV23Ctx, activity, "", true);
-    }
-
-    /**
-     * v23 operations that require a blessing (almost everything) will fail if
-     * attempted before this is true.
-     *
-     * The simplest usage is 1) There are no blessings. 2) An activity starts
-     * and calls V23Manager.init. 2) init notices there are no blessings and
-     * calls startActivityForResult 3) meanwhile, the activity and/or its
-     * components still run, but can test isBlessed before attempting anything
-     * requiring blessings. The activity will soon be re-initialized anyway. 4)
-     * user kicked over into 'account manager', gets a blessing, and the
-     * activity is restarted, this time with isBlessed == true.
-     */
-    public boolean isBlessed() {
-        return mBlessings != null;
-    }
-
-    /**
-     * Returns the blessings for this process.
-     */
-    public Blessings getBlessings() {
-        return mBlessings;
+        Log.d(TAG, "Attempting to get blessings.");
+        ListenableFuture<Blessings> f = BlessingsManager.getBlessings(
+                mV23Ctx, activity, BLESSINGS_KEY, true);
+        Futures.addCallback(f, future);
     }
 
     public void shutdown() {
@@ -128,27 +146,28 @@
         // Disabled while debugging network performance / visibility issues.
         if (useProxy) {
             ListenSpec spec = V.getListenSpec(mV23Ctx).withProxy("proxy");
-            //ListenSpec spec = V.getListenSpec(mV23Ctx).withAddress(
-            //        new ListenSpec.Address("tcp", "0.0.0.0:0"));
-            Log.d(TAG, "spec : " + spec.toString());
-            Log.d(TAG, "spec proxy: " + spec.getProxy());
+            Log.d(TAG, "listenSpec = " + spec.toString() + " p=" + spec.getProxy());
             return V.withListenSpec(mV23Ctx, spec);
         }
         return mV23Ctx;
     }
 
-    public VContext makeServer(String mountName, Object server) throws VException {
+    public VContext makeServerContext(String mountName, Object server) throws VException {
         return V.withNewServer(
-                        getListenContext(),
-                        mountName,
-                        server,
-                        VSecurity.newAllowEveryoneAuthorizer());
+                getListenContext(),
+                mountName,
+                server,
+                VSecurity.newAllowEveryoneAuthorizer());
     }
 
     public Server getServer(VContext mServerCtx) {
         return V.getServer(mServerCtx);
     }
 
+    public VContext contextWithTimeout(Duration timeout) {
+        return mV23Ctx.withTimeout(timeout);
+    }
+
     public static class Singleton {
         private static volatile V23Manager instance;
 
@@ -165,8 +184,4 @@
             return result;
         }
     }
-
-    public VContext contextWithTimeout(Duration timeout) {
-        return mV23Ctx.withTimeout(timeout);
-    }
 }
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 4905964..e8129ba 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
@@ -6,6 +6,9 @@
 
 import android.graphics.Bitmap;
 
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
 import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
 import java.util.List;
@@ -28,9 +31,9 @@
  * Handles the advertising of a moment.
  */
 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<>();
-    static final String NO_MOUNT_NAME = "";
     private final V23Manager mV23Manager;
     private final Moment mMoment;
 
@@ -64,7 +67,8 @@
             throw new IllegalStateException("Already advertising.");
         }
         try {
-            mServerCtx = mV23Manager.makeServer(NO_MOUNT_NAME, new MomentServer());
+            mServerCtx = mV23Manager.makeServerContext(
+                    NO_MOUNT_NAME, new MomentServer());
         } catch (VException e) {
             throw new IllegalStateException("Unable to start service.", e);
         }
@@ -113,13 +117,13 @@
         private byte[] mRawBytes = null;  // lazy init
         private byte[] mThumbBytes = null;  // lazy init
 
-        public MomentWireData getBasics(VContext ctx, ServerCall call)
-                throws VException {
+        public ListenableFuture<MomentWireData> getBasics(
+                VContext ctx, ServerCall call) {
             MomentWireData data = new MomentWireData();
             data.setAuthor(mMoment.getAuthor());
             data.setCaption(mMoment.getCaption());
             data.setCreationTime(mMoment.getCreationTime().getMillis());
-            return data;
+            return Futures.immediateFuture(data);
         }
 
         private byte[] makeBytes(Bitmap bitmap) {
@@ -142,14 +146,13 @@
             return mThumbBytes;
         }
 
-        public byte[] getThumbImage(VContext ctx, ServerCall call)
-                throws VException {
-            return getThumbBytes();
+        public ListenableFuture<byte[]> getThumbImage(
+                VContext ctx, ServerCall call) {
+            return Futures.immediateFuture(getThumbBytes());
         }
 
-        public byte[] getFullImage(VContext ctx, ServerCall call)
-                throws VException {
-            return getFullBytes();
+        public ListenableFuture<byte[]> getFullImage(VContext ctx, ServerCall call) {
+            return Futures.immediateFuture(getFullBytes());
         }
     }
 
diff --git a/projects/moments/app/src/main/java/io/v/moments/model/FileUtil.java b/projects/moments/app/src/main/java/io/v/moments/model/FileUtil.java
index 3ee7697..4bb073b 100644
--- a/projects/moments/app/src/main/java/io/v/moments/model/FileUtil.java
+++ b/projects/moments/app/src/main/java/io/v/moments/model/FileUtil.java
@@ -40,7 +40,8 @@
      */
     public static void rmMinusF(File path) {
         if (!path.exists()) {
-            throw new IllegalArgumentException(path.getAbsolutePath());
+            throw new IllegalArgumentException(
+                    "non-existent path: " + path.getAbsolutePath());
         }
         File[] contents = path.listFiles();
         if (contents != null) {
@@ -48,6 +49,9 @@
                 rmMinusF(f);
             }
         }
-        path.delete();
+        if (!path.delete()) {
+            throw new IllegalStateException(
+                    "unable to delete " + path.getAbsolutePath());
+        }
     }
 }
diff --git a/projects/moments/app/src/main/java/io/v/moments/model/MomentFactoryImpl.java b/projects/moments/app/src/main/java/io/v/moments/model/MomentFactoryImpl.java
index 60273c9..c87d576 100644
--- a/projects/moments/app/src/main/java/io/v/moments/model/MomentFactoryImpl.java
+++ b/projects/moments/app/src/main/java/io/v/moments/model/MomentFactoryImpl.java
@@ -95,7 +95,7 @@
                 p.getBoolean(km.get(F.ADVERTISING), false) ? AdState.ON : AdState.OFF);
     }
 
-    private class KeyMaker {
+    private static class KeyMaker {
         private final String mPrefix;
 
         KeyMaker(String prefix) {
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 f60e9eb..dbc7e89 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
@@ -27,6 +27,8 @@
 import android.widget.CompoundButton;
 import android.widget.Toast;
 
+import com.google.common.util.concurrent.FutureCallback;
+
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
@@ -49,6 +51,7 @@
 import io.v.moments.model.MomentFactoryImpl;
 import io.v.moments.model.StateStore;
 import io.v.v23.context.VContext;
+import io.v.v23.security.Blessings;
 
 /**
  * This app allows the user to take photos and advertise them on the network.
@@ -137,19 +140,33 @@
 
         setContentView(R.layout.activity_main);
 
-        // This call might leave this activity to get a Vanadium blessing.
-        // On return, should trigger onActivityResult as expected.
-        mV23Manager.init(getApplicationContext(), this);
-
-        if (!mPermissionManager.haveAllPermissions()) {
-            Log.d(TAG, "Post v23 trying to get permissions");
-            mPermissionManager.obtainPermission();
-        }
+        // This will look in prefs for a Vanadium blessing, and if not
+        // found will leave this activity (onStop likely to be called) and
+        // return via a start intent (not via onActivityResult).
+        mV23Manager.init(this, onBlessings());
 
         wireUxToDataModel();
         initializeOrRestore(savedInstanceState);
     }
 
+    private FutureCallback<Blessings> onBlessings() {
+        return new FutureCallback<Blessings>() {
+            @Override
+            public void onSuccess(Blessings b) {
+                Log.d(TAG, "Got blessings!");
+                if (!mPermissionManager.haveAllPermissions()) {
+                    Log.d(TAG, "Obtaining permissions");
+                    mPermissionManager.obtainPermission();
+                }
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                Log.d(TAG, "Failure to get blessings, nothing will work.", t);
+            }
+        };
+    }
+
     /**
      * This method builds the app's object graph, and intentionally has no
      * branches that depend on state loaded from the app's prefs or instance
@@ -236,20 +253,6 @@
     }
 
     @Override
-    public void onResume() {
-        super.onResume();
-        logState("onResume");
-        if (mV23Manager.isBlessed()) {
-            if (mPermissionManager.haveAllPermissions()) {
-            } else {
-                if (!mPermissionManager.isRequestInProgress()) {
-                    mPermissionManager.obtainPermission();
-                }
-            }
-        }
-    }
-
-    @Override
     public void onPause() {
         super.onPause();
         logState("onPause");
@@ -275,7 +278,6 @@
         startActivityForResult(intent, RequestCode.CAPTURE_IMAGE);
     }
 
-
     /**
      * On new permissions, assume it's a fresh install and wipe the working
      * directory.   File formats and naming might have changed.  This is not
@@ -334,13 +336,10 @@
                     public void run() {
                         try {
                             mAdvertiserFactory.getOrMake(moment).advertiseStart();
-                            // This toast noisy if not debugging.
-                            // toast("Started advertising " + moment.getCaption());
                             Log.d(TAG, "Started advertising " + moment.getCaption());
-
                         } catch (Exception e) {
                             e.printStackTrace();
-                            toast("Had problem starting advertising.");
+                            toast("Unable to advertise - see log.");
                         }
                     }
                 });
@@ -353,6 +352,12 @@
     }
 
     @Override
+    public void onResume() {
+        super.onResume();
+        logState("onResume");
+    }
+
+    @Override
     protected void onDestroy() {
         super.onDestroy();
         logState("onDestroy");
diff --git a/projects/moments/app/src/test/java/io/v/moments/lib/DiscoveredListTest.java b/projects/moments/app/src/test/java/io/v/moments/lib/DiscoveredListTest.java
index 295d4dd..5a91cdc 100644
--- a/projects/moments/app/src/test/java/io/v/moments/lib/DiscoveredListTest.java
+++ b/projects/moments/app/src/test/java/io/v/moments/lib/DiscoveredListTest.java
@@ -46,7 +46,6 @@
     static final Thing THING0 = makeThing("hey0");
     static final Thing THING1 = makeThing("hey1");
     static final Id ID0 = THING0.getId();
-    static final Id ID1 = THING1.getId();
 
     @Rule
     public ExpectedException mThrown = ExpectedException.none();
@@ -55,22 +54,12 @@
     ListObserver mObserver;
     @Mock
     AdConverter<Thing> mConverter;
-
-    final Set<Id> mRejectIds = new HashSet<>();
-
-    IdSet mRejects = new IdSet() {
-        @Override
-        public boolean contains(Id id) {
-            return mRejectIds.contains(id);
-        }
-    };
-
+    @Mock
+    IdSet mRejects;
     @Mock
     Handler mHandler;
-
-    io.v.v23.discovery.Service mAdvertisement =
-            new Service(UUID.randomUUID().toString(), "someInstance", "iface", new Attributes(),
-                    ImmutableList.<String>of(), new Attachments());
+    @Mock
+    io.v.v23.discovery.Service mAdvertisement;
 
     @Captor
     ArgumentCaptor<Runnable> mRunnable;
@@ -85,6 +74,9 @@
     @Before
     public void setup() {
         mList = new DiscoveredList<>(mConverter, mRejects, mHandler);
+        // By default, ID0 is not rejected.
+        when(mRejects.contains(ID0)).thenReturn(false);
+        when(mAdvertisement.getInstanceId()).thenReturn(ID0.toString());
     }
 
     @Test
@@ -150,6 +142,9 @@
         assertEquals(THING0, mList.get(0));
     }
 
+    /**
+     * Advertisements from others (not-self) should be handled/observed.
+     */
     @Test
     public void handleNormalFound() throws Exception {
         mList.setObserver(mObserver);
@@ -164,11 +159,14 @@
         verify(mObserver).notifyItemInserted(0);
     }
 
+    /**
+     * Advertisements from self should not be handled/observed.
+     */
     @Test
     public void handleSelfFound() throws Exception {
         mList.setObserver(mObserver);
 
-        mRejectIds.add(ID0);
+        when(mRejects.contains(ID0)).thenReturn(true);
 
         mList.scanUpdateReceived(new Update.Found(new Found(mAdvertisement)));
 
@@ -177,6 +175,9 @@
         assertEquals(0, mList.size());
     }
 
+    /**
+     * Do nothing upon loss of an unrecognized advertisement.
+     */
     @Test
     public void handleUnrecognizedLost() throws Exception {
         mList.setObserver(mObserver);
@@ -192,11 +193,15 @@
         verifyZeroInteractions(mObserver);
     }
 
+    /**
+     * Do nothing upon loss of a self-advertisement.
+     */
     @Test
     public void handleSelfLost() throws Exception {
         mList.setObserver(mObserver);
 
-        mRejectIds.add(ID0);
+        when(mRejects.contains(ID0)).thenReturn(true);
+
         mList.scanUpdateReceived(new Update.Lost(new Lost(mAdvertisement)));
 
         verify(mHandler).post(mRunnable.capture());
@@ -208,6 +213,9 @@
         verifyZeroInteractions(mObserver);
     }
 
+    /**
+     * Handle the loss of a non-self advertisement.
+     */
     @Test
     public void handleNormalLost() throws Exception {
         mList.setObserver(mObserver);
@@ -215,9 +223,7 @@
         verify(mObserver).notifyItemInserted(0);
         assertEquals(1, mList.size());
 
-        mList.scanUpdateReceived(new Update.Lost(new Lost(
-                new Service(THING0.getId().toString(), "foo", "bar", new Attributes(),
-                        ImmutableList.<String>of(), new Attachments()))));
+        mList.scanUpdateReceived(new Update.Lost(new Lost(mAdvertisement)));
 
         verify(mHandler).post(mRunnable.capture());
 
diff --git a/projects/moments/app/src/test/java/io/v/moments/lib/IdTest.java b/projects/moments/app/src/test/java/io/v/moments/lib/IdTest.java
index cc0f8f1..15b427d 100644
--- a/projects/moments/app/src/test/java/io/v/moments/lib/IdTest.java
+++ b/projects/moments/app/src/test/java/io/v/moments/lib/IdTest.java
@@ -29,7 +29,6 @@
         Id id1 = Id.makeRandom();
         Id id2 = Id.makeRandom();
         assertNotEquals(id1, id2);
-        assertNotEquals(id1.toLong(), id2.toLong());
     }
 
     @Test
@@ -39,9 +38,9 @@
     }
 
     @Test
-    public void constructionFailure() {
+    public void fromStringFailure() {
         mThrown.expect(java.lang.IllegalArgumentException.class);
-        mThrown.expectMessage("Invalid UUID string: pizza");
+        mThrown.expectMessage("For input string: \"pizza\"");
         Id id = Id.fromString("pizza");
     }
 }
diff --git a/projects/moments/app/src/test/java/io/v/moments/model/AdvertiserImplTest.java b/projects/moments/app/src/test/java/io/v/moments/model/AdvertiserImplTest.java
index 410110e..c97d9a4 100644
--- a/projects/moments/app/src/test/java/io/v/moments/model/AdvertiserImplTest.java
+++ b/projects/moments/app/src/test/java/io/v/moments/model/AdvertiserImplTest.java
@@ -75,7 +75,7 @@
     public void setup() throws Exception {
         mAttrs = new Attributes(makeFakeAttributes());
 
-        when(mV23Manager.makeServer(
+        when(mV23Manager.makeServerContext(
                 eq(AdvertiserImpl.NO_MOUNT_NAME),
                 any(AdvertiserImpl.MomentServer.class))).thenReturn(mServerContext);
         when(mV23Manager.getServer(mServerContext)).thenReturn(mServer);