mojo/discovery: Implement an android mojo service for discovery.

Change-Id: Idd0d9706cf9363cc4eee8ed9b742ec039684426c
diff --git a/Makefile b/Makefile
index a69fb64..5a256ea 100644
--- a/Makefile
+++ b/Makefile
@@ -35,12 +35,47 @@
 test: discovery-test
 
 .PHONY: gen-mojom
-gen-mojom: go/src/mojom/vanadium/discovery/discovery.mojom.go lib/gen/dart-gen/mojom/lib/mojo/discovery.mojom.dart
+gen-mojom: go/src/mojom/vanadium/discovery/discovery.mojom.go lib/gen/dart-gen/mojom/lib/mojo/discovery.mojom.dart java/generated-src/io/v/mojo/discovery/Advertiser.java
+
+java/generated-src/io/v/mojo/discovery/Advertiser.java: java/generated-src/mojom/vanadium/discovery.mojom.srcjar
+	cd java/generated-src/ && jar -xf mojom/vanadium/discovery.mojom.srcjar
+
+java/generated-src/mojom/vanadium/discovery.mojom.srcjar: mojom/vanadium/discovery.mojom | mojo-env-check
+	$(call MOJOM_GEN,$<,.,java/generated-src,java)
 
 go/src/mojom/vanadium/discovery/discovery.mojom.go: mojom/vanadium/discovery.mojom | mojo-env-check
 	$(call MOJOM_GEN,$<,.,.,go)
 	gofmt -w $@
 
+
+ifdef ANDROID
+gradle-build:
+	cd java && ./gradlew build
+
+java/build/outputs/apk/java-debug.apk: gradle-build
+
+build/classes.dex: java/build/outputs/apk/java-debug.apk | mojo-env-check
+	mkdir -p build
+	cd build && jar -xf ../$<
+
+$(DISCOVERY_BUILD_DIR)/discovery.mojo: build/classes.dex java/Manifest.txt | mojo-env-check
+	rm -fr build/zip-scratch build/discovery.zip
+	mkdir -p build/zip-scratch/META-INF
+	cp build/classes.dex build/zip-scratch
+	cp java/Manifest.txt build/zip-scratch/META-INF/MANIFEST.MF
+	cp -r build/lib/ build/zip-scratch/
+	cp build/lib/armeabi-v7a/libv23.so build/zip-scratch
+	cd build/zip-scratch && zip -r ../discovery.zip .
+	mkdir -p `dirname $@`
+	echo "#!mojo mojo:java_handler" > $@
+	cat build/discovery.zip >> $@
+else
+
+$(DISCOVERY_BUILD_DIR)/discovery.mojo: $(V23_GO_FILES) $(MOJO_SHARED_LIB) | mojo-env-check
+	$(call MOGO_BUILD,vanadium/discovery,$@)
+endif
+
+
 lib/gen/dart-gen/mojom/lib/mojo/discovery.mojom.dart: mojom/vanadium/discovery.mojom | mojo-env-check
 	$(call MOJOM_GEN,$<,.,lib/gen,dart)
 	# TODO(nlacasse): mojom_bindings_generator creates bad symlinks on dart
@@ -48,9 +83,6 @@
 	# See https://github.com/domokit/mojo/issues/386
 	rm -f lib/gen/mojom/$(notdir $@)
 
-$(DISCOVERY_BUILD_DIR)/discovery.mojo: $(V23_GO_FILES) | mojo-env-check
-	$(call MOGO_BUILD,vanadium/discovery,$@)
-
 discovery-test: $(V23_GO_FILES) go/src/mojom/vanadium/discovery/discovery.mojom.go | mojo-env-check
 	$(call CGO_TEST,vanadium/discovery/internal)
 
diff --git a/go/src/examples/advertiser/advertiser.go b/go/src/examples/advertiser/advertiser.go
index f428fae..7226b6e 100644
--- a/go/src/examples/advertiser/advertiser.go
+++ b/go/src/examples/advertiser/advertiser.go
@@ -29,9 +29,9 @@
 	s := discovery.Service{
 		InterfaceName: "v.io/discovery.T",
 		Addrs:         []string{"localhost:1000", "localhost:2000"},
-		Attrs:         map[string]string{"foo": "bar"},
+		Attrs:         &map[string]string{"foo": "bar"},
 	}
-	id, e1, e2 := a.proxy.Advertise(s, nil)
+	id, _, e1, e2 := a.proxy.Advertise(s, nil)
 	if e1 != nil || e2 != nil {
 		log.Println("Error occurred", e1, e2)
 		return
diff --git a/go/src/vanadium/discovery/discovery.go b/go/src/vanadium/discovery/discovery.go
index 91cf623..56932d0 100644
--- a/go/src/vanadium/discovery/discovery.go
+++ b/go/src/vanadium/discovery/discovery.go
@@ -31,7 +31,8 @@
 	mu    sync.Mutex
 	stubs map[*bindings.Stub]struct{}
 
-	ctx      *context.T
+	ctx *context.T
+
 	shutdown v23.Shutdown
 	impl     *internal.DiscoveryService
 }
diff --git a/java/Manifest.txt b/java/Manifest.txt
new file mode 100644
index 0000000..f92559b
--- /dev/null
+++ b/java/Manifest.txt
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Dex-Location: classes.dex
+Mojo-Class: io.v.mojo.discovery.VDiscoveryApp
diff --git a/java/build.gradle b/java/build.gradle
new file mode 100644
index 0000000..d410fd4
--- /dev/null
+++ b/java/build.gradle
@@ -0,0 +1,59 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.2.3'
+        classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0'
+    }
+}
+
+apply plugin: 'android-sdk-manager'
+apply plugin: 'com.android.application'
+apply plugin: 'wrapper'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "23.0.1"
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+    packagingOptions {
+        exclude 'META-INF/LICENSE.txt'
+        exclude 'META-INF/NOTICE.txt'
+    }
+
+    defaultConfig {
+        applicationId "io.v.mojo.discovery"
+        minSdkVersion 22
+        targetSdkVersion 22
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+repositories {
+    mavenCentral()
+    jcenter()
+}
+
+android.sourceSets.main.java.srcDirs += 'generated-src'
+
+dependencies {
+    compile 'io.v:vanadium-android:0.9'
+    compile files(System.getenv("MOJO_DIR") + "/src/out/android_Debug/gen/mojo/public/java/application.jar")
+    compile files(System.getenv("MOJO_DIR") + "/src/out/android_Debug/gen/mojo/public/interfaces/application/application_java.jar")
+    compile files(System.getenv("MOJO_DIR") + "/src/out/android_Debug/gen/mojo/public/java/bindings.jar")
+
+    provided files(System.getenv("MOJO_DIR") + "/src/out/android_Debug/gen/mojo/public/java/system.jar")
+}
+
+
diff --git a/java/generated-src/io/v/mojo/discovery/Advertiser.java b/java/generated-src/io/v/mojo/discovery/Advertiser.java
new file mode 100644
index 0000000..2878c73
--- /dev/null
+++ b/java/generated-src/io/v/mojo/discovery/Advertiser.java
@@ -0,0 +1,30 @@
+// 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.
+
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is autogenerated by:
+//     mojo/public/tools/bindings/mojom_bindings_generator.py
+// For:
+//     mojom/vanadium/discovery.mojom
+//
+
+package io.v.mojo.discovery;
+
+public interface Advertiser extends org.chromium.mojo.bindings.Interface {
+
+    public interface Proxy extends Advertiser, org.chromium.mojo.bindings.Interface.Proxy {
+    }
+
+    NamedManager<Advertiser, Advertiser.Proxy> MANAGER = Advertiser_Internal.MANAGER;
+
+    void advertise(Service service, String[] visibility, AdvertiseResponse callback);
+    interface AdvertiseResponse extends org.chromium.mojo.bindings.Callbacks.Callback3<Integer, String, Error> { }
+
+    void stop(int h, StopResponse callback);
+    interface StopResponse extends org.chromium.mojo.bindings.Callbacks.Callback1<Error> { }
+}
+
diff --git a/java/generated-src/io/v/mojo/discovery/Advertiser_Internal.java b/java/generated-src/io/v/mojo/discovery/Advertiser_Internal.java
new file mode 100644
index 0000000..8ae035f
--- /dev/null
+++ b/java/generated-src/io/v/mojo/discovery/Advertiser_Internal.java
@@ -0,0 +1,584 @@
+// 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.
+
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is autogenerated by:
+//     mojo/public/tools/bindings/mojom_bindings_generator.py
+// For:
+//     mojom/vanadium/discovery.mojom
+//
+
+package io.v.mojo.discovery;
+
+class Advertiser_Internal {
+
+    public static final org.chromium.mojo.bindings.Interface.NamedManager<Advertiser, Advertiser.Proxy> MANAGER =
+            new org.chromium.mojo.bindings.Interface.NamedManager<Advertiser, Advertiser.Proxy>() {
+    
+        public String getName() {
+            return "v23::discovery::Advertiser";
+        }
+    
+        public int getVersion() {
+          return 0;
+        }
+    
+        public Proxy buildProxy(org.chromium.mojo.system.Core core,
+                                org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
+            return new Proxy(core, messageReceiver);
+        }
+    
+        public Stub buildStub(org.chromium.mojo.system.Core core, Advertiser impl) {
+            return new Stub(core, impl);
+        }
+    
+        public Advertiser[] buildArray(int size) {
+          return new Advertiser[size];
+        }
+    };
+
+    private static final int ADVERTISE_ORDINAL = 0;
+    private static final int STOP_ORDINAL = 1;
+
+    static final class Proxy extends org.chromium.mojo.bindings.Interface.AbstractProxy implements Advertiser.Proxy {
+
+        Proxy(org.chromium.mojo.system.Core core,
+              org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
+            super(core, messageReceiver);
+        }
+
+        @Override
+        public void advertise(Service service, String[] visibility, AdvertiseResponse callback) {
+            AdvertiserAdvertiseParams _message = new AdvertiserAdvertiseParams();
+            _message.service = service;
+            _message.visibility = visibility;
+            getProxyHandler().getMessageReceiver().acceptWithResponder(
+                    _message.serializeWithHeader(
+                            getProxyHandler().getCore(),
+                            new org.chromium.mojo.bindings.MessageHeader(
+                                    ADVERTISE_ORDINAL,
+                                    org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG,
+                                    0)),
+                    new AdvertiserAdvertiseResponseParamsForwardToCallback(callback));
+        }
+
+        @Override
+        public void stop(int h, StopResponse callback) {
+            AdvertiserStopParams _message = new AdvertiserStopParams();
+            _message.h = h;
+            getProxyHandler().getMessageReceiver().acceptWithResponder(
+                    _message.serializeWithHeader(
+                            getProxyHandler().getCore(),
+                            new org.chromium.mojo.bindings.MessageHeader(
+                                    STOP_ORDINAL,
+                                    org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG,
+                                    0)),
+                    new AdvertiserStopResponseParamsForwardToCallback(callback));
+        }
+
+    }
+
+    static final class Stub extends org.chromium.mojo.bindings.Interface.Stub<Advertiser> {
+
+        Stub(org.chromium.mojo.system.Core core, Advertiser impl) {
+            super(core, impl);
+        }
+
+        @Override
+        public boolean accept(org.chromium.mojo.bindings.Message message) {
+            try {
+                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+                        message.asServiceMessage();
+                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+                if (!header.validateHeader(org.chromium.mojo.bindings.MessageHeader.NO_FLAG)) {
+                    return false;
+                }
+                switch(header.getType()) {
+                    case org.chromium.mojo.bindings.InterfaceControlMessagesConstants.RUN_OR_CLOSE_PIPE_MESSAGE_ID:
+                        return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRunOrClosePipe(
+                                Advertiser_Internal.MANAGER, messageWithHeader);
+                    default:
+                        return false;
+                }
+            } catch (org.chromium.mojo.bindings.DeserializationException e) {
+                System.err.println(e.toString());
+                return false;
+            }
+        }
+
+        @Override
+        public boolean acceptWithResponder(org.chromium.mojo.bindings.Message message, org.chromium.mojo.bindings.MessageReceiver receiver) {
+            try {
+                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+                        message.asServiceMessage();
+                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+                if (!header.validateHeader(org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)) {
+                    return false;
+                }
+                switch(header.getType()) {
+                    case org.chromium.mojo.bindings.InterfaceControlMessagesConstants.RUN_MESSAGE_ID:
+                        return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRun(
+                                getCore(), Advertiser_Internal.MANAGER, messageWithHeader, receiver);
+                    case ADVERTISE_ORDINAL: {
+                        AdvertiserAdvertiseParams data =
+                                AdvertiserAdvertiseParams.deserialize(messageWithHeader.getPayload());
+                        getImpl().advertise(data.service, data.visibility, new AdvertiserAdvertiseResponseParamsProxyToResponder(getCore(), receiver, header.getRequestId()));
+                        return true;
+                    }
+                    case STOP_ORDINAL: {
+                        AdvertiserStopParams data =
+                                AdvertiserStopParams.deserialize(messageWithHeader.getPayload());
+                        getImpl().stop(data.h, new AdvertiserStopResponseParamsProxyToResponder(getCore(), receiver, header.getRequestId()));
+                        return true;
+                    }
+                    default:
+                        return false;
+                }
+            } catch (org.chromium.mojo.bindings.DeserializationException e) {
+                System.err.println(e.toString());
+                return false;
+            }
+        }
+    }
+
+    static final class AdvertiserAdvertiseParams extends org.chromium.mojo.bindings.Struct {
+    
+        private static final int STRUCT_SIZE = 24;
+        private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(24, 0)};
+        private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+    
+        public Service service;
+        public String[] visibility;
+    
+        private AdvertiserAdvertiseParams(int version) {
+            super(STRUCT_SIZE, version);
+        }
+    
+        public AdvertiserAdvertiseParams() {
+            this(0);
+        }
+    
+        public static AdvertiserAdvertiseParams deserialize(org.chromium.mojo.bindings.Message message) {
+            return decode(new org.chromium.mojo.bindings.Decoder(message));
+        }
+    
+        @SuppressWarnings("unchecked")
+        public static AdvertiserAdvertiseParams decode(org.chromium.mojo.bindings.Decoder decoder0) {
+            if (decoder0 == null) {
+                return null;
+            }
+            org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+            AdvertiserAdvertiseParams result = new AdvertiserAdvertiseParams(mainDataHeader.elementsOrVersion);
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(8, false);
+                result.service = Service.decode(decoder1);
+            }
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(16, true);
+                if (decoder1 == null) {
+                    result.visibility = null;
+                } else {
+                    org.chromium.mojo.bindings.DataHeader si1 = decoder1.readDataHeaderForPointerArray(org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+                    result.visibility = new String[si1.elementsOrVersion];
+                    for (int i1 = 0; i1 < si1.elementsOrVersion; ++i1) {
+                        result.visibility[i1] = decoder1.readString(org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE * i1, false);
+                    }
+                }
+            }
+            return result;
+        }
+    
+        @SuppressWarnings("unchecked")
+        @Override
+        protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+            org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+            encoder0.encode(service, 8, false);
+            if (visibility == null) {
+                encoder0.encodeNullPointer(16, true);
+            } else {
+                org.chromium.mojo.bindings.Encoder encoder1 = encoder0.encodePointerArray(visibility.length, 16, org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+                for (int i0 = 0; i0 < visibility.length; ++i0) {
+                    encoder1.encode(visibility[i0], org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE * i0, false);
+                }
+            }
+        }
+    
+        /**
+         * @see Object#equals(Object)
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this)
+                return true;
+            if (object == null)
+                return false;
+            if (getClass() != object.getClass())
+                return false;
+            AdvertiserAdvertiseParams other = (AdvertiserAdvertiseParams) object;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.service, other.service))
+                return false;
+            if (!java.util.Arrays.deepEquals(this.visibility, other.visibility))
+                return false;
+            return true;
+        }
+    
+        /**
+         * @see Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = prime + getClass().hashCode();
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(service);
+            result = prime * result + java.util.Arrays.deepHashCode(visibility);
+            return result;
+        }
+    }
+
+    static final class AdvertiserAdvertiseResponseParams extends org.chromium.mojo.bindings.Struct {
+    
+        private static final int STRUCT_SIZE = 32;
+        private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(32, 0)};
+        private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+    
+        public int handle;
+        public String instanceId;
+        public Error err;
+    
+        private AdvertiserAdvertiseResponseParams(int version) {
+            super(STRUCT_SIZE, version);
+        }
+    
+        public AdvertiserAdvertiseResponseParams() {
+            this(0);
+        }
+    
+        public static AdvertiserAdvertiseResponseParams deserialize(org.chromium.mojo.bindings.Message message) {
+            return decode(new org.chromium.mojo.bindings.Decoder(message));
+        }
+    
+        @SuppressWarnings("unchecked")
+        public static AdvertiserAdvertiseResponseParams decode(org.chromium.mojo.bindings.Decoder decoder0) {
+            if (decoder0 == null) {
+                return null;
+            }
+            org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+            AdvertiserAdvertiseResponseParams result = new AdvertiserAdvertiseResponseParams(mainDataHeader.elementsOrVersion);
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                result.handle = decoder0.readInt(8);
+            }
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                result.instanceId = decoder0.readString(16, false);
+            }
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(24, true);
+                result.err = Error.decode(decoder1);
+            }
+            return result;
+        }
+    
+        @SuppressWarnings("unchecked")
+        @Override
+        protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+            org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+            encoder0.encode(handle, 8);
+            encoder0.encode(instanceId, 16, false);
+            encoder0.encode(err, 24, true);
+        }
+    
+        /**
+         * @see Object#equals(Object)
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this)
+                return true;
+            if (object == null)
+                return false;
+            if (getClass() != object.getClass())
+                return false;
+            AdvertiserAdvertiseResponseParams other = (AdvertiserAdvertiseResponseParams) object;
+            if (this.handle != other.handle)
+                return false;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.instanceId, other.instanceId))
+                return false;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.err, other.err))
+                return false;
+            return true;
+        }
+    
+        /**
+         * @see Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = prime + getClass().hashCode();
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(handle);
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(instanceId);
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(err);
+            return result;
+        }
+    }
+
+    static class AdvertiserAdvertiseResponseParamsForwardToCallback extends org.chromium.mojo.bindings.SideEffectFreeCloseable
+            implements org.chromium.mojo.bindings.MessageReceiver {
+        private final Advertiser.AdvertiseResponse mCallback;
+
+        AdvertiserAdvertiseResponseParamsForwardToCallback(Advertiser.AdvertiseResponse callback) {
+            this.mCallback = callback;
+        }
+
+        @Override
+        public boolean accept(org.chromium.mojo.bindings.Message message) {
+            try {
+                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+                        message.asServiceMessage();
+                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+                if (!header.validateHeader(ADVERTISE_ORDINAL,
+                                           org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) {
+                    return false;
+                }
+                AdvertiserAdvertiseResponseParams response = AdvertiserAdvertiseResponseParams.deserialize(messageWithHeader.getPayload());
+                mCallback.call(response.handle, response.instanceId, response.err);
+                return true;
+            } catch (org.chromium.mojo.bindings.DeserializationException e) {
+                return false;
+            }
+        }
+    }
+
+    static class AdvertiserAdvertiseResponseParamsProxyToResponder implements Advertiser.AdvertiseResponse {
+
+        private final org.chromium.mojo.system.Core mCore;
+        private final org.chromium.mojo.bindings.MessageReceiver mMessageReceiver;
+        private final long mRequestId;
+
+        AdvertiserAdvertiseResponseParamsProxyToResponder(
+                org.chromium.mojo.system.Core core,
+                org.chromium.mojo.bindings.MessageReceiver messageReceiver,
+                long requestId) {
+            mCore = core;
+            mMessageReceiver = messageReceiver;
+            mRequestId = requestId;
+        }
+
+        @Override
+        public void call(Integer handle, String instanceId, Error err) {
+            AdvertiserAdvertiseResponseParams _response = new AdvertiserAdvertiseResponseParams();
+            _response.handle = handle;
+            _response.instanceId = instanceId;
+            _response.err = err;
+            org.chromium.mojo.bindings.ServiceMessage _message =
+                    _response.serializeWithHeader(
+                            mCore,
+                            new org.chromium.mojo.bindings.MessageHeader(
+                                    ADVERTISE_ORDINAL,
+                                    org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG,
+                                    mRequestId));
+            mMessageReceiver.accept(_message);
+        }
+    }
+
+    static final class AdvertiserStopParams extends org.chromium.mojo.bindings.Struct {
+    
+        private static final int STRUCT_SIZE = 16;
+        private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(16, 0)};
+        private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+    
+        public int h;
+    
+        private AdvertiserStopParams(int version) {
+            super(STRUCT_SIZE, version);
+        }
+    
+        public AdvertiserStopParams() {
+            this(0);
+        }
+    
+        public static AdvertiserStopParams deserialize(org.chromium.mojo.bindings.Message message) {
+            return decode(new org.chromium.mojo.bindings.Decoder(message));
+        }
+    
+        @SuppressWarnings("unchecked")
+        public static AdvertiserStopParams decode(org.chromium.mojo.bindings.Decoder decoder0) {
+            if (decoder0 == null) {
+                return null;
+            }
+            org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+            AdvertiserStopParams result = new AdvertiserStopParams(mainDataHeader.elementsOrVersion);
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                result.h = decoder0.readInt(8);
+            }
+            return result;
+        }
+    
+        @SuppressWarnings("unchecked")
+        @Override
+        protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+            org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+            encoder0.encode(h, 8);
+        }
+    
+        /**
+         * @see Object#equals(Object)
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this)
+                return true;
+            if (object == null)
+                return false;
+            if (getClass() != object.getClass())
+                return false;
+            AdvertiserStopParams other = (AdvertiserStopParams) object;
+            if (this.h != other.h)
+                return false;
+            return true;
+        }
+    
+        /**
+         * @see Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = prime + getClass().hashCode();
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(h);
+            return result;
+        }
+    }
+
+    static final class AdvertiserStopResponseParams extends org.chromium.mojo.bindings.Struct {
+    
+        private static final int STRUCT_SIZE = 16;
+        private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(16, 0)};
+        private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+    
+        public Error err;
+    
+        private AdvertiserStopResponseParams(int version) {
+            super(STRUCT_SIZE, version);
+        }
+    
+        public AdvertiserStopResponseParams() {
+            this(0);
+        }
+    
+        public static AdvertiserStopResponseParams deserialize(org.chromium.mojo.bindings.Message message) {
+            return decode(new org.chromium.mojo.bindings.Decoder(message));
+        }
+    
+        @SuppressWarnings("unchecked")
+        public static AdvertiserStopResponseParams decode(org.chromium.mojo.bindings.Decoder decoder0) {
+            if (decoder0 == null) {
+                return null;
+            }
+            org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+            AdvertiserStopResponseParams result = new AdvertiserStopResponseParams(mainDataHeader.elementsOrVersion);
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(8, true);
+                result.err = Error.decode(decoder1);
+            }
+            return result;
+        }
+    
+        @SuppressWarnings("unchecked")
+        @Override
+        protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+            org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+            encoder0.encode(err, 8, true);
+        }
+    
+        /**
+         * @see Object#equals(Object)
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this)
+                return true;
+            if (object == null)
+                return false;
+            if (getClass() != object.getClass())
+                return false;
+            AdvertiserStopResponseParams other = (AdvertiserStopResponseParams) object;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.err, other.err))
+                return false;
+            return true;
+        }
+    
+        /**
+         * @see Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = prime + getClass().hashCode();
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(err);
+            return result;
+        }
+    }
+
+    static class AdvertiserStopResponseParamsForwardToCallback extends org.chromium.mojo.bindings.SideEffectFreeCloseable
+            implements org.chromium.mojo.bindings.MessageReceiver {
+        private final Advertiser.StopResponse mCallback;
+
+        AdvertiserStopResponseParamsForwardToCallback(Advertiser.StopResponse callback) {
+            this.mCallback = callback;
+        }
+
+        @Override
+        public boolean accept(org.chromium.mojo.bindings.Message message) {
+            try {
+                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+                        message.asServiceMessage();
+                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+                if (!header.validateHeader(STOP_ORDINAL,
+                                           org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) {
+                    return false;
+                }
+                AdvertiserStopResponseParams response = AdvertiserStopResponseParams.deserialize(messageWithHeader.getPayload());
+                mCallback.call(response.err);
+                return true;
+            } catch (org.chromium.mojo.bindings.DeserializationException e) {
+                return false;
+            }
+        }
+    }
+
+    static class AdvertiserStopResponseParamsProxyToResponder implements Advertiser.StopResponse {
+
+        private final org.chromium.mojo.system.Core mCore;
+        private final org.chromium.mojo.bindings.MessageReceiver mMessageReceiver;
+        private final long mRequestId;
+
+        AdvertiserStopResponseParamsProxyToResponder(
+                org.chromium.mojo.system.Core core,
+                org.chromium.mojo.bindings.MessageReceiver messageReceiver,
+                long requestId) {
+            mCore = core;
+            mMessageReceiver = messageReceiver;
+            mRequestId = requestId;
+        }
+
+        @Override
+        public void call(Error err) {
+            AdvertiserStopResponseParams _response = new AdvertiserStopResponseParams();
+            _response.err = err;
+            org.chromium.mojo.bindings.ServiceMessage _message =
+                    _response.serializeWithHeader(
+                            mCore,
+                            new org.chromium.mojo.bindings.MessageHeader(
+                                    STOP_ORDINAL,
+                                    org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG,
+                                    mRequestId));
+            mMessageReceiver.accept(_message);
+        }
+    }
+
+}
+
diff --git a/java/generated-src/io/v/mojo/discovery/Error.java b/java/generated-src/io/v/mojo/discovery/Error.java
new file mode 100644
index 0000000..642ea20
--- /dev/null
+++ b/java/generated-src/io/v/mojo/discovery/Error.java
@@ -0,0 +1,101 @@
+// 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.
+
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is autogenerated by:
+//     mojo/public/tools/bindings/mojom_bindings_generator.py
+// For:
+//     mojom/vanadium/discovery.mojom
+//
+
+package io.v.mojo.discovery;
+
+public final class Error extends org.chromium.mojo.bindings.Struct {
+
+    private static final int STRUCT_SIZE = 32;
+    private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(32, 0)};
+    private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+
+    public String id;
+    public int action;
+    public String msg;
+
+    private Error(int version) {
+        super(STRUCT_SIZE, version);
+    }
+
+    public Error() {
+        this(0);
+    }
+
+    public static Error deserialize(org.chromium.mojo.bindings.Message message) {
+        return decode(new org.chromium.mojo.bindings.Decoder(message));
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Error decode(org.chromium.mojo.bindings.Decoder decoder0) {
+        if (decoder0 == null) {
+            return null;
+        }
+        org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+        Error result = new Error(mainDataHeader.elementsOrVersion);
+        if (mainDataHeader.elementsOrVersion >= 0) {
+            result.id = decoder0.readString(8, false);
+        }
+        if (mainDataHeader.elementsOrVersion >= 0) {
+            result.action = decoder0.readInt(16);
+        }
+        if (mainDataHeader.elementsOrVersion >= 0) {
+            result.msg = decoder0.readString(24, false);
+        }
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+        org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+        encoder0.encode(id, 8, false);
+        encoder0.encode(action, 16);
+        encoder0.encode(msg, 24, false);
+    }
+
+    /**
+     * @see Object#equals(Object)
+     */
+    @Override
+    public boolean equals(Object object) {
+        if (object == this)
+            return true;
+        if (object == null)
+            return false;
+        if (getClass() != object.getClass())
+            return false;
+        Error other = (Error) object;
+        if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.id, other.id))
+            return false;
+        if (this.action != other.action)
+            return false;
+        if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.msg, other.msg))
+            return false;
+        return true;
+    }
+
+    /**
+     * @see Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = prime + getClass().hashCode();
+        result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(id);
+        result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(action);
+        result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(msg);
+        return result;
+    }
+}
+
diff --git a/java/generated-src/io/v/mojo/discovery/ScanHandler.java b/java/generated-src/io/v/mojo/discovery/ScanHandler.java
new file mode 100644
index 0000000..25cddab
--- /dev/null
+++ b/java/generated-src/io/v/mojo/discovery/ScanHandler.java
@@ -0,0 +1,28 @@
+// 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.
+
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is autogenerated by:
+//     mojo/public/tools/bindings/mojom_bindings_generator.py
+// For:
+//     mojom/vanadium/discovery.mojom
+//
+
+package io.v.mojo.discovery;
+
+public interface ScanHandler extends org.chromium.mojo.bindings.Interface {
+
+    public interface Proxy extends ScanHandler, org.chromium.mojo.bindings.Interface.Proxy {
+    }
+
+    NamedManager<ScanHandler, ScanHandler.Proxy> MANAGER = ScanHandler_Internal.MANAGER;
+
+    void found(Service service);
+
+    void lost(String instanceId);
+}
+
diff --git a/java/generated-src/io/v/mojo/discovery/ScanHandler_Internal.java b/java/generated-src/io/v/mojo/discovery/ScanHandler_Internal.java
new file mode 100644
index 0000000..d649ea2
--- /dev/null
+++ b/java/generated-src/io/v/mojo/discovery/ScanHandler_Internal.java
@@ -0,0 +1,279 @@
+// 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.
+
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is autogenerated by:
+//     mojo/public/tools/bindings/mojom_bindings_generator.py
+// For:
+//     mojom/vanadium/discovery.mojom
+//
+
+package io.v.mojo.discovery;
+
+class ScanHandler_Internal {
+
+    public static final org.chromium.mojo.bindings.Interface.NamedManager<ScanHandler, ScanHandler.Proxy> MANAGER =
+            new org.chromium.mojo.bindings.Interface.NamedManager<ScanHandler, ScanHandler.Proxy>() {
+    
+        public String getName() {
+            return "v23::discovery::ScanHandler";
+        }
+    
+        public int getVersion() {
+          return 0;
+        }
+    
+        public Proxy buildProxy(org.chromium.mojo.system.Core core,
+                                org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
+            return new Proxy(core, messageReceiver);
+        }
+    
+        public Stub buildStub(org.chromium.mojo.system.Core core, ScanHandler impl) {
+            return new Stub(core, impl);
+        }
+    
+        public ScanHandler[] buildArray(int size) {
+          return new ScanHandler[size];
+        }
+    };
+
+    private static final int FOUND_ORDINAL = 0;
+    private static final int LOST_ORDINAL = 1;
+
+    static final class Proxy extends org.chromium.mojo.bindings.Interface.AbstractProxy implements ScanHandler.Proxy {
+
+        Proxy(org.chromium.mojo.system.Core core,
+              org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
+            super(core, messageReceiver);
+        }
+
+        @Override
+        public void found(Service service) {
+            ScanHandlerFoundParams _message = new ScanHandlerFoundParams();
+            _message.service = service;
+            getProxyHandler().getMessageReceiver().accept(
+                    _message.serializeWithHeader(
+                            getProxyHandler().getCore(),
+                            new org.chromium.mojo.bindings.MessageHeader(FOUND_ORDINAL)));
+        }
+
+        @Override
+        public void lost(String instanceId) {
+            ScanHandlerLostParams _message = new ScanHandlerLostParams();
+            _message.instanceId = instanceId;
+            getProxyHandler().getMessageReceiver().accept(
+                    _message.serializeWithHeader(
+                            getProxyHandler().getCore(),
+                            new org.chromium.mojo.bindings.MessageHeader(LOST_ORDINAL)));
+        }
+
+    }
+
+    static final class Stub extends org.chromium.mojo.bindings.Interface.Stub<ScanHandler> {
+
+        Stub(org.chromium.mojo.system.Core core, ScanHandler impl) {
+            super(core, impl);
+        }
+
+        @Override
+        public boolean accept(org.chromium.mojo.bindings.Message message) {
+            try {
+                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+                        message.asServiceMessage();
+                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+                if (!header.validateHeader(org.chromium.mojo.bindings.MessageHeader.NO_FLAG)) {
+                    return false;
+                }
+                switch(header.getType()) {
+                    case org.chromium.mojo.bindings.InterfaceControlMessagesConstants.RUN_OR_CLOSE_PIPE_MESSAGE_ID:
+                        return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRunOrClosePipe(
+                                ScanHandler_Internal.MANAGER, messageWithHeader);
+                    case FOUND_ORDINAL: {
+                        ScanHandlerFoundParams data =
+                                ScanHandlerFoundParams.deserialize(messageWithHeader.getPayload());
+                        getImpl().found(data.service);
+                        return true;
+                    }
+                    case LOST_ORDINAL: {
+                        ScanHandlerLostParams data =
+                                ScanHandlerLostParams.deserialize(messageWithHeader.getPayload());
+                        getImpl().lost(data.instanceId);
+                        return true;
+                    }
+                    default:
+                        return false;
+                }
+            } catch (org.chromium.mojo.bindings.DeserializationException e) {
+                System.err.println(e.toString());
+                return false;
+            }
+        }
+
+        @Override
+        public boolean acceptWithResponder(org.chromium.mojo.bindings.Message message, org.chromium.mojo.bindings.MessageReceiver receiver) {
+            try {
+                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+                        message.asServiceMessage();
+                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+                if (!header.validateHeader(org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)) {
+                    return false;
+                }
+                switch(header.getType()) {
+                    case org.chromium.mojo.bindings.InterfaceControlMessagesConstants.RUN_MESSAGE_ID:
+                        return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRun(
+                                getCore(), ScanHandler_Internal.MANAGER, messageWithHeader, receiver);
+                    default:
+                        return false;
+                }
+            } catch (org.chromium.mojo.bindings.DeserializationException e) {
+                System.err.println(e.toString());
+                return false;
+            }
+        }
+    }
+
+    static final class ScanHandlerFoundParams extends org.chromium.mojo.bindings.Struct {
+    
+        private static final int STRUCT_SIZE = 16;
+        private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(16, 0)};
+        private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+    
+        public Service service;
+    
+        private ScanHandlerFoundParams(int version) {
+            super(STRUCT_SIZE, version);
+        }
+    
+        public ScanHandlerFoundParams() {
+            this(0);
+        }
+    
+        public static ScanHandlerFoundParams deserialize(org.chromium.mojo.bindings.Message message) {
+            return decode(new org.chromium.mojo.bindings.Decoder(message));
+        }
+    
+        @SuppressWarnings("unchecked")
+        public static ScanHandlerFoundParams decode(org.chromium.mojo.bindings.Decoder decoder0) {
+            if (decoder0 == null) {
+                return null;
+            }
+            org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+            ScanHandlerFoundParams result = new ScanHandlerFoundParams(mainDataHeader.elementsOrVersion);
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(8, false);
+                result.service = Service.decode(decoder1);
+            }
+            return result;
+        }
+    
+        @SuppressWarnings("unchecked")
+        @Override
+        protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+            org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+            encoder0.encode(service, 8, false);
+        }
+    
+        /**
+         * @see Object#equals(Object)
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this)
+                return true;
+            if (object == null)
+                return false;
+            if (getClass() != object.getClass())
+                return false;
+            ScanHandlerFoundParams other = (ScanHandlerFoundParams) object;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.service, other.service))
+                return false;
+            return true;
+        }
+    
+        /**
+         * @see Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = prime + getClass().hashCode();
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(service);
+            return result;
+        }
+    }
+
+    static final class ScanHandlerLostParams extends org.chromium.mojo.bindings.Struct {
+    
+        private static final int STRUCT_SIZE = 16;
+        private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(16, 0)};
+        private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+    
+        public String instanceId;
+    
+        private ScanHandlerLostParams(int version) {
+            super(STRUCT_SIZE, version);
+        }
+    
+        public ScanHandlerLostParams() {
+            this(0);
+        }
+    
+        public static ScanHandlerLostParams deserialize(org.chromium.mojo.bindings.Message message) {
+            return decode(new org.chromium.mojo.bindings.Decoder(message));
+        }
+    
+        @SuppressWarnings("unchecked")
+        public static ScanHandlerLostParams decode(org.chromium.mojo.bindings.Decoder decoder0) {
+            if (decoder0 == null) {
+                return null;
+            }
+            org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+            ScanHandlerLostParams result = new ScanHandlerLostParams(mainDataHeader.elementsOrVersion);
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                result.instanceId = decoder0.readString(8, false);
+            }
+            return result;
+        }
+    
+        @SuppressWarnings("unchecked")
+        @Override
+        protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+            org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+            encoder0.encode(instanceId, 8, false);
+        }
+    
+        /**
+         * @see Object#equals(Object)
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this)
+                return true;
+            if (object == null)
+                return false;
+            if (getClass() != object.getClass())
+                return false;
+            ScanHandlerLostParams other = (ScanHandlerLostParams) object;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.instanceId, other.instanceId))
+                return false;
+            return true;
+        }
+    
+        /**
+         * @see Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = prime + getClass().hashCode();
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(instanceId);
+            return result;
+        }
+    }
+
+}
+
diff --git a/java/generated-src/io/v/mojo/discovery/Scanner.java b/java/generated-src/io/v/mojo/discovery/Scanner.java
new file mode 100644
index 0000000..514f956
--- /dev/null
+++ b/java/generated-src/io/v/mojo/discovery/Scanner.java
@@ -0,0 +1,30 @@
+// 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.
+
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is autogenerated by:
+//     mojo/public/tools/bindings/mojom_bindings_generator.py
+// For:
+//     mojom/vanadium/discovery.mojom
+//
+
+package io.v.mojo.discovery;
+
+public interface Scanner extends org.chromium.mojo.bindings.Interface {
+
+    public interface Proxy extends Scanner, org.chromium.mojo.bindings.Interface.Proxy {
+    }
+
+    NamedManager<Scanner, Scanner.Proxy> MANAGER = Scanner_Internal.MANAGER;
+
+    void scan(String query, ScanHandler scanHandler, ScanResponse callback);
+    interface ScanResponse extends org.chromium.mojo.bindings.Callbacks.Callback2<Integer, Error> { }
+
+    void stop(int h, StopResponse callback);
+    interface StopResponse extends org.chromium.mojo.bindings.Callbacks.Callback1<Error> { }
+}
+
diff --git a/java/generated-src/io/v/mojo/discovery/Scanner_Internal.java b/java/generated-src/io/v/mojo/discovery/Scanner_Internal.java
new file mode 100644
index 0000000..f219dc8
--- /dev/null
+++ b/java/generated-src/io/v/mojo/discovery/Scanner_Internal.java
@@ -0,0 +1,558 @@
+// 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.
+
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is autogenerated by:
+//     mojo/public/tools/bindings/mojom_bindings_generator.py
+// For:
+//     mojom/vanadium/discovery.mojom
+//
+
+package io.v.mojo.discovery;
+
+class Scanner_Internal {
+
+    public static final org.chromium.mojo.bindings.Interface.NamedManager<Scanner, Scanner.Proxy> MANAGER =
+            new org.chromium.mojo.bindings.Interface.NamedManager<Scanner, Scanner.Proxy>() {
+    
+        public String getName() {
+            return "v23::discovery::Scanner";
+        }
+    
+        public int getVersion() {
+          return 0;
+        }
+    
+        public Proxy buildProxy(org.chromium.mojo.system.Core core,
+                                org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
+            return new Proxy(core, messageReceiver);
+        }
+    
+        public Stub buildStub(org.chromium.mojo.system.Core core, Scanner impl) {
+            return new Stub(core, impl);
+        }
+    
+        public Scanner[] buildArray(int size) {
+          return new Scanner[size];
+        }
+    };
+
+    private static final int SCAN_ORDINAL = 0;
+    private static final int STOP_ORDINAL = 1;
+
+    static final class Proxy extends org.chromium.mojo.bindings.Interface.AbstractProxy implements Scanner.Proxy {
+
+        Proxy(org.chromium.mojo.system.Core core,
+              org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
+            super(core, messageReceiver);
+        }
+
+        @Override
+        public void scan(String query, ScanHandler scanHandler, ScanResponse callback) {
+            ScannerScanParams _message = new ScannerScanParams();
+            _message.query = query;
+            _message.scanHandler = scanHandler;
+            getProxyHandler().getMessageReceiver().acceptWithResponder(
+                    _message.serializeWithHeader(
+                            getProxyHandler().getCore(),
+                            new org.chromium.mojo.bindings.MessageHeader(
+                                    SCAN_ORDINAL,
+                                    org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG,
+                                    0)),
+                    new ScannerScanResponseParamsForwardToCallback(callback));
+        }
+
+        @Override
+        public void stop(int h, StopResponse callback) {
+            ScannerStopParams _message = new ScannerStopParams();
+            _message.h = h;
+            getProxyHandler().getMessageReceiver().acceptWithResponder(
+                    _message.serializeWithHeader(
+                            getProxyHandler().getCore(),
+                            new org.chromium.mojo.bindings.MessageHeader(
+                                    STOP_ORDINAL,
+                                    org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG,
+                                    0)),
+                    new ScannerStopResponseParamsForwardToCallback(callback));
+        }
+
+    }
+
+    static final class Stub extends org.chromium.mojo.bindings.Interface.Stub<Scanner> {
+
+        Stub(org.chromium.mojo.system.Core core, Scanner impl) {
+            super(core, impl);
+        }
+
+        @Override
+        public boolean accept(org.chromium.mojo.bindings.Message message) {
+            try {
+                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+                        message.asServiceMessage();
+                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+                if (!header.validateHeader(org.chromium.mojo.bindings.MessageHeader.NO_FLAG)) {
+                    return false;
+                }
+                switch(header.getType()) {
+                    case org.chromium.mojo.bindings.InterfaceControlMessagesConstants.RUN_OR_CLOSE_PIPE_MESSAGE_ID:
+                        return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRunOrClosePipe(
+                                Scanner_Internal.MANAGER, messageWithHeader);
+                    default:
+                        return false;
+                }
+            } catch (org.chromium.mojo.bindings.DeserializationException e) {
+                System.err.println(e.toString());
+                return false;
+            }
+        }
+
+        @Override
+        public boolean acceptWithResponder(org.chromium.mojo.bindings.Message message, org.chromium.mojo.bindings.MessageReceiver receiver) {
+            try {
+                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+                        message.asServiceMessage();
+                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+                if (!header.validateHeader(org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)) {
+                    return false;
+                }
+                switch(header.getType()) {
+                    case org.chromium.mojo.bindings.InterfaceControlMessagesConstants.RUN_MESSAGE_ID:
+                        return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRun(
+                                getCore(), Scanner_Internal.MANAGER, messageWithHeader, receiver);
+                    case SCAN_ORDINAL: {
+                        ScannerScanParams data =
+                                ScannerScanParams.deserialize(messageWithHeader.getPayload());
+                        getImpl().scan(data.query, data.scanHandler, new ScannerScanResponseParamsProxyToResponder(getCore(), receiver, header.getRequestId()));
+                        return true;
+                    }
+                    case STOP_ORDINAL: {
+                        ScannerStopParams data =
+                                ScannerStopParams.deserialize(messageWithHeader.getPayload());
+                        getImpl().stop(data.h, new ScannerStopResponseParamsProxyToResponder(getCore(), receiver, header.getRequestId()));
+                        return true;
+                    }
+                    default:
+                        return false;
+                }
+            } catch (org.chromium.mojo.bindings.DeserializationException e) {
+                System.err.println(e.toString());
+                return false;
+            }
+        }
+    }
+
+    static final class ScannerScanParams extends org.chromium.mojo.bindings.Struct {
+    
+        private static final int STRUCT_SIZE = 24;
+        private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(24, 0)};
+        private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+    
+        public String query;
+        public ScanHandler scanHandler;
+    
+        private ScannerScanParams(int version) {
+            super(STRUCT_SIZE, version);
+        }
+    
+        public ScannerScanParams() {
+            this(0);
+        }
+    
+        public static ScannerScanParams deserialize(org.chromium.mojo.bindings.Message message) {
+            return decode(new org.chromium.mojo.bindings.Decoder(message));
+        }
+    
+        @SuppressWarnings("unchecked")
+        public static ScannerScanParams decode(org.chromium.mojo.bindings.Decoder decoder0) {
+            if (decoder0 == null) {
+                return null;
+            }
+            org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+            ScannerScanParams result = new ScannerScanParams(mainDataHeader.elementsOrVersion);
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                result.query = decoder0.readString(8, false);
+            }
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                result.scanHandler = decoder0.readServiceInterface(16, false, ScanHandler.MANAGER);
+            }
+            return result;
+        }
+    
+        @SuppressWarnings("unchecked")
+        @Override
+        protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+            org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+            encoder0.encode(query, 8, false);
+            encoder0.encode(scanHandler, 16, false, ScanHandler.MANAGER);
+        }
+    
+        /**
+         * @see Object#equals(Object)
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this)
+                return true;
+            if (object == null)
+                return false;
+            if (getClass() != object.getClass())
+                return false;
+            ScannerScanParams other = (ScannerScanParams) object;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.query, other.query))
+                return false;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.scanHandler, other.scanHandler))
+                return false;
+            return true;
+        }
+    
+        /**
+         * @see Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = prime + getClass().hashCode();
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(query);
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(scanHandler);
+            return result;
+        }
+    }
+
+    static final class ScannerScanResponseParams extends org.chromium.mojo.bindings.Struct {
+    
+        private static final int STRUCT_SIZE = 24;
+        private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(24, 0)};
+        private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+    
+        public int handle;
+        public Error err;
+    
+        private ScannerScanResponseParams(int version) {
+            super(STRUCT_SIZE, version);
+        }
+    
+        public ScannerScanResponseParams() {
+            this(0);
+        }
+    
+        public static ScannerScanResponseParams deserialize(org.chromium.mojo.bindings.Message message) {
+            return decode(new org.chromium.mojo.bindings.Decoder(message));
+        }
+    
+        @SuppressWarnings("unchecked")
+        public static ScannerScanResponseParams decode(org.chromium.mojo.bindings.Decoder decoder0) {
+            if (decoder0 == null) {
+                return null;
+            }
+            org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+            ScannerScanResponseParams result = new ScannerScanResponseParams(mainDataHeader.elementsOrVersion);
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                result.handle = decoder0.readInt(8);
+            }
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(16, true);
+                result.err = Error.decode(decoder1);
+            }
+            return result;
+        }
+    
+        @SuppressWarnings("unchecked")
+        @Override
+        protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+            org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+            encoder0.encode(handle, 8);
+            encoder0.encode(err, 16, true);
+        }
+    
+        /**
+         * @see Object#equals(Object)
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this)
+                return true;
+            if (object == null)
+                return false;
+            if (getClass() != object.getClass())
+                return false;
+            ScannerScanResponseParams other = (ScannerScanResponseParams) object;
+            if (this.handle != other.handle)
+                return false;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.err, other.err))
+                return false;
+            return true;
+        }
+    
+        /**
+         * @see Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = prime + getClass().hashCode();
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(handle);
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(err);
+            return result;
+        }
+    }
+
+    static class ScannerScanResponseParamsForwardToCallback extends org.chromium.mojo.bindings.SideEffectFreeCloseable
+            implements org.chromium.mojo.bindings.MessageReceiver {
+        private final Scanner.ScanResponse mCallback;
+
+        ScannerScanResponseParamsForwardToCallback(Scanner.ScanResponse callback) {
+            this.mCallback = callback;
+        }
+
+        @Override
+        public boolean accept(org.chromium.mojo.bindings.Message message) {
+            try {
+                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+                        message.asServiceMessage();
+                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+                if (!header.validateHeader(SCAN_ORDINAL,
+                                           org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) {
+                    return false;
+                }
+                ScannerScanResponseParams response = ScannerScanResponseParams.deserialize(messageWithHeader.getPayload());
+                mCallback.call(response.handle, response.err);
+                return true;
+            } catch (org.chromium.mojo.bindings.DeserializationException e) {
+                return false;
+            }
+        }
+    }
+
+    static class ScannerScanResponseParamsProxyToResponder implements Scanner.ScanResponse {
+
+        private final org.chromium.mojo.system.Core mCore;
+        private final org.chromium.mojo.bindings.MessageReceiver mMessageReceiver;
+        private final long mRequestId;
+
+        ScannerScanResponseParamsProxyToResponder(
+                org.chromium.mojo.system.Core core,
+                org.chromium.mojo.bindings.MessageReceiver messageReceiver,
+                long requestId) {
+            mCore = core;
+            mMessageReceiver = messageReceiver;
+            mRequestId = requestId;
+        }
+
+        @Override
+        public void call(Integer handle, Error err) {
+            ScannerScanResponseParams _response = new ScannerScanResponseParams();
+            _response.handle = handle;
+            _response.err = err;
+            org.chromium.mojo.bindings.ServiceMessage _message =
+                    _response.serializeWithHeader(
+                            mCore,
+                            new org.chromium.mojo.bindings.MessageHeader(
+                                    SCAN_ORDINAL,
+                                    org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG,
+                                    mRequestId));
+            mMessageReceiver.accept(_message);
+        }
+    }
+
+    static final class ScannerStopParams extends org.chromium.mojo.bindings.Struct {
+    
+        private static final int STRUCT_SIZE = 16;
+        private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(16, 0)};
+        private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+    
+        public int h;
+    
+        private ScannerStopParams(int version) {
+            super(STRUCT_SIZE, version);
+        }
+    
+        public ScannerStopParams() {
+            this(0);
+        }
+    
+        public static ScannerStopParams deserialize(org.chromium.mojo.bindings.Message message) {
+            return decode(new org.chromium.mojo.bindings.Decoder(message));
+        }
+    
+        @SuppressWarnings("unchecked")
+        public static ScannerStopParams decode(org.chromium.mojo.bindings.Decoder decoder0) {
+            if (decoder0 == null) {
+                return null;
+            }
+            org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+            ScannerStopParams result = new ScannerStopParams(mainDataHeader.elementsOrVersion);
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                result.h = decoder0.readInt(8);
+            }
+            return result;
+        }
+    
+        @SuppressWarnings("unchecked")
+        @Override
+        protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+            org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+            encoder0.encode(h, 8);
+        }
+    
+        /**
+         * @see Object#equals(Object)
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this)
+                return true;
+            if (object == null)
+                return false;
+            if (getClass() != object.getClass())
+                return false;
+            ScannerStopParams other = (ScannerStopParams) object;
+            if (this.h != other.h)
+                return false;
+            return true;
+        }
+    
+        /**
+         * @see Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = prime + getClass().hashCode();
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(h);
+            return result;
+        }
+    }
+
+    static final class ScannerStopResponseParams extends org.chromium.mojo.bindings.Struct {
+    
+        private static final int STRUCT_SIZE = 16;
+        private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(16, 0)};
+        private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+    
+        public Error err;
+    
+        private ScannerStopResponseParams(int version) {
+            super(STRUCT_SIZE, version);
+        }
+    
+        public ScannerStopResponseParams() {
+            this(0);
+        }
+    
+        public static ScannerStopResponseParams deserialize(org.chromium.mojo.bindings.Message message) {
+            return decode(new org.chromium.mojo.bindings.Decoder(message));
+        }
+    
+        @SuppressWarnings("unchecked")
+        public static ScannerStopResponseParams decode(org.chromium.mojo.bindings.Decoder decoder0) {
+            if (decoder0 == null) {
+                return null;
+            }
+            org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+            ScannerStopResponseParams result = new ScannerStopResponseParams(mainDataHeader.elementsOrVersion);
+            if (mainDataHeader.elementsOrVersion >= 0) {
+                org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(8, true);
+                result.err = Error.decode(decoder1);
+            }
+            return result;
+        }
+    
+        @SuppressWarnings("unchecked")
+        @Override
+        protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+            org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+            encoder0.encode(err, 8, true);
+        }
+    
+        /**
+         * @see Object#equals(Object)
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this)
+                return true;
+            if (object == null)
+                return false;
+            if (getClass() != object.getClass())
+                return false;
+            ScannerStopResponseParams other = (ScannerStopResponseParams) object;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.err, other.err))
+                return false;
+            return true;
+        }
+    
+        /**
+         * @see Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = prime + getClass().hashCode();
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(err);
+            return result;
+        }
+    }
+
+    static class ScannerStopResponseParamsForwardToCallback extends org.chromium.mojo.bindings.SideEffectFreeCloseable
+            implements org.chromium.mojo.bindings.MessageReceiver {
+        private final Scanner.StopResponse mCallback;
+
+        ScannerStopResponseParamsForwardToCallback(Scanner.StopResponse callback) {
+            this.mCallback = callback;
+        }
+
+        @Override
+        public boolean accept(org.chromium.mojo.bindings.Message message) {
+            try {
+                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+                        message.asServiceMessage();
+                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+                if (!header.validateHeader(STOP_ORDINAL,
+                                           org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) {
+                    return false;
+                }
+                ScannerStopResponseParams response = ScannerStopResponseParams.deserialize(messageWithHeader.getPayload());
+                mCallback.call(response.err);
+                return true;
+            } catch (org.chromium.mojo.bindings.DeserializationException e) {
+                return false;
+            }
+        }
+    }
+
+    static class ScannerStopResponseParamsProxyToResponder implements Scanner.StopResponse {
+
+        private final org.chromium.mojo.system.Core mCore;
+        private final org.chromium.mojo.bindings.MessageReceiver mMessageReceiver;
+        private final long mRequestId;
+
+        ScannerStopResponseParamsProxyToResponder(
+                org.chromium.mojo.system.Core core,
+                org.chromium.mojo.bindings.MessageReceiver messageReceiver,
+                long requestId) {
+            mCore = core;
+            mMessageReceiver = messageReceiver;
+            mRequestId = requestId;
+        }
+
+        @Override
+        public void call(Error err) {
+            ScannerStopResponseParams _response = new ScannerStopResponseParams();
+            _response.err = err;
+            org.chromium.mojo.bindings.ServiceMessage _message =
+                    _response.serializeWithHeader(
+                            mCore,
+                            new org.chromium.mojo.bindings.MessageHeader(
+                                    STOP_ORDINAL,
+                                    org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG,
+                                    mRequestId));
+            mMessageReceiver.accept(_message);
+        }
+    }
+
+}
+
diff --git a/java/generated-src/io/v/mojo/discovery/Service.java b/java/generated-src/io/v/mojo/discovery/Service.java
new file mode 100644
index 0000000..cfae988
--- /dev/null
+++ b/java/generated-src/io/v/mojo/discovery/Service.java
@@ -0,0 +1,187 @@
+// 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.
+
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is autogenerated by:
+//     mojo/public/tools/bindings/mojom_bindings_generator.py
+// For:
+//     mojom/vanadium/discovery.mojom
+//
+
+package io.v.mojo.discovery;
+
+public final class Service extends org.chromium.mojo.bindings.Struct {
+
+    private static final int STRUCT_SIZE = 48;
+    private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] {new org.chromium.mojo.bindings.DataHeader(48, 0)};
+    private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[0];
+
+    public String instanceId;
+    public String instanceName;
+    public String interfaceName;
+    public java.util.Map<String, String> attrs;
+    public String[] addrs;
+
+    private Service(int version) {
+        super(STRUCT_SIZE, version);
+    }
+
+    public Service() {
+        this(0);
+    }
+
+    public static Service deserialize(org.chromium.mojo.bindings.Message message) {
+        return decode(new org.chromium.mojo.bindings.Decoder(message));
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Service decode(org.chromium.mojo.bindings.Decoder decoder0) {
+        if (decoder0 == null) {
+            return null;
+        }
+        org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+        Service result = new Service(mainDataHeader.elementsOrVersion);
+        if (mainDataHeader.elementsOrVersion >= 0) {
+            result.instanceId = decoder0.readString(8, true);
+        }
+        if (mainDataHeader.elementsOrVersion >= 0) {
+            result.instanceName = decoder0.readString(16, true);
+        }
+        if (mainDataHeader.elementsOrVersion >= 0) {
+            result.interfaceName = decoder0.readString(24, false);
+        }
+        if (mainDataHeader.elementsOrVersion >= 0) {
+            org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(32, true);
+            if (decoder1 == null) {
+                result.attrs = null;
+            } else {
+                decoder1.readDataHeaderForMap();
+                String[] keys0;
+                String[] values0;
+                {
+                    org.chromium.mojo.bindings.Decoder decoder2 = decoder1.readPointer(org.chromium.mojo.bindings.DataHeader.HEADER_SIZE, false);
+                    {
+                        org.chromium.mojo.bindings.DataHeader si2 = decoder2.readDataHeaderForPointerArray(org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+                        keys0 = new String[si2.elementsOrVersion];
+                        for (int i2 = 0; i2 < si2.elementsOrVersion; ++i2) {
+                            keys0[i2] = decoder2.readString(org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE * i2, false);
+                        }
+                    }
+                }
+                {
+                    org.chromium.mojo.bindings.Decoder decoder2 = decoder1.readPointer(org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE, false);
+                    {
+                        org.chromium.mojo.bindings.DataHeader si2 = decoder2.readDataHeaderForPointerArray(keys0.length);
+                        values0 = new String[si2.elementsOrVersion];
+                        for (int i2 = 0; i2 < si2.elementsOrVersion; ++i2) {
+                            values0[i2] = decoder2.readString(org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE * i2, false);
+                        }
+                    }
+                }
+                result.attrs = new java.util.HashMap<String, String>();
+                for (int index0 = 0; index0 < keys0.length; ++index0) {
+                    result.attrs.put(keys0[index0],  values0[index0]);
+                }
+            }
+        }
+        if (mainDataHeader.elementsOrVersion >= 0) {
+            org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(40, false);
+            {
+                org.chromium.mojo.bindings.DataHeader si1 = decoder1.readDataHeaderForPointerArray(org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+                result.addrs = new String[si1.elementsOrVersion];
+                for (int i1 = 0; i1 < si1.elementsOrVersion; ++i1) {
+                    result.addrs[i1] = decoder1.readString(org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE * i1, false);
+                }
+            }
+        }
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+        org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+        encoder0.encode(instanceId, 8, true);
+        encoder0.encode(instanceName, 16, true);
+        encoder0.encode(interfaceName, 24, false);
+        if (attrs == null) {
+            encoder0.encodeNullPointer(32, true);
+        } else {
+            org.chromium.mojo.bindings.Encoder encoder1 = encoder0.encoderForMap(32);
+            int size0 = attrs.size();
+            String[] keys0 = new String[size0];
+            String[] values0 = new String[size0];
+            int index0 = 0;
+            for (java.util.Map.Entry<String, String> entry0 : attrs.entrySet()) {
+                keys0[index0] = entry0.getKey();
+                values0[index0] = entry0.getValue();
+                ++index0;
+            }
+            {
+                org.chromium.mojo.bindings.Encoder encoder2 = encoder1.encodePointerArray(keys0.length, org.chromium.mojo.bindings.DataHeader.HEADER_SIZE, org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+                for (int i1 = 0; i1 < keys0.length; ++i1) {
+                    encoder2.encode(keys0[i1], org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE * i1, false);
+                }
+            }
+            {
+                org.chromium.mojo.bindings.Encoder encoder2 = encoder1.encodePointerArray(values0.length, org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE, org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+                for (int i1 = 0; i1 < values0.length; ++i1) {
+                    encoder2.encode(values0[i1], org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE * i1, false);
+                }
+            }
+        }
+        if (addrs == null) {
+            encoder0.encodeNullPointer(40, false);
+        } else {
+            org.chromium.mojo.bindings.Encoder encoder1 = encoder0.encodePointerArray(addrs.length, 40, org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+            for (int i0 = 0; i0 < addrs.length; ++i0) {
+                encoder1.encode(addrs[i0], org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE * i0, false);
+            }
+        }
+    }
+
+    /**
+     * @see Object#equals(Object)
+     */
+    @Override
+    public boolean equals(Object object) {
+        if (object == this)
+            return true;
+        if (object == null)
+            return false;
+        if (getClass() != object.getClass())
+            return false;
+        Service other = (Service) object;
+        if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.instanceId, other.instanceId))
+            return false;
+        if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.instanceName, other.instanceName))
+            return false;
+        if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.interfaceName, other.interfaceName))
+            return false;
+        if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.attrs, other.attrs))
+            return false;
+        if (!java.util.Arrays.deepEquals(this.addrs, other.addrs))
+            return false;
+        return true;
+    }
+
+    /**
+     * @see Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = prime + getClass().hashCode();
+        result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(instanceId);
+        result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(instanceName);
+        result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(interfaceName);
+        result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(attrs);
+        result = prime * result + java.util.Arrays.deepHashCode(addrs);
+        return result;
+    }
+}
+
diff --git a/java/generated-src/mojom/vanadium/discovery.mojom.srcjar b/java/generated-src/mojom/vanadium/discovery.mojom.srcjar
new file mode 100644
index 0000000..1f2979a
--- /dev/null
+++ b/java/generated-src/mojom/vanadium/discovery.mojom.srcjar
Binary files differ
diff --git a/java/gradle/wrapper/gradle-wrapper.jar b/java/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..085a1cd
--- /dev/null
+++ b/java/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/java/gradle/wrapper/gradle-wrapper.properties b/java/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..4483a73
--- /dev/null
+++ b/java/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Nov 20 15:21:22 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
diff --git a/java/gradlew b/java/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/java/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/java/src/main/AndroidManifest.xml b/java/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..bfb22d3
--- /dev/null
+++ b/java/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.v.mojo.Discovery">
+  <application>
+  </application>
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+  <uses-permission android:name="android.permission.INTERNET"/>
+  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+  <uses-permission android:name="android.permission.BLUETOOTH"/>
+</manifest>
diff --git a/java/src/main/java/io/v/mojo/discovery/AdvertiserImpl.java b/java/src/main/java/io/v/mojo/discovery/AdvertiserImpl.java
new file mode 100644
index 0000000..f2e8b1d
--- /dev/null
+++ b/java/src/main/java/io/v/mojo/discovery/AdvertiserImpl.java
@@ -0,0 +1,100 @@
+// 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.mojo.discovery;
+
+import android.app.admin.SystemUpdatePolicy;
+import android.util.Log;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.chromium.mojo.system.MojoException;
+
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.v.v23.context.CancelableVContext;
+import io.v.v23.context.VContext;
+import io.v.v23.discovery.Attributes;
+import io.v.v23.discovery.VDiscovery;
+import io.v.v23.security.BlessingPattern;
+
+class AdvertiserImpl implements Advertiser {
+
+    private VDiscovery discovery;
+    private VContext rootCtx;
+
+    private final AtomicInteger nextAdvertiser = new AtomicInteger(0);
+
+    private final Map<Integer, CancelableVContext> contextMap = new HashMap<>();
+
+    public AdvertiserImpl(VDiscovery discovery, VContext rootCtx) {
+        this.discovery = discovery;
+        this.rootCtx = rootCtx;
+    }
+    @Override
+    public void advertise(Service service, String[] visibility, final AdvertiseResponse callback) {
+        synchronized (this) {
+            final Integer nextValue = nextAdvertiser.getAndAdd(1);
+            CancelableVContext ctx = rootCtx.withCancel();
+            contextMap.put(nextValue, ctx);
+            Attributes attrs = null;
+            final io.v.v23.discovery.Service vService = new io.v.v23.discovery.Service(
+                    service.instanceId, service.instanceName, service.interfaceName,
+                    new Attributes(service.attrs), Arrays.asList(service.addrs));
+            if (service.attrs == null) {
+                vService.setAttrs(new Attributes(new HashMap<String, String>()));
+            }
+            List<BlessingPattern> patterns = new ArrayList<>(visibility.length);
+	    if (visibility != null) {
+		    for (String pattern : visibility) {
+			    patterns.add(new BlessingPattern(pattern));
+		    }
+	    }
+            ListenableFuture<ListenableFuture<Void>> done = discovery.advertise(ctx, vService, patterns);
+            Futures.addCallback(done, new FutureCallback<ListenableFuture<Void>>() {
+                @Override
+                public void onSuccess(ListenableFuture<Void> result) {
+                    callback.call(nextValue, vService.getInstanceId(), null);
+                }
+
+                @Override
+                public void onFailure(Throwable t) {
+                    System.out.println("Failed with " + t.toString());
+                    Error e = new Error();
+                    e.msg = t.toString();
+                    e.id = "unknown";
+                    callback.call(0, "", e);
+                }
+            });
+        }
+    }
+
+    @Override
+    public void stop(int h, StopResponse response) {
+        synchronized (this) {
+            CancelableVContext ctx = contextMap.get(h);
+            if (ctx != null) {
+                contextMap.remove(h);
+                ctx.cancel();
+            }
+            response.call(null);
+        }
+    }
+
+    @Override
+    public void close() {}
+
+    @Override
+    public void onConnectionError(MojoException e) {}
+
+}
diff --git a/java/src/main/java/io/v/mojo/discovery/ScannerImpl.java b/java/src/main/java/io/v/mojo/discovery/ScannerImpl.java
new file mode 100644
index 0000000..5e854d6
--- /dev/null
+++ b/java/src/main/java/io/v/mojo/discovery/ScannerImpl.java
@@ -0,0 +1,92 @@
+// 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.mojo.discovery;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.chromium.mojo.system.MojoException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.v.v23.InputChannels;
+import io.v.v23.context.CancelableVContext;
+import io.v.v23.context.VContext;
+import io.v.v23.discovery.Update;
+import io.v.v23.discovery.VDiscovery;
+import io.v.v23.rpc.Callback;
+import io.v.v23.verror.VException;
+
+class ScannerImpl implements Scanner{
+    private final VDiscovery discovery;
+    private final VContext rootCtx;
+
+
+    private final Map<Integer, CancelableVContext> contextMap  = new HashMap<>();
+
+    private final AtomicInteger nextScanner = new AtomicInteger(0);
+
+    public ScannerImpl(VDiscovery discovery, VContext rootCtx) {
+        this.discovery = discovery;
+        this.rootCtx = rootCtx;
+    }
+    @Override
+    public void scan(String query, final ScanHandler scanHandler, ScanResponse callback) {
+        synchronized (this) {
+            System.out.println("Got a scan call");
+            int handle = nextScanner.getAndAdd(1);
+            CancelableVContext ctx = rootCtx.withCancel();
+            contextMap.put(handle, ctx);
+            ListenableFuture<Void> done = InputChannels.withCallback(discovery.scan(ctx, query),
+                    new Callback<Update>() {
+                        @Override
+                        public void onSuccess(Update update) {
+                            if (update instanceof Update.Found) {
+                                Update.Found found = (Update.Found) update;
+                                io.v.v23.discovery.Service vService = found.getElem().getService();
+                                Service mService = new Service();
+                                mService.instanceId = vService.getInstanceId();
+                                mService.instanceName = vService.getInstanceName();
+                                mService.interfaceName = vService.getInterfaceName();
+                                mService.addrs = new String[vService.getAddrs().size()];
+                                mService.addrs = vService.getAddrs().toArray(mService.addrs);
+                                mService.attrs = vService.getAttrs();
+                                scanHandler.found(mService);
+                            } else {
+                                Update.Lost lost = (Update.Lost) update;
+                                scanHandler.lost(lost.getElem().getInstanceId());
+                            }
+                        }
+
+                        @Override
+                        public void onFailure(VException t) {
+
+                        }
+                    });
+            System.out.println("Returning a scan call");
+            callback.call(handle, null);
+        }
+    }
+
+    @Override
+    public void stop(int h, StopResponse response) {
+        synchronized (this) {
+            CancelableVContext ctx = contextMap.get(h);
+            if (ctx != null) {
+                contextMap.remove(h);
+                ctx.cancel();
+            }
+            response.call(null);
+        }
+    }
+
+    @Override
+    public void close() {}
+
+    @Override
+    public void onConnectionError(MojoException e) {}
+}
diff --git a/java/src/main/java/io/v/mojo/discovery/VDiscoveryApp.java b/java/src/main/java/io/v/mojo/discovery/VDiscoveryApp.java
new file mode 100644
index 0000000..6bd3a71
--- /dev/null
+++ b/java/src/main/java/io/v/mojo/discovery/VDiscoveryApp.java
@@ -0,0 +1,75 @@
+// 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.mojo.discovery;
+
+import android.content.Context;
+
+import org.chromium.mojo.application.ApplicationConnection;
+import org.chromium.mojo.application.ApplicationDelegate;
+import org.chromium.mojo.application.ApplicationRunner;
+import org.chromium.mojo.application.ServiceFactoryBinder;
+import org.chromium.mojo.bindings.InterfaceRequest;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojom.mojo.Shell;
+
+import io.v.android.v23.V;
+import io.v.v23.context.VContext;
+
+/**
+ * A mojo app that eposes  the v23 discovery api through the i.v.mojo.discovery interface.
+ */
+public class VDiscoveryApp implements ApplicationDelegate {
+
+    private final VContext rootCtx;
+
+    VDiscoveryApp(Context context) {
+        rootCtx = V.init(context);
+    }
+
+    @Override
+    public void initialize(Shell shell, String[] strings, String s) {}
+
+    @Override
+    public boolean configureIncomingConnection(ApplicationConnection applicationConnection) {
+        applicationConnection.addService(new ServiceFactoryBinder<Advertiser>() {
+            @Override
+            public void bind(InterfaceRequest<Advertiser> request) {
+                Advertiser.MANAGER.bind(new AdvertiserImpl(V.getDiscovery(rootCtx), rootCtx),
+                        request);
+            }
+
+            @Override
+            public String getInterfaceName() {
+                return Advertiser.MANAGER.getName();
+            }
+        });
+
+        applicationConnection.addService(new ServiceFactoryBinder<Scanner>() {
+            @Override
+            public void bind(InterfaceRequest<Scanner> request) {
+                Scanner.MANAGER.bind(new ScannerImpl(V.getDiscovery(rootCtx), rootCtx),
+                        request);
+
+            }
+
+            @Override
+            public String getInterfaceName() {
+                return Scanner.MANAGER.getName();
+            }
+        });
+        return true;
+    }
+
+    @Override
+    public void quit() {
+        V.shutdown();
+    }
+
+    public static void mojoMain(Context context, Core core,
+                                MessagePipeHandle applicationRequestHandle) {
+        ApplicationRunner.run(new VDiscoveryApp(context), core, applicationRequestHandle);
+    }
+}
diff --git a/java/src/main/res/values/strings.xml b/java/src/main/res/values/strings.xml
new file mode 100644
index 0000000..928385f
--- /dev/null
+++ b/java/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">Location</string>
+</resources>
\ No newline at end of file
diff --git a/lib/gen/dart-gen/mojom/lib/discovery/discovery.mojom.dart b/lib/gen/dart-gen/mojom/lib/discovery/discovery.mojom.dart
index ecdc676..3d27cd9 100644
--- a/lib/gen/dart-gen/mojom/lib/discovery/discovery.mojom.dart
+++ b/lib/gen/dart-gen/mojom/lib/discovery/discovery.mojom.dart
@@ -1009,9 +1009,7 @@
 
 const int kAdvertiser_advertise_name = 0;
 const int kAdvertiser_stop_name = 1;
-
-const String AdvertiserName =
-      'discovery::Advertiser';
+const String AdvertiserName = "v23::discovery::Advertiser";
 
 abstract class Advertiser {
   dynamic advertise(Service service,List<String> visibility,[Function responseFactory = null]);
@@ -1145,9 +1143,9 @@
   }
 
   factory AdvertiserProxy.connectToService(
-      bindings.ServiceConnector s, String url) {
+      bindings.ServiceConnector s, String url, [String serviceName]) {
     AdvertiserProxy p = new AdvertiserProxy.unbound();
-    s.connectToService(url, p);
+    s.connectToService(url, p, serviceName);
     return p;
   }
 
@@ -1286,9 +1284,7 @@
 
 const int kScanner_scan_name = 0;
 const int kScanner_stop_name = 1;
-
-const String ScannerName =
-      'discovery::Scanner';
+const String ScannerName = "v23::discovery::Scanner";
 
 abstract class Scanner {
   dynamic scan(String query,Object scanHandler,[Function responseFactory = null]);
@@ -1422,9 +1418,9 @@
   }
 
   factory ScannerProxy.connectToService(
-      bindings.ServiceConnector s, String url) {
+      bindings.ServiceConnector s, String url, [String serviceName]) {
     ScannerProxy p = new ScannerProxy.unbound();
-    s.connectToService(url, p);
+    s.connectToService(url, p, serviceName);
     return p;
   }
 
@@ -1562,9 +1558,7 @@
 
 const int kScanHandler_found_name = 0;
 const int kScanHandler_lost_name = 1;
-
-const String ScanHandlerName =
-      'discovery::ScanHandler';
+const String ScanHandlerName = "v23::discovery::ScanHandler";
 
 abstract class ScanHandler {
   void found(Service service);
@@ -1659,9 +1653,9 @@
   }
 
   factory ScanHandlerProxy.connectToService(
-      bindings.ServiceConnector s, String url) {
+      bindings.ServiceConnector s, String url, [String serviceName]) {
     ScanHandlerProxy p = new ScanHandlerProxy.unbound();
-    s.connectToService(url, p);
+    s.connectToService(url, p, serviceName);
     return p;
   }
 
diff --git a/mojom/vanadium/discovery.mojom b/mojom/vanadium/discovery.mojom
index c5e20ac..7a716fb 100644
--- a/mojom/vanadium/discovery.mojom
+++ b/mojom/vanadium/discovery.mojom
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+[JavaPackage="io.v.mojo.discovery"]
 module discovery;
 
 // Copied from v.io/v23/discovery/types.vdl