Got advertising to work.

Change-Id: I5a7e7997fbdcdae267c6ef768654f241f374c61d
diff --git a/android-lib/src/main/java/io/v/android/libs/discovery/ble/BlePlugin.java b/android-lib/src/main/java/io/v/android/libs/discovery/ble/BlePlugin.java
index 67ad58a..88a2fbf 100644
--- a/android-lib/src/main/java/io/v/android/libs/discovery/ble/BlePlugin.java
+++ b/android-lib/src/main/java/io/v/android/libs/discovery/ble/BlePlugin.java
@@ -4,9 +4,9 @@
 
 package io.v.android.libs.discovery.ble;
 
-import android.app.Activity;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
 import android.bluetooth.BluetoothGattCharacteristic;
 import android.bluetooth.BluetoothGattServer;
 import android.bluetooth.BluetoothGattServerCallback;
@@ -30,6 +30,7 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -46,14 +47,21 @@
 import io.v.impl.google.lib.discovery.ScanHandler;
 import io.v.x.ref.lib.discovery.Advertisement;
 
+/**
+ * BlePlugin implements the Discovery Plugin Interface for Bluetooth.
+ */
 public class BlePlugin {
+    // We are using a constant for the MTU because Android and paypal/gatt don't get along
+    // when the paypal gatt client sends a setMTU message.  The Android server seems to send
+    // a malformed L2CAP message.
+    private static final int MTU = 23;
+
     // Object used to lock advertisement objects.
     private final Object advertisementLock;
     // The id to assign to the next advertisment.
     private int nextAdv;
     // A map of advertisement ids to the advertisement that corresponds to them.
     private Map<Integer, BluetoothGattService> advertisements;
-
     // A map of advertisement ids to the thread waiting for cancellation of the context.
     private Map<Integer, Thread> advCancellationThreads;
 
@@ -62,20 +70,26 @@
     // A map of scanner ids to the thread waiting for cancellation of the context.
     private Map<Integer, Thread> scanCancellationThreads;
     private DeviceCache cachedDevices;
+    // Used to track the set of devices we currently talking to.
+    private Set<String> pendingCalls;
 
+    // Set of Ble objects that will be interacted with to perform operations.
     private BluetoothLeAdvertiser bluetoothLeAdvertise;
     private BluetoothLeScanner bluetoothLeScanner;
     private BluetoothGattServer bluetoothGattServer;
-    private ScanCallback callback;
 
-    private Set<String> pendingCalls;
-
+    // We need to hold onto the callbacks for scan an advertise because that is what is used
+    // to stop the operation.
+    private ScanCallback scanCallback;
+    private AdvertiseCallback advertiseCallback;
 
     private boolean isScanning;
 
     private Context androidContext;
 
 
+    // A thread to wait for the cancellation of a particular advertisement.  VContext.done().await()
+    // is blocking so have to spin up a thread per outstanding advertisement.
     private class AdvertisementCancellationRunner implements Runnable{
         private VContext mCtx;
 
@@ -96,6 +110,7 @@
         }
     }
 
+    // Similar to AdvertisementCancellationRunner except for scanning.
     private class ScannerCancellationRunner implements Runnable{
         private VContext mCtx;
 
@@ -138,10 +153,29 @@
             public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
                 super.onConnectionStateChange(device, status, newState);
             }
+
+            @Override
+            public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+                super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
+                byte[] total =characteristic.getValue();
+                byte[] res = {};
+                // Only send MTU - 1 bytes. The first byte of all packets is the op code..
+                if (offset < total.length) {
+                    int finalByte = offset + MTU - 1;
+                    if (finalByte > total.length) {
+                        finalByte = total.length;
+                    }
+                    res = Arrays.copyOfRange(total, offset, finalByte);
+                    bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, res);
+                } else {
+                    bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0,  res);
+                }
+            }
         });
     }
 
 
+    // Converts a Vanadium Advertisement to a Bluetooth gatt service.
     private BluetoothGattService convertToService(Advertisement adv) throws IOException {
         Map<UUID, byte[]> attributes = BleAdvertisementConverter.vadvertismentToBleAttr(adv);
         BluetoothGattService service = new BluetoothGattService(
@@ -164,12 +198,16 @@
             t.start();
             advCancellationThreads.put(currentId, t);
             bluetoothGattServer.addService(service);
-
+            readvertise();
         }
     }
 
     private void removeAdvertisement(int id) {
         synchronized (advertisements) {
+            BluetoothGattService s = advertisements.get(id);
+            if (s != null) {
+                bluetoothGattServer.removeService(s);
+            }
             advertisements.remove(id);
             advCancellationThreads.remove(id);
             readvertise();
@@ -198,7 +236,7 @@
     private void updateScanning() {
         if (isScanning && scanCancellationThreads.size() == 0) {
             isScanning = false;
-            bluetoothLeScanner.stopScan(callback);
+            bluetoothLeScanner.stopScan(scanCallback);
             return;
         }
 
@@ -213,16 +251,13 @@
             scanFilter.add(builder.build());
 
 
-            callback = new ScanCallback() {
+            scanCallback = new ScanCallback() {
                 @Override
                 public void onScanResult(int callbackType, ScanResult result) {
                     // in L the only value for callbackType is CALLBACK_TYPE_ALL_MATCHES, so
                     // we don't look at it's value.
-                    Log.d("vanadium", "Got a callback");
                     ScanRecord record = result.getScanRecord();
                     byte[] data = record.getManufacturerSpecificData(1001);
-                    Log.d("vanadium", "result is " + result.getDevice().getName());
-                    Log.d("vanadium", "data is " + data);
                     ByteBuffer buffer = ByteBuffer.wrap(data);
                     final long hash = buffer.getLong();
                     final String deviceId = result.getDevice().getAddress();
@@ -255,11 +290,11 @@
                                 pendingCalls.remove(deviceId);
                             }
                             bluetoothLeScanner.startScan(scanFilter, new ScanSettings.Builder().
-                                    setScanMode(ScanSettings.SCAN_MODE_BALANCED).build(), callback);
+                                    setScanMode(ScanSettings.SCAN_MODE_BALANCED).build(), scanCallback);
                         }
                     };
                     BluetoothGattClientCallback cb = new BluetoothGattClientCallback(ccb);
-                    bluetoothLeScanner.stopScan(callback);
+                    bluetoothLeScanner.stopScan(scanCallback);
                     Log.d("vanadium", "connecting to " + result.getDevice());
                     result.getDevice().connectGatt(androidContext, false, cb);
                 }
@@ -273,11 +308,19 @@
                 }
             };
             bluetoothLeScanner.startScan(scanFilter, new ScanSettings.Builder().
-                    setScanMode(ScanSettings.SCAN_MODE_BALANCED).build(), callback);
+                    setScanMode(ScanSettings.SCAN_MODE_BALANCED).build(), scanCallback);
         }
     }
 
     private void readvertise() {
+        if (advertiseCallback != null) {
+            bluetoothLeAdvertise.stopAdvertising(advertiseCallback);
+            advertiseCallback = null;
+        }
+        if (advertisements.size() == 0) {
+            return;
+        }
+
         int hash = advertisements.hashCode();
 
         AdvertiseData.Builder builder = new AdvertiseData.Builder();
@@ -286,10 +329,9 @@
         buf.putLong(hash);
         builder.addManufacturerData(1001, buf.array());
         AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
-        settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED);
+        settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY);
         settingsBuilder.setConnectable(true);
-        bluetoothLeAdvertise.startAdvertising(settingsBuilder.build(), builder.build(),
-                new AdvertiseCallback() {
+        advertiseCallback = new AdvertiseCallback() {
                     @Override
                     public void onStartSuccess(AdvertiseSettings settingsInEffect) {
                         Log.i("vanadium", "Successfully started " + settingsInEffect);
@@ -299,6 +341,8 @@
                     public void onStartFailure(int errorCode) {
                         Log.i("vanadium", "Failed to start advertising " + errorCode);
                     }
-                });
+                };
+        bluetoothLeAdvertise.startAdvertising(settingsBuilder.build(), builder.build(),
+                advertiseCallback);
     }
 }
\ No newline at end of file
diff --git a/android-lib/src/main/java/io/v/android/libs/discovery/ble/BluetoothGattClientCallback.java b/android-lib/src/main/java/io/v/android/libs/discovery/ble/BluetoothGattClientCallback.java
index 1e848e1..067ba8d 100644
--- a/android-lib/src/main/java/io/v/android/libs/discovery/ble/BluetoothGattClientCallback.java
+++ b/android-lib/src/main/java/io/v/android/libs/discovery/ble/BluetoothGattClientCallback.java
@@ -4,7 +4,6 @@
 
 package io.v.android.libs.discovery.ble;
 
-import android.app.Activity;
 import android.bluetooth.BluetoothGatt;
 import android.bluetooth.BluetoothGattCallback;
 import android.bluetooth.BluetoothGattCharacteristic;
@@ -19,7 +18,7 @@
 import java.util.UUID;
 
 /**
- * Created by bjornick on 10/6/15.
+ * BluetoothGattClientCallback is a handler for responses from a GattServer.
  */
 public class BluetoothGattClientCallback extends BluetoothGattCallback {
     public interface Callback {
@@ -43,11 +42,17 @@
     @Override
     public void onServicesDiscovered(BluetoothGatt gatt, int status) {
         for (BluetoothGattService service : gatt.getServices()) {
+            // Skip the GATT AND GAP Services.
             if (service.getUuid().toString().startsWith("0000180")) {
                 continue;
             }
             services.put(service.getUuid(), new HashMap<UUID, byte[]>());
-            chars.addAll(service.getCharacteristics());
+            // We only keep track of the characteristics that can be read.
+            for (BluetoothGattCharacteristic ch : chars) {
+                if ((ch.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) != 0) {
+                    chars.add(ch);
+                }
+            }
         }
         pos = 0;
         maybeReadNextCharacteristic();
@@ -63,13 +68,10 @@
             return;
         }
         BluetoothGattCharacteristic c = chars.get(pos++);
-
-        if ((c.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) {
+        if (!gatt.readCharacteristic(c)) {
+            Log.d("vanadium", "Failed to read characteristic " + c.getUuid());
             maybeReadNextCharacteristic();
-            return;
         }
-
-        Log.d("vanadium", "trying to read " + c + " " + gatt.readCharacteristic(c));
     }
 
     @Override
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/DeviceCache.java b/lib/src/main/java/io/v/impl/google/lib/discovery/DeviceCache.java
index 97c250b..480de84 100644
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/DeviceCache.java
+++ b/lib/src/main/java/io/v/impl/google/lib/discovery/DeviceCache.java
@@ -18,59 +18,56 @@
 
 import io.v.x.ref.lib.discovery.Advertisement;
 
-/**
- * Created by bjornick on 10/9/15.
- */
 public class DeviceCache {
-    private Map<Long, CacheEntry> mCachedDevices;
-    private Map<String, CacheEntry> mKnownIds;
+    private Map<Long, CacheEntry> cachedDevices;
+    private Map<String, CacheEntry> knownIds;
 
     private class CacheEntry {
-        Set<Advertisement> mAdvertisements;
+        Set<Advertisement> advertisements;
 
-        long mHash;
+        long hash;
 
-        Date mLastSeen;
+        Date lastSeen;
 
-        String mDeviceId;
+        String deviceId;
 
         CacheEntry(Set<Advertisement> advs, long hash, String deviceId) {
-            mAdvertisements = advs;
-            mHash = hash;
-            mLastSeen = new Date();
-            mDeviceId = deviceId;
+            advertisements = advs;
+            this.hash = hash;
+            lastSeen = new Date();
+            this.deviceId = deviceId;
         }
     }
 
-    int mNextScanner;
-    private Map<UUID, Set<Advertisement>> mKnownServices;
-    private Map<Integer, VScanner> mScannersById;
-    private Map<UUID, Set<VScanner>> mScannersByUUID;
+    int nextScanner;
+    private Map<UUID, Set<Advertisement>> knownServices;
+    private Map<Integer, VScanner> scannersById;
+    private Map<UUID, Set<VScanner>> scannersByUUID;
 
-    private Duration mMaxAge;
+    private Duration maxAge;
 
 
     public DeviceCache(Duration maxAge) {
-        mCachedDevices = new HashMap<>();
-        mKnownIds = new HashMap<>();
-        mKnownServices = new HashMap<>();
-        mScannersById = new HashMap<>();
-        mScannersByUUID = new HashMap<>();
-        mMaxAge = maxAge;
-        mNextScanner = 0;
+        cachedDevices = new HashMap<>();
+        knownIds = new HashMap<>();
+        knownServices = new HashMap<>();
+        scannersById = new HashMap<>();
+        scannersByUUID = new HashMap<>();
+        this.maxAge = maxAge;
+        nextScanner = 0;
     }
 
     public boolean haveSeenHash(long hash, String deviceId) {
         synchronized (this) {
-            CacheEntry entry = mCachedDevices.get(hash);
+            CacheEntry entry = cachedDevices.get(hash);
             if (entry != null) {
-                entry.mLastSeen = new Date();
-                if (!entry.mDeviceId.equals(deviceId)) {
+                entry.lastSeen = new Date();
+                if (!entry.deviceId.equals(deviceId)) {
                     // This probably happened becuase a device has changed it's ble mac address.
                     // We need to update the mac address for this entry.
-                    mKnownIds.remove(entry.mDeviceId);
-                    entry.mDeviceId = deviceId;
-                    mKnownIds.put(deviceId, entry);
+                    knownIds.remove(entry.deviceId);
+                    entry.deviceId = deviceId;
+                    knownIds.put(deviceId, entry);
                 }
             }
             return entry != null;
@@ -80,19 +77,19 @@
     public void saveDevice(long hash, Set<Advertisement> advs, String deviceId) {
         CacheEntry entry = new CacheEntry(advs, hash, deviceId);
         synchronized (this) {
-            CacheEntry oldEntry = mKnownIds.get(deviceId);
+            CacheEntry oldEntry = knownIds.get(deviceId);
             Set<Advertisement> oldValues = null;
             if (oldEntry != null) {
-                mCachedDevices.remove(oldEntry.mHash);
-                mKnownIds.remove(oldEntry.mDeviceId);
-                oldValues = oldEntry.mAdvertisements;
+                cachedDevices.remove(oldEntry.hash);
+                knownIds.remove(oldEntry.deviceId);
+                oldValues = oldEntry.advertisements;
             } else {
                 oldValues = new HashSet<>();
             }
             Sets.SetView<Advertisement> removed = Sets.difference(oldValues, advs);
             for (Advertisement adv : removed) {
                 UUID uuid = UUIDUtil.UuidtUUID(adv.getServiceUuid());
-                Set<Advertisement> set = mKnownServices.get(uuid);
+                Set<Advertisement> set = knownServices.get(uuid);
 
                 if (set != null) {
                     set.remove(adv);
@@ -100,7 +97,7 @@
                     handleUpdate(adv);
 
                     if (set.size() == 0) {
-                        mKnownServices.remove(uuid);
+                        knownServices.remove(uuid);
                     }
                 }
             }
@@ -108,27 +105,27 @@
             Sets.SetView<Advertisement> added = Sets.difference(advs, oldValues);
             for (Advertisement adv: added) {
                 UUID uuid = UUIDUtil.UuidtUUID(adv.getServiceUuid());
-                Set<Advertisement> set = mKnownServices.get(uuid);
+                Set<Advertisement> set = knownServices.get(uuid);
                 if (set == null) {
                     set = new HashSet<>();
-                    mKnownServices.put(uuid, set);
+                    knownServices.put(uuid, set);
                 }
                 set.add(adv);
                 handleUpdate(adv);
             }
-            mCachedDevices.put(hash, entry);
-            CacheEntry oldDeviceEntry = mKnownIds.get(deviceId);
+            cachedDevices.put(hash, entry);
+            CacheEntry oldDeviceEntry = knownIds.get(deviceId);
             if (oldDeviceEntry != null) {
                 // Delete the old hash value.
-                mCachedDevices.remove(hash);
+                cachedDevices.remove(hash);
             }
-            mKnownIds.put(deviceId, entry);
+            knownIds.put(deviceId, entry);
         }
     }
 
     private void handleUpdate(Advertisement adv) {
         UUID uuid = UUIDUtil.UuidtUUID(adv.getServiceUuid());
-        Set<VScanner> scanners = mScannersByUUID.get(uuid);
+        Set<VScanner> scanners = scannersByUUID.get(uuid);
         if (scanners == null) {
             return;
         }
@@ -139,14 +136,14 @@
 
     public int addScanner(VScanner scanner) {
         synchronized (this) {
-            int id = mNextScanner++;
-            mScannersById.put(id, scanner);
-            Set<VScanner> scanners = mScannersByUUID.get(scanner.getmServiceUUID());
+            int id = nextScanner++;
+            scannersById.put(id, scanner);
+            Set<VScanner> scanners = scannersByUUID.get(scanner.getmServiceUUID());
             if (scanners == null) {
                 scanners = new HashSet<>();
-                mScannersByUUID.put(scanner.getmServiceUUID(), scanners);
+                scannersByUUID.put(scanner.getmServiceUUID(), scanners);
             }
-            Set<Advertisement> knownAdvs = mKnownServices.get(scanner.getmServiceUUID());
+            Set<Advertisement> knownAdvs = knownServices.get(scanner.getmServiceUUID());
             if (knownAdvs != null) {
                 for (Advertisement adv : knownAdvs) {
                     scanner.getHandler().handleUpdate(adv);
@@ -159,16 +156,16 @@
 
     public void removeScanner(int id) {
         synchronized (this) {
-            VScanner scanner = mScannersById.get(id);
+            VScanner scanner = scannersById.get(id);
             if (scanner != null) {
-                Set<VScanner> list = mScannersByUUID.get(scanner.getmServiceUUID());
+                Set<VScanner> list = scannersByUUID.get(scanner.getmServiceUUID());
                 if (list != null) {
                     list.remove(scanner);
                     if (list.size() == 0) {
-                        mScannersByUUID.remove(scanner.getmServiceUUID());
+                        scannersByUUID.remove(scanner.getmServiceUUID());
                     }
                 }
-                mScannersById.remove(id);
+                scannersById.remove(id);
             }
         }
     }
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/EncodingUtil.java b/lib/src/main/java/io/v/impl/google/lib/discovery/EncodingUtil.java
index 2972de5..76c8775 100644
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/EncodingUtil.java
+++ b/lib/src/main/java/io/v/impl/google/lib/discovery/EncodingUtil.java
@@ -18,9 +18,6 @@
 
 import io.v.x.ref.lib.discovery.EncryptionKey;
 
-/**
- * Created by bjornick on 10/8/15.
- */
 public class EncodingUtil {
     static final Charset utf8 = Charset.forName("UTF-8");
     private static void writeUint(OutputStream out, int value) throws IOException {
@@ -102,19 +99,19 @@
     }
 
     static public class KeysAndAlgorithm {
-        int mEncryptionAlgorithm;
-        List<EncryptionKey> mKeys;
+        int encryptionAlgorithm;
+        List<EncryptionKey> keys;
         public int getEncryptionAlgorithm() {
-            return mEncryptionAlgorithm;
+            return encryptionAlgorithm;
         }
 
         public List<EncryptionKey> getKeys() {
-            return mKeys;
+            return keys;
         }
 
         KeysAndAlgorithm(int encryptionAlgo, List<EncryptionKey> keys) {
-            mEncryptionAlgorithm = encryptionAlgo;
-            mKeys = keys;
+            encryptionAlgorithm = encryptionAlgo;
+            this.keys = keys;
         }
     }
 
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/UUIDUtil.java b/lib/src/main/java/io/v/impl/google/lib/discovery/UUIDUtil.java
index 8b37b3a..f754241 100644
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/UUIDUtil.java
+++ b/lib/src/main/java/io/v/impl/google/lib/discovery/UUIDUtil.java
@@ -12,13 +12,19 @@
 import io.v.x.ref.lib.discovery.Uuid;
 
 /**
- * Created by bjornick on 10/7/15.
+ * UUIDUtil is a class for generating v5 UUIDs and converting from java.util.UUID and
+ * io.v.impl.google.lib.discovery.Uuid.
  */
 public class UUIDUtil {
     public static native UUID UUIDForInterfaceName(String name);
 
     public static native UUID UUIDForAttributeKey(String key);
 
+    /**
+     * Converts from java.util.UUID to io.v.impl.google.lib.discovery.Uuid.
+     * @param id The java.util.UUID
+     * @return The io.v.impl.google.lib.discovery.Uuid version of id
+     */
     public static Uuid UUIDtUuid(UUID id) {
         ByteBuffer b = ByteBuffer.allocate(16);
         b.putLong(id.getMostSignificantBits());
@@ -26,6 +32,11 @@
         return new Uuid(Bytes.asList(b.array()));
     }
 
+    /**
+     * Converts from io.v.impl.google.lib.discovery.Uuid to java.util.UUID
+     * @param id The io.v.impl.google.lib.discovery.Uuid
+     * @return The java.util.UUID version of id
+     */
     public static UUID UuidtUUID(Uuid id) {
         ByteBuffer b = ByteBuffer.wrap(Bytes.toArray(id));
         return new UUID(b.getLong(), b.getLong());
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/VScanner.java b/lib/src/main/java/io/v/impl/google/lib/discovery/VScanner.java
index 3664c8f..2172f61 100644
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/VScanner.java
+++ b/lib/src/main/java/io/v/impl/google/lib/discovery/VScanner.java
@@ -8,6 +8,9 @@
 
 import io.v.impl.google.lib.discovery.ScanHandler;
 
+/**
+ * VScanenr wraps a ServiceUUID and a ScanHandler.
+ */
 public class VScanner {
     private UUID serviceUUID;
 
diff --git a/lib/src/main/java/io/v/impl/google/lib/discovery/ble/BleAdvertisementConverter.java b/lib/src/main/java/io/v/impl/google/lib/discovery/ble/BleAdvertisementConverter.java
index 8cbbf5e..a94339d 100644
--- a/lib/src/main/java/io/v/impl/google/lib/discovery/ble/BleAdvertisementConverter.java
+++ b/lib/src/main/java/io/v/impl/google/lib/discovery/ble/BleAdvertisementConverter.java
@@ -22,10 +22,18 @@
 import io.v.x.ref.lib.discovery.plugins.ble.Constants;
 
 /**
- * Created by bjornick on 10/7/15.
+ * BleAdvertisementConverter converts from io.v.impl.google.lib.discovery.Advertisement to
+ * the UUID gatt Services and vice-versa.
  */
 public class BleAdvertisementConverter {
     private static Charset utf8 = Charset.forName("UTF-8");
+
+    /**
+     * Converts from io.v.impl.google.lib.discovery.Advertisement to the ble representation.
+     * @param adv The Vanadium Advertisement
+     * @return Map of Characteristic UUIDs to their values.
+     * @throws IOException
+     */
     public static Map<UUID, byte[]> vadvertismentToBleAttr(Advertisement adv)
             throws IOException {
         Map<UUID, byte[]> attr = new HashMap<>();
@@ -56,6 +64,13 @@
         return attr;
     }
 
+    /**
+     * Converts from Map of Characteristic UUIDs to their values to a
+     * io.v.impl.google.lib.discovery.Advertisement.
+     * @param attr The map of characteristic uuids to their values
+     * @return The Vanadium Advertisement based on characteristics.
+     * @throws IOException
+     */
     public static Advertisement bleAttrToVAdvertisement(Map<UUID, byte[]> attr)
             throws IOException {
         Map<String, String> cleanAttrs = new HashMap<String, String>();
diff --git a/projects/discovery_sample/app/src/main/java/io/v/discoverysample/MainActivity.java b/projects/discovery_sample/app/src/main/java/io/v/discoverysample/MainActivity.java
index bf582e5..9e2546c 100644
--- a/projects/discovery_sample/app/src/main/java/io/v/discoverysample/MainActivity.java
+++ b/projects/discovery_sample/app/src/main/java/io/v/discoverysample/MainActivity.java
@@ -10,6 +10,8 @@
 import android.widget.ListView;
 import android.widget.Toast;
 
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 import io.v.android.libs.discovery.ble.BlePlugin;
@@ -17,34 +19,44 @@
 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.lib.discovery.ScanHandler;
 import io.v.impl.google.lib.discovery.UUIDUtil;
 import io.v.v23.context.CancelableVContext;
 import io.v.v23.context.VContext;
+import io.v.v23.discovery.Attributes;
+import io.v.v23.discovery.Service;
 import io.v.v23.security.Blessings;
 import io.v.v23.verror.VException;
 import io.v.v23.vom.VomUtil;
+import io.v.x.ref.lib.discovery.Advertisement;
+import io.v.x.ref.lib.discovery.EncryptionAlgorithm;
 
 
 public class MainActivity extends Activity {
 
     private static final int BLESSINGS_REQUEST = 1;
     private Button chooseBlessingsButton;
-    private Button startScanButton;
+    private Button scanButton;
+    private Button advertiseButton;
     private Blessings blessings;
     private BlePlugin plugin;
 
     private VContext rootCtx;
     private CancelableVContext scanCtx;
+    private CancelableVContext advCtx;
+
     private boolean isScanning;
+    private boolean isAdvertising;
 
     private ScanHandlerAdapter adapter;
 
+    private Advertisement advertisement;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         rootCtx = V.init(this);
         isScanning = false;
+        isAdvertising = false;
 
         setContentView(R.layout.activity_main);
         chooseBlessingsButton = (Button)findViewById(R.id.blessingsButton);
@@ -55,15 +67,37 @@
             }
         });
 
-        startScanButton = (Button)findViewById(R.id.scanForService);
-        startScanButton.setEnabled(false);
-        startScanButton.setOnClickListener(new View.OnClickListener() {
+        scanButton = (Button)findViewById(R.id.scanForService);
+        scanButton.setEnabled(false);
+        scanButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
                 flipScan();
             }
         });
 
+        advertiseButton = (Button) findViewById(R.id.advertiseButton);
+        advertiseButton.setEnabled(false);
+        advertiseButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                flipAdvertise();
+            }
+        });
+
+        byte[] instanceId = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15};
+        Attributes attrs = new Attributes();
+        attrs.put("foo", "bar");
+        List<String> addrs = new ArrayList<>();
+        addrs.add("localhost:2000");
+        String interfaceName = "v.io/x/ref.Interface";
+        advertisement = new Advertisement(
+                new Service(instanceId, "Android instance",
+                        interfaceName, attrs, addrs),
+                UUIDUtil.UUIDtUuid(UUIDUtil.UUIDForInterfaceName(interfaceName)),
+                new EncryptionAlgorithm(0),
+                null,
+                false);
         adapter = new ScanHandlerAdapter(this);
         ListView devices = (ListView) findViewById(R.id.list_view);
         devices.setAdapter(adapter);
@@ -76,6 +110,28 @@
         return true;
     }
 
+    private void flipAdvertise() {
+        if (plugin == null) {
+            plugin = new BlePlugin(this);
+        }
+
+        if (!isAdvertising) {
+            isAdvertising = true;
+
+            advCtx = rootCtx.withCancel();
+            try {
+                plugin.addAdvertisement(advCtx, advertisement);
+                advertiseButton.setText("Stop Advertisement");
+            } catch (IOException e) {
+                advCtx.cancel();
+                isAdvertising = false;
+            }
+        } else {
+            isAdvertising = false;
+            advCtx.cancel();
+            advertiseButton.setText("Advertise");
+        }
+    }
     private void flipScan() {
         if (plugin == null) {
             plugin = new BlePlugin(this);
@@ -86,9 +142,11 @@
                     UUIDUtil.UUIDForInterfaceName("v.io/x/ref.Interface"),
                     adapter);
             isScanning = true;
+            scanButton.setText("Stop scanning");
         } else {
             isScanning = false;
             scanCtx.cancel();
+            scanButton.setText("Start Scan");
         }
     }
 
@@ -112,7 +170,8 @@
                             Toast.LENGTH_SHORT).show();
 
                     // Enable the "start service" button.
-                    startScanButton.setEnabled(true);
+                    scanButton.setEnabled(true);
+                    advertiseButton.setEnabled(true);
                 } catch (BlessingCreationException e) {
                     String msg = "Couldn't create blessing: " + e.getMessage();
                     Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
diff --git a/projects/discovery_sample/app/src/main/res/layout/activity_main.xml b/projects/discovery_sample/app/src/main/res/layout/activity_main.xml
index 8ec4d9a..67646aa 100644
--- a/projects/discovery_sample/app/src/main/res/layout/activity_main.xml
+++ b/projects/discovery_sample/app/src/main/res/layout/activity_main.xml
@@ -13,11 +13,18 @@
         android:layout_alignParentTop="true"/>
     <Button
         android:id="@+id/scanForService"
-        android:text="Scan for Service"
+        android:text="Start Scan"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
         android:layout_toRightOf="@id/blessingsButton"/>
+    <Button
+        android:id="@+id/advertiseButton"
+        android:text="Advertise"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_toRightOf="@id/scanForService"/>
     <ListView
         android:id="@+id/list_view"
         android:layout_width="fill_parent"