Merge branch 'master' into ble
diff --git a/lib/build.gradle b/lib/build.gradle
index 602e1b3..2ed0b9a 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -105,6 +105,7 @@
 }
 
 tasks.'processResources'.dependsOn(generateVdl)
+tasks.'compileJava'.dependsOn(generateVdl)
 
 task checkVanadiumEnvironment {
     VanadiumEnvironment.getVanadiumEnvironment()
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckChooserActivity.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckChooserActivity.java
index 4dd4dea..2790330 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckChooserActivity.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckChooserActivity.java
@@ -13,6 +13,10 @@
 import android.support.v4.widget.DrawerLayout;
 
 import io.v.android.apps.syncslides.db.DB;
+import io.v.android.apps.syncslides.discovery.V23Manager;
+import io.v.android.v23.services.blessing.BlessingCreationException;
+import io.v.v23.security.Blessings;
+import io.v.v23.verror.VException;
 
 public class DeckChooserActivity extends AppCompatActivity
         implements NavigationDrawerFragment.NavigationDrawerCallbacks {
@@ -27,7 +31,11 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        // Do this initialization early on in case it needs to start the AccountManager.
+        Log.d(TAG, "onCreate");
+        // Immediately initialize V23, possibly sending user to the
+        // AccountManager to get blessings.
+        V23Manager.Singleton.get().init(getApplicationContext(), this);
+
         mDB = DB.Singleton.get(getApplicationContext());
         mDB.init(this);
 
@@ -79,10 +87,12 @@
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
-        if (mDB.onActivityResult(requestCode, resultCode, data)) {
+        Log.d(TAG, "onActivityResult");
+        if (V23Manager.onActivityResult(
+                getApplicationContext(), requestCode, resultCode, data)) {
+            Log.d(TAG, "did the v23 result");
             return;
         }
         // Any other activity results would be handled here.
     }
-
 }
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java
index c525a9c..3555157 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java
@@ -41,7 +41,8 @@
             throw new IllegalStateException("Wrong lifecycle.");
         }
         Log.d(TAG, "Starting.");
-        DiscoveryManager dm = DiscoveryManager.Singleton.get();
+        DiscoveryManager dm = DiscoveryManager.make();
+        // Listening stops below in mLiveDecks.discard.
         dm.setListener(this);
         dm.start(context);
         mLiveDecks = dm;
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java
index c55e62d..48fafc0 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java
@@ -13,7 +13,8 @@
 import android.widget.Toast;
 
 import io.v.android.apps.syncslides.db.DB;
-import io.v.android.apps.syncslides.discovery.ParticipantPeer;
+import io.v.android.apps.syncslides.discovery.ParticipantServerImpl;
+import io.v.android.apps.syncslides.discovery.V23Manager;
 import io.v.android.apps.syncslides.model.Deck;
 import io.v.android.apps.syncslides.model.DeckImpl;
 import io.v.android.apps.syncslides.model.Participant;
@@ -34,15 +35,28 @@
     private String mPresentationId = "randomPresentation1";
     private boolean mSynced;
 
+    /**
+     * Once a user clicks 'present' - which happens at some unpredictable time
+     * after onStart, the user begins presenting the deck, and the system must
+     * advertise the presentation.   Once advertising is started, it doesn't
+     * stop until onStop is called.  If the activity is paused, advertising
+     * should continue.
+     */
+    private boolean mShouldBeAdvertising;
+    private boolean mIsAdvertising;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Log.d(TAG, "onCreate");
-        // Do this initialization early on in case it needs to start the AccountManager.
+        // Immediately initialize V23, possibly sending user to the
+        // AccountManager to get blessings.
+        V23Manager.Singleton.get().init(getApplicationContext(), this);
         DB.Singleton.get(getApplicationContext()).init(this);
-
         setContentView(R.layout.activity_presentation);
 
+        mShouldBeAdvertising = false;
+        mIsAdvertising = false;
         if (savedInstanceState == null) {
             Log.d(TAG, "savedInstanceState is null");
             mDeck = DeckImpl.fromBundle(getIntent().getExtras());
@@ -54,6 +68,10 @@
             mDeck = DeckImpl.fromBundle(savedInstanceState);
             mRole = (Role) savedInstanceState.get(Participant.B.PARTICIPANT_ROLE);
             mSynced = savedInstanceState.getBoolean(Participant.B.PARTICIPANT_SYNCED);
+            mShouldBeAdvertising = savedInstanceState.getBoolean(Participant.B.PARTICIPANT_SHOULD_ADV);
+            if (mShouldBeAdvertising) {
+                Log.d(TAG, "Need to restore advertising");
+            }
         }
 
         // TODO(jregan): This appears to be an attempt to avoid fragment
@@ -63,6 +81,10 @@
             return;
         }
 
+        if (mShouldBeAdvertising){
+            startAdvertising();
+        }
+
         getSupportActionBar().setTitle(mDeck.getTitle());
 
         // If this is an audience member, we want them to jump straight to the fullscreen view.
@@ -74,32 +96,41 @@
     }
 
     @Override
-    protected void onSaveInstanceState(Bundle b) {
-        Log.d(TAG, "onSaveInstanceState1");
-        super.onSaveInstanceState(b);
-        Log.d(TAG, "onSaveInstanceState2");
-        packBundle(b);
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        Log.d(TAG, "onActivityResult");
+        if (V23Manager.onActivityResult(
+                getApplicationContext(), requestCode, resultCode, data)) {
+            Log.d(TAG, "did the v23 result");
+            return;
+        }
+        // Any other activity results would be handled here.
     }
 
-    private Bundle packBundle(Bundle b) {
+    @Override
+    protected void onSaveInstanceState(Bundle b) {
+        Log.d(TAG, "onSaveInstanceState");
+        super.onSaveInstanceState(b);
         mDeck.toBundle(b);
         b.putSerializable(Participant.B.PARTICIPANT_ROLE, mRole);
         b.putBoolean(Participant.B.PARTICIPANT_SYNCED, mSynced);
-        return b;
+        b.putBoolean(Participant.B.PARTICIPANT_SHOULD_ADV, mShouldBeAdvertising);
     }
 
     @Override
     protected void onStart() {
         super.onStart();
         Log.d(TAG, "onStart");
+        if (mShouldBeAdvertising){
+            startAdvertising();
+        }
     }
 
     @Override
     protected void onStop() {
         super.onStop();
         Log.d(TAG, "onStop");
-        // Don't shutdown v23 at this point.
-        // TODO(jregan): Stop advertising the live presentation if necessary.
+        stopAdvertising();
     }
 
     /**
@@ -125,15 +156,43 @@
         }
     }
 
-    /**
-     * Start the service for advertising a presentation.
-     */
-    private void beginAdvertising() {
-        Log.d(TAG, "beginAdvertising");
-        Intent intent = new Intent(this, ParticipantPeer.class);
-        intent.putExtras(packBundle(new Bundle()));
-        stopService(intent);
-        startService(intent);
+    private boolean shouldUseV23() {
+        return Participant.ENABLE_MT_DISCOVERY && V23Manager.Singleton.get().isBlessed();
+    }
+
+    private void startAdvertising() {
+        Log.d(TAG, "startAdvertising");
+        mShouldBeAdvertising = true;
+        if (mIsAdvertising) {
+            Log.d(TAG, "Already advertising.");
+            return;
+        }
+        if (shouldUseV23()) {
+            V23Manager.Singleton.get().mount(
+                    Participant.Mt.makeMountName(mDeck),
+                    new ParticipantServerImpl(mDeck));
+            Log.d(TAG, "MT advertising started.");
+        } else {
+            Log.d(TAG, "No means to start advertising.");
+        }
+        mIsAdvertising = true;
+    }
+
+    private void stopAdvertising() {
+        Log.d(TAG, "stopAdvertising");
+        if (!mIsAdvertising) {
+            Log.d(TAG, "Not advertising.");
+            return;
+        }
+        if (shouldUseV23()) {
+            // At the moment, only one service can be mounted, and this call
+            // will unmount it if mounted, else do nothing.
+            V23Manager.Singleton.get().unMount();
+            Log.d(TAG, "MT advertising stopped.");
+        } else {
+            Log.d(TAG, "No advertising to stop.");
+        }
+        mIsAdvertising = false;
     }
 
     /**
@@ -148,9 +207,7 @@
                 Log.i(TAG, "Started presentation");
                 Toast.makeText(getApplicationContext(), "Started presentation",
                         Toast.LENGTH_SHORT).show();
-                if (Participant.ENABLE_MT_DISCOVERY) {
-                    beginAdvertising();
-                }
+                startAdvertising();
             }
         });
         mRole = Role.PRESENTER;
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java
index a60ebc6..d3c193a 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java
@@ -30,7 +30,7 @@
                     if (result == null) {
                         // Switch between FakeDB and SyncbaseDB by commenting out one.
                         instance = result = new FakeDB(context);
-                        //instance = result = new SyncbaseDB(context);
+                        // instance = result = new SyncbaseDB(context);
                     }
                 }
             }
@@ -48,14 +48,6 @@
     void init(Activity activity);
 
     /**
-     * If init() sent an intent to another Activity, the result must be forwarded
-     * from our app's activity to this method.
-     *
-     * @return true if the requestCode matches an intent sent by this implementation.
-     */
-    boolean onActivityResult(int requestCode, int resultCode, Intent data);
-
-    /**
      * Provides a list of elements via an API that fits well with RecyclerView.Adapter.
      */
     interface DBList<E> {
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java
index 1c0789a..7eaddeb 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java
@@ -228,12 +228,6 @@
     }
 
     @Override
-    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
-        // Nothing to do.
-        return false;
-    }
-
-    @Override
     public void askQuestion(String deckId, String presentationId,
                             String firstName, String lastName) {
         // Nothing to do.
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java
index 2913c4c..e6b487a 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java
@@ -6,7 +6,6 @@
 
 import android.app.Activity;
 import android.content.Context;
-import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Handler;
@@ -32,37 +31,27 @@
 import java.util.UUID;
 
 import io.v.android.apps.syncslides.R;
+import io.v.android.apps.syncslides.discovery.V23Manager;
 import io.v.android.apps.syncslides.model.Deck;
 import io.v.android.apps.syncslides.model.DeckImpl;
 import io.v.android.apps.syncslides.model.Listener;
 import io.v.android.apps.syncslides.model.NoopList;
 import io.v.android.apps.syncslides.model.Slide;
 import io.v.android.apps.syncslides.model.SlideImpl;
-import io.v.android.libs.security.BlessingsManager;
 import io.v.android.v23.V;
-import io.v.android.v23.services.blessing.BlessingCreationException;
-import io.v.android.v23.services.blessing.BlessingService;
 import io.v.impl.google.naming.NamingUtil;
 import io.v.impl.google.services.syncbase.SyncbaseServer;
 import io.v.v23.context.CancelableVContext;
 import io.v.v23.context.VContext;
-import io.v.v23.namespace.Namespace;
-import io.v.v23.naming.Endpoint;
-import io.v.v23.naming.GlobReply;
-import io.v.v23.naming.MountEntry;
-import io.v.v23.naming.MountedServer;
 import io.v.v23.rpc.Server;
 import io.v.v23.security.BlessingPattern;
-import io.v.v23.security.Blessings;
-import io.v.v23.security.VPrincipal;
-import io.v.v23.security.VSecurity;
 import io.v.v23.security.access.AccessList;
 import io.v.v23.security.access.Constants;
 import io.v.v23.security.access.Permissions;
-import io.v.v23.services.watch.ResumeMarker;
 import io.v.v23.services.syncbase.nosql.SyncgroupMemberInfo;
 import io.v.v23.services.syncbase.nosql.SyncgroupPrefix;
 import io.v.v23.services.syncbase.nosql.SyncgroupSpec;
+import io.v.v23.services.watch.ResumeMarker;
 import io.v.v23.syncbase.Syncbase;
 import io.v.v23.syncbase.SyncbaseApp;
 import io.v.v23.syncbase.SyncbaseService;
@@ -70,7 +59,6 @@
 import io.v.v23.syncbase.nosql.ChangeType;
 import io.v.v23.syncbase.nosql.Database;
 import io.v.v23.syncbase.nosql.DatabaseCore;
-import io.v.v23.syncbase.nosql.PrefixRange;
 import io.v.v23.syncbase.nosql.RowRange;
 import io.v.v23.syncbase.nosql.Stream;
 import io.v.v23.syncbase.nosql.Syncgroup;
@@ -84,11 +72,6 @@
 public class SyncbaseDB implements DB {
 
     private static final String TAG = "SyncbaseDB";
-    /**
-     * The intent result code for when we get blessings from the account manager.
-     * The value must not conflict with any other blessing result codes.
-     */
-    private static final int BLESSING_REQUEST = 200;
     private static final String SYNCBASE_APP = "syncslides";
     private static final String SYNCBASE_DB = "syncslides";
     private static final String DECKS_TABLE = "Decks";
@@ -97,13 +80,7 @@
     static final String CURRENT_SLIDE = "CurrentSlide";
     static final String QUESTIONS = "questions";
     private static final String SYNCGROUP_PRESENTATION_DESCRIPTION = "Live Presentation";
-    private static final String PI_MILK_CRATE = "192.168.86.254:8101";
 
-    // If SyncbaseDB needs to start the AccountManager to get blessings, it will not
-    // finish its initialization, but the fragment that is trying to initialize
-    // DB will continue to load and use DB.  That fragment will reload when the
-    // AccountManager is finished, so if mInitialized is false, any DB methods should
-    // return noop values.
     private boolean mInitialized = false;
     private Handler mHandler;
     private Permissions mPermissions;
@@ -125,11 +102,12 @@
 
     @Override
     public void init(Activity activity) {
+        Log.d(TAG, "init");
         if (mInitialized) {
+            Log.d(TAG, "already initialized");
             return;
         }
         mHandler = new Handler(Looper.getMainLooper());
-        mVContext = V.init(mContext);
         // TODO(kash): Set proper ACLs.
         AccessList acl = new AccessList(
                 ImmutableList.of(new BlessingPattern("...")), ImmutableList.<String>of());
@@ -138,68 +116,22 @@
                 Constants.READ.getValue(), acl,
                 Constants.WRITE.getValue(), acl,
                 Constants.ADMIN.getValue(), acl));
-        getBlessings(activity);
-    }
 
-    private void getBlessings(Activity activity) {
-        Blessings blessings = null;
-        try {
-            // See if there are blessings stored in shared preferences.
-            blessings = BlessingsManager.getBlessings(mContext);
-        } catch (VException e) {
-            handleError("Error getting blessings from shared preferences " + e.getMessage());
-        }
-        if (blessings == null) {
-            // Request new blessings from the account manager via an intent.  This intent
-            // will call back to onActivityResult() which will continue with
-            // configurePrincipal().
-            refreshBlessings(activity);
+        // If blessings aren't in place, the fragment that called this
+        // initialization may continue to load and use DB, but nothing will
+        // work so DB methods should return noop values.  It's assumed that
+        // the calling fragment will send the user to the AccountManager,
+        // accept blessings on return, then re-call this init.
+        if (V23Manager.Singleton.get().isBlessed()) {
+            Log.d(TAG, "no blessings.");
             return;
         }
-        configurePrincipal(blessings);
-    }
-
-    private void refreshBlessings(Activity activity) {
-        Intent intent = BlessingService.newBlessingIntent(mContext);
-        activity.startActivityForResult(intent, BLESSING_REQUEST);
-    }
-
-    @Override
-    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (requestCode == BLESSING_REQUEST) {
-            try {
-                byte[] blessingsVom = BlessingService.extractBlessingReply(resultCode, data);
-                Blessings blessings = (Blessings) VomUtil.decode(blessingsVom, Blessings.class);
-                BlessingsManager.addBlessings(mContext, blessings);
-                Toast.makeText(mContext, "Success", Toast.LENGTH_SHORT).show();
-                configurePrincipal(blessings);
-            } catch (BlessingCreationException e) {
-                handleError("Couldn't create blessing: " + e.getMessage());
-            } catch (VException e) {
-                handleError("Couldn't derive blessing: " + e.getMessage());
-            }
-            return true;
-        }
-        return false;
-    }
-
-    private void configurePrincipal(Blessings blessings) {
-        // TODO(kash): Probably better to do this not in the UI thread.
-        try {
-            VPrincipal p = V.getPrincipal(mVContext);
-            p.blessingStore().setDefaultBlessings(blessings);
-            p.blessingStore().set(blessings, new BlessingPattern("..."));
-            VSecurity.addToRoots(p, blessings);
-        } catch (VException e) {
-            handleError(String.format(
-                    "Couldn't set local blessing %s: %s", blessings, e.getMessage()));
-            return;
-        }
-        setupSyncbase(blessings);
+        mVContext = V23Manager.Singleton.get().getVContext();
+        setupSyncbase();
     }
 
     // TODO(kash): Run this in an AsyncTask so it doesn't block the UI.
-    private void setupSyncbase(Blessings blessings) {
+    private void setupSyncbase() {
         // Prepare the syncbase storage directory.
         File storageDir = new File(mContext.getFilesDir(), "syncbase");
         storageDir.mkdirs();
@@ -215,7 +147,7 @@
             }
             mVContext = SyncbaseServer.withNewServer(mVContext, new SyncbaseServer.Params()
                     .withPermissions(mPermissions)
-                    .withName(NamingUtil.join("/", PI_MILK_CRATE, id))
+                    .withName(V23Manager.syncName(id))
                     .withStorageRootDir(storageDir.getAbsolutePath()));
         } catch (SyncbaseServer.StartException e) {
             handleError("Couldn't start syncbase server");
@@ -301,7 +233,7 @@
                                 Arrays.asList(
                                         new SyncgroupPrefix(PRESENTATIONS_TABLE, prefix),
                                         new SyncgroupPrefix(DECKS_TABLE, deckId)),
-                                Arrays.asList(NamingUtil.join("/", PI_MILK_CRATE, "sg")),
+                                Arrays.asList(V23Manager.syncName("sg")),
                                 false
                         ),
                         new SyncgroupMemberInfo((byte) 10));
@@ -314,18 +246,7 @@
             }
             Log.i(TAG, "Finished creating syncgroup");
 
-            Namespace namespace = V.getNamespace(mVContext);
-            namespace.setRoots(Arrays.asList("/" + PI_MILK_CRATE));
-            for (GlobReply reply : namespace.glob(mVContext, "...")) {
-                if (reply instanceof GlobReply.Entry) {
-                    MountEntry entry = ((GlobReply.Entry) reply).getElem();
-                    Log.d(TAG, "Entry: " + entry.getName());
-                    for (MountedServer server : entry.getServers()) {
-                        String endPoint = server.getServer();
-                        Log.d(TAG, "Got endPoint = " + endPoint);
-                    }
-                }
-            }
+            V23Manager.Singleton.get().scan("...");
 
             // TODO(kash): Create a syncgroup for Notes?  Not sure if we should do that
             // here or somewhere else.  We're not going to demo sync across a user's
@@ -363,19 +284,7 @@
                     for (String member : syncgroup.getMembers(mVContext).keySet()) {
                         Log.i(TAG, "Member: " + member);
                     }
-                    Namespace namespace = V.getNamespace(mVContext);
-                    namespace.setRoots(Arrays.asList("/" + PI_MILK_CRATE));
-                    for (GlobReply reply : namespace.glob(mVContext, "...")) {
-                        if (reply instanceof GlobReply.Entry) {
-                            MountEntry entry = ((GlobReply.Entry) reply).getElem();
-                            Log.d(TAG, "Entry: " + entry.getName());
-                            for (MountedServer server : entry.getServers()) {
-                                String endPoint = server.getServer();
-                                Log.d(TAG, "Got endPoint = " + endPoint);
-                            }
-                        }
-                    }
-
+                    V23Manager.Singleton.get().scan("...");
                     Handler handler = new Handler(Looper.getMainLooper());
                     handler.post(new Runnable() {
                         @Override
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/DiscoveryManager.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/DiscoveryManager.java
index 175c4ea..539d5b8 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/DiscoveryManager.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/DiscoveryManager.java
@@ -20,15 +20,13 @@
 /**
  * Singleton Discovery manager.
  *
- * Scans a mounttable to look for presentations, and permits mounting of
- * a service representing a live presentation.
+ * Scans a mounttable to look for presentations, and permits mounting of a
+ * service representing a live presentation.
  */
 public class DiscoveryManager implements DB.DBList<Deck>, Moderator.Observer {
     private static final String TAG = "DiscoveryManager";
     // Search result indicator.
     public static final int NOT_FOUND = -1;
-    // Manages creation, mounting and unmounting of V23 services.
-    private final V23Manager mV23Manager;
     // Scans a mount table to understand who's 'giving a presentation', hence
     // the name moderator.  With each scan, determines who's new
     // (freshman), still there (senior) and gone (graduated).
@@ -36,11 +34,25 @@
     // Runs the moderator's scan repeatedly.
     private final PeriodicTasker mTasker = new PeriodicTasker();
     private final List<Participant> mParticipants = new ArrayList<>();
-    private Listener mListener;
     private final Handler mHandler;
+    private Listener mListener;
 
-    private DiscoveryManager(V23Manager manager, Moderator moderator) {
-        mV23Manager = manager;
+    public static DiscoveryManager make() {
+        // If blessings not in place, use fake data.
+        boolean useRealDiscovery =
+                Participant.ENABLE_MT_DISCOVERY &&
+                        V23Manager.Singleton.get().isBlessed();
+        if (useRealDiscovery) {
+            Log.d(TAG, "Using real discovery.");
+            return new DiscoveryManager(
+                    new Moderator(new ParticipantScannerMt()));
+        }
+        Log.d(TAG, "Using fake discovery.");
+        return new DiscoveryManager(
+                new Moderator(new ParticipantScannerFake()));
+    }
+
+    private DiscoveryManager(Moderator moderator) {
         mModerator = moderator;
         mHandler = new Handler(Looper.getMainLooper());
     }
@@ -50,16 +62,11 @@
             throw new IllegalStateException("Must have a listener.");
         }
         Log.d(TAG, "Starting");
-        if (Participant.ENABLE_MT_DISCOVERY) {
-            if (mV23Manager == null) {
-                throw new IllegalStateException("Must have V23.");
-            }
-            mV23Manager.init(context);
-        }
         // The observer is the guy who implements onTaskDone, and wants
         // to be notified when a scan is complete.
         mModerator.setObserver(this);
         mTasker.start(mModerator);
+        Log.d(TAG, "Done Starting");
         return this;
     }
 
@@ -68,20 +75,6 @@
         mTasker.stop();
     }
 
-    /**
-     * Stops discovery and underlying V23 services.
-     */
-    public void stopEverything() {
-        Log.d(TAG, "Stopping everything.");
-        stop();
-        if (Participant.ENABLE_MT_DISCOVERY) {
-            if (mV23Manager == null) {
-                throw new IllegalStateException("Must have V23.");
-            }
-            mV23Manager.shutdown(V23Manager.Behavior.STRICT);
-        }
-    }
-
     @Override
     public void onTaskDone() {
         for (Participant p : mModerator.getFreshman()) {
@@ -130,35 +123,6 @@
         stop();
         mListener = null;
     }
-
-    public static class Singleton {
-        private static volatile DiscoveryManager instance;
-
-        public static DiscoveryManager get() {
-            DiscoveryManager result = instance;
-            if (instance == null) {
-                synchronized (Singleton.class) {
-                    result = instance;
-                    if (result == null) {
-                        instance = result = makeInstance();
-                    }
-                }
-            }
-            return result;
-        }
-
-        private static DiscoveryManager makeInstance() {
-            Log.d(TAG, "Creating singleton.");
-            if (Participant.ENABLE_MT_DISCOVERY) {
-                V23Manager manager = V23Manager.Singleton.get();
-                return new DiscoveryManager(
-                        manager,
-                        new Moderator(new ParticipantScannerMt(manager)));
-            }
-            return new DiscoveryManager(
-                    null, new Moderator(new ParticipantScannerFake()));
-        }
-    }
 }
 
 
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGenerator.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGenerator.java
deleted file mode 100644
index 1be0897..0000000
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGenerator.java
+++ /dev/null
@@ -1,15 +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.android.apps.syncslides.discovery;
-
-import java.util.List;
-
-/**
- * Generates a string for use as a V23 Service Name that won't collide with
- * an existing set.
- */
-public interface NameGenerator {
-    String getName(List<String> existing, String suggested);
-}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGeneratorByDate.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGeneratorByDate.java
deleted file mode 100644
index a44b739..0000000
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/NameGeneratorByDate.java
+++ /dev/null
@@ -1,28 +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.android.apps.syncslides.discovery;
-
-import org.joda.time.DateTime;
-import org.joda.time.format.DateTimeFormat;
-import org.joda.time.format.DateTimeFormatter;
-
-import java.util.List;
-
-/**
- * Generates a string for use as a V23 Service Name based on the current time
- * down to millisecond resolution. Ugly name, but very unlikely to collide.
- */
-class NameGeneratorByDate implements NameGenerator {
-    private static final DateTimeFormatter FMT =
-            DateTimeFormat.forPattern("yyyy_MM_dd_hh_mm_ss_SSSS");
-
-    /**
-     * Ignore incoming data, just pick 'now' down to the millisecond.
-     */
-    @Override
-    public String getName(List<String> existing, String suggested) {
-        return DateTime.now().toString(FMT);
-    }
-}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java
index 71a06bc..9452f5b 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java
@@ -4,23 +4,16 @@
 
 package io.v.android.apps.syncslides.discovery;
 
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.os.IBinder;
 import android.util.Log;
 
 import org.joda.time.DateTime;
 import org.joda.time.format.DateTimeFormat;
 import org.joda.time.format.DateTimeFormatter;
 
-import io.v.android.apps.syncslides.Role;
 import io.v.android.apps.syncslides.model.Deck;
 import io.v.android.apps.syncslides.model.DeckImpl;
 import io.v.android.apps.syncslides.model.Participant;
-import io.v.v23.context.VContext;
-import io.v.v23.rpc.ServerCall;
+import io.v.v23.verror.VException;
 
 /**
  * Someone taking part in a presentation.
@@ -37,53 +30,38 @@
  * and run it as a server, using the (public) userName as part of the mount
  * name.
  */
-public class ParticipantPeer extends Service implements Participant {
+public class ParticipantPeer implements Participant {
     private static final String TAG = "ParticipantPeer";
     private static final DateTimeFormatter TIME_FMT =
             DateTimeFormat.forPattern("hh_mm_ss_SSSS");
-    // V23 EndPoint of the V23 service representing the participant.
-    private String mEndpointStr;
-    // When did we last grab data from the endPoint?  Meaningful only in
-    // 'audience' mode, where the contents of mUserName etc. came from a remote
-    // server rather than from being fed into the ctor.
-    private DateTime mRefreshTime;
-    // Name of the user participating, intended to be visible to others. This
-    // can be a colloquial name as opposed to a 'real' name or email address
-    // extracted from a device or blessing.
+    // V23 name of the V23 service representing the participant.
+    private String mServiceName;
+    // Visible name of human presenter.
+    // TODO(jregan): Switch to VPerson or the model equivalent.
     private String mUserName;
+    // When did we last grab data from the endPoint?
+    private DateTime mRefreshTime;
     // Deck the user is presenting.  Can only present one at a time.
     private Deck mDeck;
-    // The role of the participant.
-    private Role mRole;
+    private ParticipantClient mClient = null;
 
-    public ParticipantPeer(String userName, Deck deck, String endPoint) {
+    public ParticipantPeer(String userName, Deck deck, String serviceName) {
         mUserName = userName;
         mDeck = deck;
-        mEndpointStr = endPoint;
-    }
-
-    public ParticipantPeer(String endPoint) {
-        this(Unknown.USER_NAME, DeckImpl.DUMMY, endPoint);
+        mServiceName = serviceName;
     }
 
     public ParticipantPeer(String userName, Deck deck) {
-        this(userName, deck, Unknown.END_POINT);
+        this(userName, deck, Unknown.SERVER_NAME);
     }
 
-    public ParticipantPeer() {
-        this(Unknown.END_POINT);
-    }
-
-    public static Participant fromBundle(Bundle b) {
-        return new ParticipantPeer(
-                b.getString(B.PARTICIPANT_NAME),
-                DeckImpl.fromBundle(b),
-                b.getString(B.PARTICIPANT_END_POINT));
+    public ParticipantPeer(String serviceName) {
+        this(Unknown.USER_NAME, DeckImpl.DUMMY, serviceName);
     }
 
     @Override
-    public String getEndPoint() {
-        return mEndpointStr;
+    public String getServiceName() {
+        return mServiceName;
     }
 
     @Override
@@ -91,120 +69,64 @@
         return mUserName;
     }
 
-    /**
-     * TODO(jregan): Assure legal mount name (remove blanks and such).
-     */
-    public String getMountName() {
-        return ParticipantScannerMt.ROOT_NAME + "/p_" + mDeck.getId();
-    }
-
     @Override
     public Deck getDeck() {
         return mDeck;
     }
 
     @Override
-    public Bundle toBundle() {
-        Bundle b = new Bundle();
-        b.putSerializable(Participant.B.PARTICIPANT_ROLE, mRole);
-        b.putString(B.PARTICIPANT_END_POINT, mEndpointStr);
-        b.putString(B.PARTICIPANT_NAME, mUserName);
-        mDeck.toBundle(b);
-        return b;
-    }
-
-    private void unpackBundle(Bundle b) {
-        mDeck = DeckImpl.fromBundle(b);
-        mRole = (Role) b.get(Participant.B.PARTICIPANT_ROLE);
-        mEndpointStr = b.getString(B.PARTICIPANT_END_POINT);
-        mUserName = b.getString(B.PARTICIPANT_NAME);
-    }
-
-    @Override
     public String toString() {
-        return mUserName + ":" + mDeck.getTitle() +
-                (mRefreshTime == null ?
-                        "" : ":" + mRefreshTime.toString(TIME_FMT));
+        return "[userName=\"" + mUserName +
+                "\", deck=" + mDeck +
+                ", time=" + getStringRefreshtime() + "]";
+    }
+
+    private String getStringRefreshtime() {
+        return mRefreshTime == null ?
+                "never" : mRefreshTime.toString(TIME_FMT);
     }
 
     @Override
     public boolean equals(Object obj) {
-        if (!(obj instanceof Participant)) {
+        if (!(obj instanceof ParticipantPeer)) {
             return false;
         }
-        return mEndpointStr.equals(((ParticipantPeer) obj).mEndpointStr);
+        ParticipantPeer p = (ParticipantPeer) obj;
+        return mServiceName.equals(p.mServiceName) && mDeck.equals(p.mDeck);
     }
 
     @Override
     public int hashCode() {
-        return mEndpointStr.hashCode() + mDeck.getTitle().hashCode();
+        return mServiceName.hashCode() + mDeck.hashCode();
     }
 
     /**
-     * Make an RPC on the mEndpointStr to get title, snapshot, etc.
+     * Make an RPC on the mServiceName to get title, snapshot, etc.
      */
     @Override
     public void refreshData() {
-        Log.d(TAG, "Refreshing data for participant " + mUserName);
-        // TODO(jregan): make the rpc
-        mRefreshTime = DateTime.now();
-    }
+        Log.d(TAG, "Initiating refresh");
 
-    /**
-     * Binding not necessary - this service just answers requests from the
-     * outside, and doesn't communicate with the parent app.
-     */
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        super.onStartCommand(intent, flags, startId);
-        Log.d(TAG, "onStartCommand");
-        // TODO(jregan): Unpack blessings from the intent and pass them into
-        // V.getPrincipal.
-        unpackBundle(intent.getExtras());
-        Log.d(TAG, "role = " + mRole + ", deck=" + mDeck);
-        V23Manager mgr = V23Manager.Singleton.get();
-        mgr.init(getApplicationContext());
-        ServerImpl server = new ServerImpl(this);
-        String mountName = getMountName();
-        Log.d(TAG, "mountName = " + mountName);
-        mEndpointStr = mgr.mount(mountName, server);
-        Log.d(TAG, "Got endpoint: " + mEndpointStr);
-        return START_REDELIVER_INTENT;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        V23Manager.Singleton.get().unMount();
-        Log.d(TAG, "###### onDestroy");
+        if (mClient == null) {
+            Log.d(TAG, "Grabbing client.");
+            mClient = ParticipantClientFactory.getParticipantClient(
+                    mServiceName);
+            Log.d(TAG, "Got client.");
+        }
+        try {
+            Log.d(TAG, "Calling get");
+            Description description = mClient.get(
+                    V23Manager.Singleton.get().getVContext());
+            mDeck = new DeckImpl(description.getTitle());
+            mRefreshTime = DateTime.now();
+            Log.d(TAG, "Completed refresh.");
+        } catch (VException e) {
+            e.printStackTrace();
+        }
     }
 
     private static class Unknown {
-        static final String END_POINT = "unknownEndPoint";
+        static final String SERVER_NAME = "unknownServerName";
         static final String USER_NAME = "unknownUserName";
     }
-
-    /**
-     * Implementation of VDL Participant service.
-     */
-    private class ServerImpl implements ParticipantServer {
-        private final Participant mParticipant;
-
-        public ServerImpl(Participant p) {
-            mParticipant = p;
-        }
-
-        public Description get(VContext ctx, ServerCall call)
-                throws io.v.v23.verror.VException {
-            Description d = new Description();
-            d.setTitle(mParticipant.getDeck().getTitle());
-            d.setUserName(mParticipant.getUserName());
-            return d;
-        }
-    }
 }
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerFake.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerFake.java
index adba0c1..0d6defd 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerFake.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerFake.java
@@ -4,8 +4,6 @@
 
 package io.v.android.apps.syncslides.discovery;
 
-import android.util.Log;
-
 import java.util.HashSet;
 import java.util.Set;
 
@@ -23,13 +21,15 @@
         if (mCounter >= 2 && mCounter <= 8) {
             participants.add(
                     new ParticipantPeer(
-                            "Alice", new DeckImpl("Kale - Just eat it.", null, "deckId1")));
+                            "Alice", new DeckImpl(
+                            "Kale - Just eat it.", null, "deckByAlice")));
         }
         // Bob has less to say than Alice.
         if (mCounter >= 4 && mCounter <= 6) {
             participants.add(
                     new ParticipantPeer(
-                            "Bob", new DeckImpl("Java - Object deluge.", null, "deckId1")));
+                            "Bob", new DeckImpl(
+                            "Java - Object deluge.", null, "deckByBob")));
         }
         return participants;
     }
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerMt.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerMt.java
index 79b3533..af17b3c 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerMt.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantScannerMt.java
@@ -4,6 +4,8 @@
 
 package io.v.android.apps.syncslides.discovery;
 
+import android.util.Log;
+
 import java.util.HashSet;
 import java.util.Set;
 
@@ -15,27 +17,13 @@
 public class ParticipantScannerMt implements ParticipantScanner {
     private static final String TAG = "ParticipantScannerMt";
 
-    /**
-     * Every v23 service will be mounted in the namespace with a name prefixed
-     * by this.
-     */
-    public static String ROOT_NAME = "users/syncslides";
-
-    /**
-     * Used for V23 communication.
-     */
-    private final V23Manager mV23Manager;
-
-    public ParticipantScannerMt(V23Manager v23Manager) {
-        mV23Manager = v23Manager;
-    }
-
     @Override
     public Set<Participant> scan() {
-        Set<String> endPoints = mV23Manager.scan(ROOT_NAME + "/*");
         Set<Participant> result = new HashSet<>();
-        for (String endPoint : endPoints) {
-            result.add(new ParticipantPeer(endPoint));
+        for (String n : V23Manager.Singleton.get().scan(
+                Participant.Mt.makeScanString())) {
+            Log.d(TAG, "Found: " + n);
+            result.add(new ParticipantPeer(n));
         }
         return result;
     }
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantServerImpl.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantServerImpl.java
new file mode 100644
index 0000000..fbd251a
--- /dev/null
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantServerImpl.java
@@ -0,0 +1,32 @@
+// 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.android.apps.syncslides.discovery;
+
+import android.util.Log;
+
+import io.v.android.apps.syncslides.model.Deck;
+import io.v.v23.context.VContext;
+import io.v.v23.rpc.ServerCall;
+
+/**
+ * Serves data used in deck discovery.
+ */
+public class ParticipantServerImpl implements ParticipantServer {
+    private static final String TAG = "PresentationActivity";
+    private final Deck mDeck;
+
+    public ParticipantServerImpl(Deck d) {
+        mDeck = d;
+    }
+
+    public Description get(VContext ctx, ServerCall call)
+            throws io.v.v23.verror.VException {
+        Log.d(TAG, "Responding to Get RPC.");
+        Description d = new Description();
+        d.setTitle(mDeck.getTitle());
+        d.setUserName(mDeck.getId());
+        return d;
+    }
+}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/PeriodicTasker.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/PeriodicTasker.java
index bf4a933..306c63f 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/PeriodicTasker.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/PeriodicTasker.java
@@ -25,7 +25,7 @@
             Duration.standardSeconds(2);
 
     private static final Duration WAIT_BETWEEN_TASKS =
-            Duration.standardSeconds(5);
+            V23Manager.MT_TIMEOUT.plus(Duration.standardSeconds(3));
 
     private ScheduledExecutorService mTimer = null;
 
@@ -40,6 +40,7 @@
                 DELAY_BEFORE_FIRST_TASK.getMillis(),
                 WAIT_BETWEEN_TASKS.getMillis(),
                 TimeUnit.MILLISECONDS);
+        Log.d(TAG, "Done Starting");
     }
 
     void stop() {
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/V23Manager.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/V23Manager.java
index 0eb4722..1966ab3 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/V23Manager.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/V23Manager.java
@@ -4,8 +4,11 @@
 
 package io.v.android.apps.syncslides.discovery;
 
+import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
 import android.util.Log;
+import android.widget.Toast;
 
 import org.joda.time.Duration;
 
@@ -14,8 +17,14 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
+import io.v.android.libs.security.BlessingsManager;
 import io.v.android.v23.V;
+import io.v.android.v23.services.blessing.BlessingCreationException;
+import io.v.android.v23.services.blessing.BlessingService;
+import io.v.impl.google.naming.NamingUtil;
 import io.v.v23.context.VContext;
 import io.v.v23.namespace.Namespace;
 import io.v.v23.naming.Endpoint;
@@ -25,8 +34,12 @@
 import io.v.v23.rpc.ListenSpec;
 import io.v.v23.rpc.Server;
 import io.v.v23.rpc.ServerState;
+import io.v.v23.security.BlessingPattern;
+import io.v.v23.security.Blessings;
+import io.v.v23.security.VPrincipal;
 import io.v.v23.security.VSecurity;
 import io.v.v23.verror.VException;
+import io.v.v23.vom.VomUtil;
 
 /**
  * Does vanadium stuff - MT scanning, service creation, unmounting, etc.
@@ -37,16 +50,17 @@
  * these in onCreate and onDestroy respectively.
  */
 public class V23Manager {
+    public static final Duration MT_TIMEOUT =
+            Duration.standardSeconds(10);
+    public static final int BLESSING_REQUEST = 201;
     private static final String TAG = "V23Manager";
-
-    private static final Duration MT_TIMEOUT =
-            Duration.standardSeconds(5);
-    // Generates a name to use in the MT.
-    private final NameGenerator mNameGenerator = new NameGeneratorByDate();
+    private static final ExecutorService mExecutor =
+            Executors.newSingleThreadExecutor();
+    private static final String MT_ADDRESS = FixedMt.PI_MILK_CRATE;
+    // private static final String MT_ADDRESS = FixedMt.JR_MOTOX;
     private Context mAndroidCtx;
     private VContext mBaseContext = null;
-    private VContext mMTContext = null;
-    private Namespace mNamespace = null;
+    private Blessings mBlessings = null;
     // Can only have one of these at the moment.  Could add more...
     private Server mLiveServer = null;
 
@@ -54,41 +68,161 @@
     private V23Manager() {
     }
 
+    private static Blessings loadBlessings(Context context) {
+        Log.d(TAG, "loadBlessings from prefs");
+        try {
+            // See if there are blessings stored in shared preferences.
+            return BlessingsManager.getBlessings(context);
+        } catch (VException e) {
+            Log.w(TAG, "Cannot get blessings from prefs: " + e.getMessage());
+        }
+        return null;
+    }
+
     /**
-     * Placeholder for possibly scraping a website for the NS Root.
-     *
+     * To be called from an Activity's onActivityResult method, e.g.
+     *     public void onActivityResult(
+     *         int requestCode, int resultCode, Intent data) {
+     *         if (V23Manager.onActivityResult(
+     *             getApplicationContext(), requestCode, resultCode, data)) {
+     *           return;
+     *         }
+     *     }
+     */
+    public static boolean onActivityResult(
+            Context androidCtx, int requestCode, int resultCode, Intent data) {
+        Log.d(TAG, "onActivityResult");
+        if (requestCode != BLESSING_REQUEST) {
+            return false;
+        }
+        try {
+            Log.d(TAG, "unpacking blessing");
+            Blessings blessings = unpackBlessings(androidCtx, resultCode, data);
+            Singleton.get().configurePrincipal(blessings);
+        } catch (BlessingCreationException e) {
+            throw new IllegalStateException(e);
+        } catch (VException e) {
+            throw new IllegalStateException(e);
+        }
+        return true;
+    }
+
+    public static Blessings unpackBlessings(
+            Context androidCtx, int resultCode, Intent data)
+            throws BlessingCreationException, VException {
+        byte[] blessingsVom = BlessingService.extractBlessingReply(
+                resultCode, data);
+        Blessings blessings = (Blessings) VomUtil.decode(
+                blessingsVom, Blessings.class);
+        BlessingsManager.addBlessings(androidCtx, blessings);
+        Toast.makeText(androidCtx, "Got blessings", Toast.LENGTH_SHORT).show();
+        return blessings;
+    }
+
+    /**
      * @return IP address of the mounttable to scan.
      */
-    private static List<String> determineNamespaceRoot() {
+    public static List<String> determineNamespaceRoot() {
         List<String> result = new ArrayList<>();
-        result.add("/" + FixedMt.PI_MILK_CRATE);
+        result.add("/" + MT_ADDRESS);
         return result;
     }
 
-    public Context getAndroidCtx() {
-        return mAndroidCtx;
+    public static String syncName(String id) {
+        return NamingUtil.join("/", MT_ADDRESS, id);
     }
 
-    public void init(Context androidCtx) {
+    public VContext getVContext() {
+        return mBaseContext;
+    }
+
+    public void init(Context androidCtx, Activity activity) {
+        init(androidCtx, null, activity);
+    }
+
+    public synchronized void init(
+            Context androidCtx, Blessings otherBlessings, Activity activity) {
         Log.d(TAG, "init");
         if (mAndroidCtx != null) {
             if (mAndroidCtx == androidCtx) {
-                Log.d(TAG, "Already initialized.");
+                Log.d(TAG, "Initialization already started.");
                 return;
             } else {
+                Log.d(TAG, "Initialization with new context.");
                 shutdown(Behavior.STRICT);
             }
         }
+        Blessings blessings = otherBlessings;
         mAndroidCtx = androidCtx;
+        // Must call V.init before attempting to load blessings, so that proper
+        // code is loaded.
         mBaseContext = V.init(mAndroidCtx);
-        mMTContext = mBaseContext.withTimeout(MT_TIMEOUT);
-        mNamespace = V.getNamespace(mMTContext);
-        try {
-            mNamespace.setRoots(determineNamespaceRoot());
-        } catch (VException e) {
-            // TODO(jregan): Handle total v23 failure higher up the stack.
-            throw new IllegalStateException(e);
+        if (blessings == null) {
+            blessings = loadBlessings(androidCtx);
         }
+        Namespace ns = V.getNamespace(mBaseContext);
+        try {
+            ns.setRoots(determineNamespaceRoot());
+            Log.d(TAG, "Set namespace root to: " + determineNamespaceRoot());
+        } catch (VException e) {
+            throw new IllegalStateException("Unable to set namespace.");
+        }
+        if (blessings == null) {
+            Log.d(TAG, "No blessings - firing activity " + activity.getTitle());
+            // Bail out and go get them, and re-enter init with them.
+            if (activity == null) {
+                throw new IllegalArgumentException(
+                        "Cannot get blessings without an activity to return to.");
+            }
+            activity.startActivityForResult(
+                    BlessingService.newBlessingIntent(androidCtx),
+                    BLESSING_REQUEST);
+            return;
+        }
+        asyncConfigurePrincipal(blessings);
+    }
+
+    /**
+     * 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;
+    }
+
+    private void configurePrincipal(final Blessings blessings) {
+        Log.d(TAG, "configurePrincipal: blessings=" +
+                (blessings == null ? "null" : blessings.toString()));
+        try {
+            VPrincipal p = V.getPrincipal(mBaseContext);
+            p.blessingStore().setDefaultBlessings(blessings);
+            p.blessingStore().set(blessings, new BlessingPattern("..."));
+            VSecurity.addToRoots(p, blessings);
+            mBlessings = blessings;
+        } catch (VException e) {
+            Log.e(TAG, String.format(
+                    "Couldn't set local blessing %s: %s",
+                    blessings, e.getMessage()));
+        }
+        Log.d(TAG, "blessings stored: " +
+                (mBlessings == null ? "NONE!" : mBlessings.toString()));
+    }
+
+    private void asyncConfigurePrincipal(final Blessings blessings) {
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                configurePrincipal(blessings);
+            }
+        });
     }
 
     public void shutdown(Behavior behavior) {
@@ -105,15 +239,40 @@
         mAndroidCtx = null;
     }
 
+    private void error(String msg) {
+        Log.e(TAG, msg);
+        Toast.makeText(mAndroidCtx, msg, Toast.LENGTH_LONG).show();
+    }
+
     public Set<String> scan(String pattern) {
         FirstGrabber grabber = new FirstGrabber();
         scan(pattern, grabber);
         return grabber.result;
     }
 
+    /**
+     * For every server, take the first endpoint, ignore the rest.
+     */
+    private class FirstGrabber implements Visitor {
+        final HashSet<String> result = new HashSet<>();
+
+        public void visit(MountEntry entry) {
+            Log.d(TAG, "  Entry: \"" + entry.getName() + "\"");
+            result.add(entry.getName());
+            final boolean logEndpoints = true;
+            if (logEndpoints) {
+                for (MountedServer server : entry.getServers()) {
+                    Log.d(TAG, "  endPoint: \"" + server.getServer() + "\"");
+                }
+            }
+        }
+    }
+
     public void scan(String pattern, Visitor visitor) {
         try {
-            for (GlobReply reply : mNamespace.glob(mMTContext, pattern)) {
+            VContext ctx = mBaseContext.withTimeout(MT_TIMEOUT);
+            Namespace ns = V.getNamespace(ctx);
+            for (GlobReply reply : ns.glob(ctx, pattern)) {
                 if (reply instanceof GlobReply.Entry) {
                     visitor.visit(((GlobReply.Entry) reply).getElem());
                 }
@@ -124,45 +283,77 @@
         }
     }
 
-    public String mount(String mountName, Object server) {
-        Log.d(TAG, "mount");
-        try {
-            // ListenSpec spec = V.getListenSpec(mBaseContext).withProxy("proxy");
-            ListenSpec spec = V.getListenSpec(mBaseContext).withAddress(
-                    new ListenSpec.Address("tcp", "localhost:0"));
-            VContext ctx = V.withNewServer(
-                    V.withListenSpec(mBaseContext, spec),
-                    mountName,
-                    server,
-                    VSecurity.newAllowEveryoneAuthorizer());
-            mLiveServer = V.getServer(ctx);
-            Log.d(TAG, "Server status: " + mLiveServer.getStatus().getState());
-            Endpoint[] endpoints = mLiveServer.getStatus().getEndpoints();
-            Log.d(TAG, "Listening on endpoints: " + Arrays.toString(endpoints));
-            if (endpoints.length < 1) {
-                throw new IllegalStateException("No endpoints!");
-            }
-            return endpoints[0].name();
-        } catch (VException e) {
-            // TODO(jregan): Handle total v23 failure higher up the stack.
-            throw new IllegalStateException(e);
+    private VContext getListenContext() throws VException {
+        final boolean useProxy = false;
+        // Disabled while debugging network performance / visibility issues.
+        if (useProxy) {
+            ListenSpec spec = V.getListenSpec(mBaseContext).withProxy("proxy");
+            //ListenSpec spec = V.getListenSpec(mBaseContext).withAddress(
+            //        new ListenSpec.Address("tcp", "0.0.0.0:0"));
+            Log.d(TAG, "spec : " + spec.toString());
+            Log.d(TAG, "spec proxy: " + spec.getProxy().toString());
+            return V.withListenSpec(mBaseContext, spec);
         }
+        return mBaseContext;
+    }
+
+    private Server makeServer(String mountName, Object server) throws VException {
+        return V.getServer(
+                V.withNewServer(
+                        getListenContext(),
+                        mountName,
+                        server,
+                        VSecurity.newAllowEveryoneAuthorizer()));
+    }
+
+    public void mount(final String mountName, final Object server) {
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                Log.d(TAG, "mounting on name \"" + mountName +
+                        "\" at table " + MT_ADDRESS);
+                try {
+                    mLiveServer = makeServer(mountName, server);
+                    Log.d(TAG, "  Server status proxies: " +
+                            Arrays.deepToString(
+                                    mLiveServer.getStatus().getProxies()));
+                    Endpoint[] points = mLiveServer.getStatus().getEndpoints();
+                    for (Endpoint point : points) {
+                        Log.d(TAG, "  Listening on: " + point);
+                    }
+                    if (points.length < 1) {
+                        throw new IllegalStateException("No endpoints!");
+                    }
+                } catch (VException e) {
+                    // TODO(jregan): java gymnastics to propagate exceptions
+                    // to a callback instead of throwing over a cliff.
+                    throw new IllegalStateException(e);
+                }
+                Log.d(TAG, "Done mounting on name \"" + mountName + "\"");
+            }
+        });
     }
 
     public void unMount() {
-        Log.d(TAG, "unMount");
-        if (mLiveServer == null) {
-            throw new IllegalStateException("No v32 service");
-        }
-        if (mLiveServer.getStatus().getState() != ServerState.SERVER_ACTIVE) {
-            throw new IllegalStateException("v32 service not active.");
-        }
-        try {
-            mLiveServer.stop();
-        } catch (VException e) {
-            throw new IllegalStateException(e);
-        }
-        mLiveServer = null;
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                Log.d(TAG, "unMount");
+                if (mLiveServer == null) {
+                    return;
+                }
+                if (mLiveServer.getStatus().getState() != ServerState.SERVER_ACTIVE) {
+                    throw new IllegalStateException("v32 service not active.");
+                }
+                try {
+                    mLiveServer.stop();
+                } catch (VException e) {
+                    throw new IllegalStateException(e);
+                }
+                Log.d(TAG, "unMounted server.");
+                mLiveServer = null;
+            }
+        });
     }
 
     public enum Behavior {PERMISSIVE, STRICT}
@@ -195,19 +386,6 @@
         static final String PI_MILK_CRATE = "192.168.86.254:8101";
         static final String JR_LAPTOP_AT_HOME = "192.168.2.71:23000";
         static final String JR_LAPTOP_VEYRON = "192.168.8.106:23000";
-    }
-
-    /**
-     * For every server, take the first endpoint, ignore the rest.
-     */
-    private class FirstGrabber implements Visitor {
-        final HashSet<String> result = new HashSet<>();
-
-        public void visit(MountEntry entry) {
-            for (MountedServer server : entry.getServers()) {
-                result.add(server.getServer());
-                return;
-            }
-        }
+        static final String JR_MOTOX = "192.168.43.136:23000";
     }
 }
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/participant_service.vdl b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/participant_service.vdl
index c8f2fb1..7965344 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/participant_service.vdl
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/participant_service.vdl
@@ -4,6 +4,10 @@
 
 package discovery
 
+import (
+    "io/v/android/apps/syncslides/db"
+)
+
 type Description struct {
 	UserName string
 	Title string
@@ -11,4 +15,13 @@
 
 type Participant interface {
 	Get() (Description | error)
-}
\ No newline at end of file
+}
+
+// TODO(jregan):  Ditch Description in favor of db.VDeck.
+// so we get thumb automatically.  Rewire the remaining
+// code.  Add whatever else we need.
+// This is just a test of the code generator, and should
+// be removed shortly.
+type ChuckleParticipant interface {
+	Get() (db.VDeck | error)
+}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java
index 3570612..b90e061 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java
@@ -39,6 +39,20 @@
               ", thumb=" + (mThumb == null ? "no" : "yes") + "]";
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof DeckImpl)) {
+            return false;
+        }
+        DeckImpl p = (DeckImpl) obj;
+        return mDeckId.equals(p.mDeckId);
+    }
+
+    @Override
+    public int hashCode() {
+        return mDeckId.hashCode();
+    }
+
     public static Deck fromBundle(Bundle b) {
         if (b == null) {
             throw new IllegalArgumentException("Need a bundle.");
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java
index f84433c..7266789 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java
@@ -20,7 +20,26 @@
      * the UX. MT location determined in
      * {@link io.v.android.apps.syncslides.discovery.V23Manager}.
      */
-    boolean ENABLE_MT_DISCOVERY = false;
+    boolean ENABLE_MT_DISCOVERY = true;
+
+    public static class Mt {
+        /**
+         * Every v23 service will be mounted in the namespace with a name
+         * prefixed by this.
+         */
+        public static String ROOT_NAME = "liveDeck";
+
+        /**
+         * TODO(jregan): Assure legal mount name (remove blanks and such).
+         */
+        public static String makeMountName(Deck deck) {
+            return ROOT_NAME + "/" + deck.getId();
+        }
+
+        public static String makeScanString() {
+            return ROOT_NAME + "/*";
+        }
+    }
 
     // Name of the user participating, intended to be visible to others. This
     // can be a colloquial name as opposed to a 'real' name or email address
@@ -31,14 +50,11 @@
     Deck getDeck();
 
     // Name of a service with participant information.
-    String getEndPoint();
+    String getServiceName();
 
     // Initially get or refresh data from the endPoint.
     void refreshData();
 
-    // Serializable form of this for storing, passing via Message, etc.
-    Bundle toBundle();
-
     // For debugging.
     String toString();
 
@@ -53,8 +69,10 @@
      */
     class B {
         public static final String PARTICIPANT_ROLE = "participant_role";
-        public static final String PARTICIPANT_END_POINT = "participant_endPoint";
+        public static final String PARTICIPANT_SHOULD_ADV = "participant_is_advertising";
+        public static final String PARTICIPANT_SERVICE_NAME = "participant_endPoint";
         public static final String PARTICIPANT_NAME = "participant_name";
+        public static final String PARTICIPANT_BLESSINGS = "participant_blessings";
         public static final String PARTICIPANT_SYNCED = "participant_synced";
     }
 }