mojo/discovery: Updating to latest v23 Java and switching
to have a single stream of updates with an enum field
indicating whether it is a lost or found update instead
of two separate found and lost streams.

This is more consistent with Discovery API in other languages
and also more consistent with similar Dart APIs such as
Syncbase's watch.

The old approach also made it easy to get into synchronization
issues since there is no ordering gaurantee between two
separate streams of data.

MultiPart: 1/2

Change-Id: I3832039fb3ebea172e99bb25af6e8f090b258380
diff --git a/go/src/mojom/vanadium/discovery/discovery.mojom.go b/go/src/mojom/vanadium/discovery/discovery.mojom.go
index bf1b8dd..ebc0dbf 100644
--- a/go/src/mojom/vanadium/discovery/discovery.mojom.go
+++ b/go/src/mojom/vanadium/discovery/discovery.mojom.go
@@ -22,7 +22,9 @@
 // These IDs are the Mojom Identifiers / Type Keys.
 // Mojom libraries importing this one will use these identifiers when building
 // TypeReference objects.
+var ID_discovery_UpdateType__ string = "discovery_UpdateType__"
 var ID_discovery_Service__ string = "discovery_Service__"
+var ID_discovery_Update__ string = "discovery_Update__"
 var ID_discovery_Error__ string = "discovery_Error__"
 var ID_discovery_Advertiser__ string = "discovery_Advertiser__"
 var ID_discovery_Scanner__ string = "discovery_Scanner__"
@@ -31,12 +33,21 @@
 var discoveryDesc__ = make(map[string]mojom_types.UserDefinedType)
 
 func init() {
+	discoveryDesc__["discovery_UpdateType__"] = &mojom_types.UserDefinedTypeEnumType{
+		Value: discovery_UpdateType__(),
+	}
 	discoveryDesc__["discovery_Service__"] = &mojom_types.UserDefinedTypeStructType{
 		Value: discovery_Service__(),
 	}
+
+	discoveryDesc__["discovery_Update__"] = &mojom_types.UserDefinedTypeStructType{
+		Value: discovery_Update__(),
+	}
+
 	discoveryDesc__["discovery_Error__"] = &mojom_types.UserDefinedTypeStructType{
 		Value: discovery_Error__(),
 	}
+
 	discoveryDesc__["discovery_Advertiser__"] = &mojom_types.UserDefinedTypeInterfaceType{
 		Value: discovery_Advertiser__(),
 	}
@@ -47,6 +58,7 @@
 	discoveryDesc__["discovery_Advertiser_Advertise_ResponseParams__"] = &mojom_types.UserDefinedTypeStructType{
 		Value: discovery_Advertiser_Advertise_ResponseParams__(),
 	}
+
 	discoveryDesc__["discovery_Advertiser_Stop_Params__"] = &mojom_types.UserDefinedTypeStructType{
 		Value: discovery_Advertiser_Stop_Params__(),
 	}
@@ -54,6 +66,7 @@
 	discoveryDesc__["discovery_Advertiser_Stop_ResponseParams__"] = &mojom_types.UserDefinedTypeStructType{
 		Value: discovery_Advertiser_Stop_ResponseParams__(),
 	}
+
 	discoveryDesc__["discovery_Scanner__"] = &mojom_types.UserDefinedTypeInterfaceType{
 		Value: discovery_Scanner__(),
 	}
@@ -61,9 +74,17 @@
 		Value: discovery_Scanner_Scan_Params__(),
 	}
 
+	discoveryDesc__["discovery_ScanHandler__"] = &mojom_types.UserDefinedTypeInterfaceType{
+		Value: discovery_ScanHandler__(),
+	}
+	discoveryDesc__["discovery_ScanHandler_Update_Params__"] = &mojom_types.UserDefinedTypeStructType{
+		Value: discovery_ScanHandler_Update_Params__(),
+	}
+
 	discoveryDesc__["discovery_Scanner_Scan_ResponseParams__"] = &mojom_types.UserDefinedTypeStructType{
 		Value: discovery_Scanner_Scan_ResponseParams__(),
 	}
+
 	discoveryDesc__["discovery_Scanner_Stop_Params__"] = &mojom_types.UserDefinedTypeStructType{
 		Value: discovery_Scanner_Stop_Params__(),
 	}
@@ -71,22 +92,49 @@
 	discoveryDesc__["discovery_Scanner_Stop_ResponseParams__"] = &mojom_types.UserDefinedTypeStructType{
 		Value: discovery_Scanner_Stop_ResponseParams__(),
 	}
-	discoveryDesc__["discovery_ScanHandler__"] = &mojom_types.UserDefinedTypeInterfaceType{
-		Value: discovery_ScanHandler__(),
-	}
-	discoveryDesc__["discovery_ScanHandler_Found_Params__"] = &mojom_types.UserDefinedTypeStructType{
-		Value: discovery_ScanHandler_Found_Params__(),
-	}
-
-	discoveryDesc__["discovery_ScanHandler_Lost_Params__"] = &mojom_types.UserDefinedTypeStructType{
-		Value: discovery_ScanHandler_Lost_Params__(),
-	}
 
 }
 func GetAllMojomTypeDefinitions() map[string]mojom_types.UserDefinedType {
 	return discoveryDesc__
 }
 
+type UpdateType int32
+
+const (
+	UpdateType_Found = 1
+	UpdateType_Lost  = UpdateType_Found + 1
+)
+
+// String names and labels used by the MojomEnum types.
+var (
+	enumName_UpdateType            = "UpdateType"
+	enumFullIdentifier_UpdateType  = "discovery.UpdateType"
+	enumFieldName_UpdateType_Found = "Found"
+	enumFieldName_UpdateType_Lost  = "Lost"
+)
+
+func discovery_UpdateType__() mojom_types.MojomEnum {
+	return mojom_types.MojomEnum{
+		DeclData: &mojom_types.DeclarationData{
+			ShortName:      &enumName_UpdateType,
+			FullIdentifier: &enumFullIdentifier_UpdateType,
+		},
+		Values: []mojom_types.EnumValue{mojom_types.EnumValue{
+			DeclData: &mojom_types.DeclarationData{
+				ShortName: &enumFieldName_UpdateType_Found,
+			},
+			EnumTypeKey: ID_discovery_UpdateType__,
+			IntValue:    int32(1),
+		}, mojom_types.EnumValue{
+			DeclData: &mojom_types.DeclarationData{
+				ShortName: &enumFieldName_UpdateType_Lost,
+			},
+			EnumTypeKey: ID_discovery_UpdateType__,
+			IntValue:    int32(2),
+		}},
+	}
+}
+
 type Advertiser interface {
 	Advertise(inService Service, inVisibility *[]string) (outHandle uint32, outInstanceId string, outErr *Error, err error)
 	Stop(inH uint32) (outErr *Error, err error)
@@ -1465,8 +1513,7 @@
 }
 
 type ScanHandler interface {
-	Found(inService Service) (err error)
-	Lost(inInstanceId string) (err error)
+	Update(inUpdate Update) (err error)
 }
 
 var scanHandler_Name = "v23::discovery::ScanHandler"
@@ -1511,8 +1558,7 @@
 	return ScanHandler_Request(r), ScanHandler_Pointer(p)
 }
 
-const scanHandler_Found_Name uint32 = 0
-const scanHandler_Lost_Name uint32 = 1
+const scanHandler_Update_Name uint32 = 0
 
 type ScanHandler_Proxy struct {
 	router *bindings.Router
@@ -1530,16 +1576,16 @@
 	p.router.Close()
 }
 
-type scanHandler_Found_Params struct {
-	inService Service
+type scanHandler_Update_Params struct {
+	inUpdate Update
 }
 
-func (s *scanHandler_Found_Params) Encode(encoder *bindings.Encoder) error {
+func (s *scanHandler_Update_Params) Encode(encoder *bindings.Encoder) error {
 	encoder.StartStruct(8, 0)
 	if err := encoder.WritePointer(); err != nil {
 		return err
 	}
-	if err := s.inService.Encode(encoder); err != nil {
+	if err := s.inUpdate.Encode(encoder); err != nil {
 		return err
 	}
 	if err := encoder.Finish(); err != nil {
@@ -1548,23 +1594,23 @@
 	return nil
 }
 
-var scanHandler_Found_Params_Versions []bindings.DataHeader = []bindings.DataHeader{
+var scanHandler_Update_Params_Versions []bindings.DataHeader = []bindings.DataHeader{
 	bindings.DataHeader{16, 0},
 }
 
-func (s *scanHandler_Found_Params) Decode(decoder *bindings.Decoder) error {
+func (s *scanHandler_Update_Params) Decode(decoder *bindings.Decoder) error {
 	header, err := decoder.StartStruct()
 	if err != nil {
 		return err
 	}
-	index := sort.Search(len(scanHandler_Found_Params_Versions), func(i int) bool {
-		return scanHandler_Found_Params_Versions[i].ElementsOrVersion >= header.ElementsOrVersion
+	index := sort.Search(len(scanHandler_Update_Params_Versions), func(i int) bool {
+		return scanHandler_Update_Params_Versions[i].ElementsOrVersion >= header.ElementsOrVersion
 	})
-	if index < len(scanHandler_Found_Params_Versions) {
-		if scanHandler_Found_Params_Versions[index].ElementsOrVersion > header.ElementsOrVersion {
+	if index < len(scanHandler_Update_Params_Versions) {
+		if scanHandler_Update_Params_Versions[index].ElementsOrVersion > header.ElementsOrVersion {
 			index--
 		}
-		expectedSize := scanHandler_Found_Params_Versions[index].Size
+		expectedSize := scanHandler_Update_Params_Versions[index].Size
 		if expectedSize != header.Size {
 			return &bindings.ValidationError{bindings.UnexpectedStructHeader,
 				fmt.Sprintf("invalid struct header size: should be %d, but was %d", expectedSize, header.Size),
@@ -1579,7 +1625,7 @@
 		if pointer0 == 0 {
 			return &bindings.ValidationError{bindings.UnexpectedNullPointer, "unexpected null pointer"}
 		} else {
-			if err := s.inService.Decode(decoder); err != nil {
+			if err := s.inUpdate.Decode(decoder); err != nil {
 				return err
 			}
 		}
@@ -1592,138 +1638,34 @@
 
 // String names and labels used by the MojomStruct types.
 var (
-	structName_ScanHandlerFoundParams                = "ScanHandlerFoundParams"
-	structFullIdentifier_ScanHandlerFoundParams      = "discovery.ScanHandlerFoundParams"
-	structFieldName_ScanHandlerFoundParams_InService = "InService"
+	structName_ScanHandlerUpdateParams               = "ScanHandlerUpdateParams"
+	structFullIdentifier_ScanHandlerUpdateParams     = "discovery.ScanHandlerUpdateParams"
+	structFieldName_ScanHandlerUpdateParams_InUpdate = "InUpdate"
 )
 
-func discovery_ScanHandler_Found_Params__() mojom_types.MojomStruct {
+func discovery_ScanHandler_Update_Params__() mojom_types.MojomStruct {
 	return mojom_types.MojomStruct{
 		DeclData: &mojom_types.DeclarationData{
-			ShortName:      &structName_ScanHandlerFoundParams,
-			FullIdentifier: &structFullIdentifier_ScanHandlerFoundParams,
+			ShortName:      &structName_ScanHandlerUpdateParams,
+			FullIdentifier: &structFullIdentifier_ScanHandlerUpdateParams,
 		}, Fields: []mojom_types.StructField{mojom_types.StructField{
 			DeclData: &mojom_types.DeclarationData{
-				ShortName: &structFieldName_ScanHandlerFoundParams_InService,
+				ShortName: &structFieldName_ScanHandlerUpdateParams_InUpdate,
 			},
 			Type: &mojom_types.TypeTypeReference{
-				Value: mojom_types.TypeReference{Identifier: &ID_discovery_Service__,
-					TypeKey: &ID_discovery_Service__},
+				Value: mojom_types.TypeReference{Identifier: &ID_discovery_Update__,
+					TypeKey: &ID_discovery_Update__},
 			},
 		}},
 	}
 }
 
-func (p *ScanHandler_Proxy) Found(inService Service) (err error) {
-	payload := &scanHandler_Found_Params{
-		inService,
+func (p *ScanHandler_Proxy) Update(inUpdate Update) (err error) {
+	payload := &scanHandler_Update_Params{
+		inUpdate,
 	}
 	header := bindings.MessageHeader{
-		Type:  scanHandler_Found_Name,
-		Flags: bindings.MessageNoFlag,
-	}
-	var message *bindings.Message
-	if message, err = bindings.EncodeMessage(header, payload); err != nil {
-		err = fmt.Errorf("can't encode request: %v", err.Error())
-		p.Close_Proxy()
-		return
-	}
-	if err = p.router.Accept(message); err != nil {
-		p.Close_Proxy()
-		return
-	}
-	return
-}
-
-type scanHandler_Lost_Params struct {
-	inInstanceId string
-}
-
-func (s *scanHandler_Lost_Params) Encode(encoder *bindings.Encoder) error {
-	encoder.StartStruct(8, 0)
-	if err := encoder.WritePointer(); err != nil {
-		return err
-	}
-	if err := encoder.WriteString(s.inInstanceId); err != nil {
-		return err
-	}
-	if err := encoder.Finish(); err != nil {
-		return err
-	}
-	return nil
-}
-
-var scanHandler_Lost_Params_Versions []bindings.DataHeader = []bindings.DataHeader{
-	bindings.DataHeader{16, 0},
-}
-
-func (s *scanHandler_Lost_Params) Decode(decoder *bindings.Decoder) error {
-	header, err := decoder.StartStruct()
-	if err != nil {
-		return err
-	}
-	index := sort.Search(len(scanHandler_Lost_Params_Versions), func(i int) bool {
-		return scanHandler_Lost_Params_Versions[i].ElementsOrVersion >= header.ElementsOrVersion
-	})
-	if index < len(scanHandler_Lost_Params_Versions) {
-		if scanHandler_Lost_Params_Versions[index].ElementsOrVersion > header.ElementsOrVersion {
-			index--
-		}
-		expectedSize := scanHandler_Lost_Params_Versions[index].Size
-		if expectedSize != header.Size {
-			return &bindings.ValidationError{bindings.UnexpectedStructHeader,
-				fmt.Sprintf("invalid struct header size: should be %d, but was %d", expectedSize, header.Size),
-			}
-		}
-	}
-	if header.ElementsOrVersion >= 0 {
-		pointer0, err := decoder.ReadPointer()
-		if err != nil {
-			return err
-		}
-		if pointer0 == 0 {
-			return &bindings.ValidationError{bindings.UnexpectedNullPointer, "unexpected null pointer"}
-		} else {
-			value0, err := decoder.ReadString()
-			if err != nil {
-				return err
-			}
-			s.inInstanceId = value0
-		}
-	}
-	if err := decoder.Finish(); err != nil {
-		return err
-	}
-	return nil
-}
-
-// String names and labels used by the MojomStruct types.
-var (
-	structName_ScanHandlerLostParams                   = "ScanHandlerLostParams"
-	structFullIdentifier_ScanHandlerLostParams         = "discovery.ScanHandlerLostParams"
-	structFieldName_ScanHandlerLostParams_InInstanceId = "InInstanceId"
-)
-
-func discovery_ScanHandler_Lost_Params__() mojom_types.MojomStruct {
-	return mojom_types.MojomStruct{
-		DeclData: &mojom_types.DeclarationData{
-			ShortName:      &structName_ScanHandlerLostParams,
-			FullIdentifier: &structFullIdentifier_ScanHandlerLostParams,
-		}, Fields: []mojom_types.StructField{mojom_types.StructField{
-			DeclData: &mojom_types.DeclarationData{
-				ShortName: &structFieldName_ScanHandlerLostParams_InInstanceId,
-			},
-			Type: &mojom_types.TypeStringType{mojom_types.StringType{false}},
-		}},
-	}
-}
-
-func (p *ScanHandler_Proxy) Lost(inInstanceId string) (err error) {
-	payload := &scanHandler_Lost_Params{
-		inInstanceId,
-	}
-	header := bindings.MessageHeader{
-		Type:  scanHandler_Lost_Name,
+		Type:  scanHandler_Update_Name,
 		Flags: bindings.MessageNoFlag,
 	}
 	var message *bindings.Message
@@ -1750,10 +1692,9 @@
 }
 
 var (
-	interfaceName_ScanHandler             = "ScanHandler"
-	interfaceFullIdentifier_ScanHandler   = "discovery.ScanHandler"
-	interfaceMethodName_ScanHandler_Found = "Found"
-	interfaceMethodName_ScanHandler_Lost  = "Lost"
+	interfaceName_ScanHandler              = "ScanHandler"
+	interfaceFullIdentifier_ScanHandler    = "discovery.ScanHandler"
+	interfaceMethodName_ScanHandler_Update = "Update"
 )
 
 func discovery_ScanHandler__() mojom_types.MojomInterface {
@@ -1764,18 +1705,12 @@
 			ShortName:      &interfaceName_ScanHandler,
 			FullIdentifier: &interfaceFullIdentifier_ScanHandler,
 		},
-		Methods: map[uint32]mojom_types.MojomMethod{scanHandler_Found_Name: mojom_types.MojomMethod{
+		Methods: map[uint32]mojom_types.MojomMethod{scanHandler_Update_Name: mojom_types.MojomMethod{
 			DeclData: &mojom_types.DeclarationData{
-				ShortName: &interfaceMethodName_ScanHandler_Found,
+				ShortName: &interfaceMethodName_ScanHandler_Update,
 			},
-			Parameters:     discovery_ScanHandler_Found_Params__(),
-			ResponseParams: responseParamsMap[interfaceMethodName_ScanHandler_Found],
-		}, scanHandler_Lost_Name: mojom_types.MojomMethod{
-			DeclData: &mojom_types.DeclarationData{
-				ShortName: &interfaceMethodName_ScanHandler_Lost,
-			},
-			Parameters:     discovery_ScanHandler_Lost_Params__(),
-			ResponseParams: responseParamsMap[interfaceMethodName_ScanHandler_Lost],
+			Parameters:     discovery_ScanHandler_Update_Params__(),
+			ResponseParams: responseParamsMap[interfaceMethodName_ScanHandler_Update],
 		}},
 	}
 }
@@ -1806,31 +1741,17 @@
 
 func (s *scanHandler_Stub) Accept(message *bindings.Message) (err error) {
 	switch message.Header.Type {
-	case scanHandler_Found_Name:
+	case scanHandler_Update_Name:
 		if message.Header.Flags != bindings.MessageNoFlag {
 			return &bindings.ValidationError{bindings.MessageHeaderInvalidFlags,
 				fmt.Sprintf("invalid message header flag: %v", message.Header.Flags),
 			}
 		}
-		var request scanHandler_Found_Params
+		var request scanHandler_Update_Params
 		if err := message.DecodePayload(&request); err != nil {
 			return err
 		}
-		err = s.impl.Found(request.inService)
-		if err != nil {
-			return
-		}
-	case scanHandler_Lost_Name:
-		if message.Header.Flags != bindings.MessageNoFlag {
-			return &bindings.ValidationError{bindings.MessageHeaderInvalidFlags,
-				fmt.Sprintf("invalid message header flag: %v", message.Header.Flags),
-			}
-		}
-		var request scanHandler_Lost_Params
-		if err := message.DecodePayload(&request); err != nil {
-			return err
-		}
-		err = s.impl.Lost(request.inInstanceId)
+		err = s.impl.Update(request.inUpdate)
 		if err != nil {
 			return
 		}
@@ -2205,6 +2126,110 @@
 	}
 }
 
+type Update struct {
+	Service    Service
+	UpdateType UpdateType
+}
+
+func (s *Update) Encode(encoder *bindings.Encoder) error {
+	encoder.StartStruct(16, 0)
+	if err := encoder.WritePointer(); err != nil {
+		return err
+	}
+	if err := s.Service.Encode(encoder); err != nil {
+		return err
+	}
+	if err := encoder.WriteInt32(int32(s.UpdateType)); err != nil {
+		return err
+	}
+	if err := encoder.Finish(); err != nil {
+		return err
+	}
+	return nil
+}
+
+var update_Versions []bindings.DataHeader = []bindings.DataHeader{
+	bindings.DataHeader{24, 0},
+}
+
+func (s *Update) Decode(decoder *bindings.Decoder) error {
+	header, err := decoder.StartStruct()
+	if err != nil {
+		return err
+	}
+	index := sort.Search(len(update_Versions), func(i int) bool {
+		return update_Versions[i].ElementsOrVersion >= header.ElementsOrVersion
+	})
+	if index < len(update_Versions) {
+		if update_Versions[index].ElementsOrVersion > header.ElementsOrVersion {
+			index--
+		}
+		expectedSize := update_Versions[index].Size
+		if expectedSize != header.Size {
+			return &bindings.ValidationError{bindings.UnexpectedStructHeader,
+				fmt.Sprintf("invalid struct header size: should be %d, but was %d", expectedSize, header.Size),
+			}
+		}
+	}
+	if header.ElementsOrVersion >= 0 {
+		pointer0, err := decoder.ReadPointer()
+		if err != nil {
+			return err
+		}
+		if pointer0 == 0 {
+			return &bindings.ValidationError{bindings.UnexpectedNullPointer, "unexpected null pointer"}
+		} else {
+			if err := s.Service.Decode(decoder); err != nil {
+				return err
+			}
+		}
+	}
+	if header.ElementsOrVersion >= 0 {
+		value0, err := decoder.ReadInt32()
+		if err != nil {
+			return err
+		}
+		s.UpdateType = UpdateType(value0)
+	}
+	if err := decoder.Finish(); err != nil {
+		return err
+	}
+	return nil
+}
+
+// String names and labels used by the MojomStruct types.
+var (
+	structName_Update                 = "Update"
+	structFullIdentifier_Update       = "discovery.Update"
+	structFieldName_Update_Service    = "Service"
+	structFieldName_Update_UpdateType = "UpdateType"
+)
+
+func discovery_Update__() mojom_types.MojomStruct {
+	return mojom_types.MojomStruct{
+		DeclData: &mojom_types.DeclarationData{
+			ShortName:      &structName_Update,
+			FullIdentifier: &structFullIdentifier_Update,
+		}, Fields: []mojom_types.StructField{mojom_types.StructField{
+			DeclData: &mojom_types.DeclarationData{
+				ShortName: &structFieldName_Update_Service,
+			},
+			Type: &mojom_types.TypeTypeReference{
+				Value: mojom_types.TypeReference{Identifier: &ID_discovery_Service__,
+					TypeKey: &ID_discovery_Service__},
+			},
+		}, mojom_types.StructField{
+			DeclData: &mojom_types.DeclarationData{
+				ShortName: &structFieldName_Update_UpdateType,
+			},
+			Type: &mojom_types.TypeTypeReference{
+				Value: mojom_types.TypeReference{Identifier: &ID_discovery_UpdateType__,
+					TypeKey: &ID_discovery_UpdateType__},
+			},
+		}},
+	}
+}
+
 type Error struct {
 	Id     string
 	Action int32
diff --git a/go/src/vanadium/discovery/internal/discovery.go b/go/src/vanadium/discovery/internal/discovery.go
index 41bd5f5..5d28a81 100644
--- a/go/src/vanadium/discovery/internal/discovery.go
+++ b/go/src/vanadium/discovery/internal/discovery.go
@@ -152,9 +152,17 @@
 		for v := range scanCh {
 			switch value := v.(type) {
 			case discovery.UpdateFound:
-				proxy.Found(v2mService(value.Value.Service))
+				update := mojom.Update{
+					Service:    v2mService(value.Value.Service),
+					UpdateType: mojom.UpdateType_Found,
+				}
+				proxy.Update(update)
 			case discovery.UpdateLost:
-				proxy.Lost(value.Value.InstanceId)
+				update := mojom.Update{
+					Service:    v2mService(value.Value.Service),
+					UpdateType: mojom.UpdateType_Lost,
+				}
+				proxy.Update(update)
 			}
 		}
 	}()
diff --git a/java/build.gradle b/java/build.gradle
index d410fd4..30d92e7 100644
--- a/java/build.gradle
+++ b/java/build.gradle
@@ -48,12 +48,10 @@
 android.sourceSets.main.java.srcDirs += 'generated-src'
 
 dependencies {
-    compile 'io.v:vanadium-android:0.9'
+    compile 'io.v:vanadium-android:1.3'
     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/ScanHandler.java b/java/generated-src/io/v/mojo/discovery/ScanHandler.java
index 25cddab..08885bf 100644
--- a/java/generated-src/io/v/mojo/discovery/ScanHandler.java
+++ b/java/generated-src/io/v/mojo/discovery/ScanHandler.java
@@ -21,8 +21,6 @@
 
     NamedManager<ScanHandler, ScanHandler.Proxy> MANAGER = ScanHandler_Internal.MANAGER;
 
-    void found(Service service);
-
-    void lost(String instanceId);
+    void update(Update update);
 }
 
diff --git a/java/generated-src/io/v/mojo/discovery/ScanHandler_Internal.java b/java/generated-src/io/v/mojo/discovery/ScanHandler_Internal.java
index d649ea2..477d0e7 100644
--- a/java/generated-src/io/v/mojo/discovery/ScanHandler_Internal.java
+++ b/java/generated-src/io/v/mojo/discovery/ScanHandler_Internal.java
@@ -41,8 +41,7 @@
         }
     };
 
-    private static final int FOUND_ORDINAL = 0;
-    private static final int LOST_ORDINAL = 1;
+    private static final int UPDATE_ORDINAL = 0;
 
     static final class Proxy extends org.chromium.mojo.bindings.Interface.AbstractProxy implements ScanHandler.Proxy {
 
@@ -52,23 +51,13 @@
         }
 
         @Override
-        public void found(Service service) {
-            ScanHandlerFoundParams _message = new ScanHandlerFoundParams();
-            _message.service = service;
+        public void update(Update update) {
+            ScanHandlerUpdateParams _message = new ScanHandlerUpdateParams();
+            _message.update = update;
             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)));
+                            new org.chromium.mojo.bindings.MessageHeader(UPDATE_ORDINAL)));
         }
 
     }
@@ -92,16 +81,10 @@
                     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);
+                    case UPDATE_ORDINAL: {
+                        ScanHandlerUpdateParams data =
+                                ScanHandlerUpdateParams.deserialize(messageWithHeader.getPayload());
+                        getImpl().update(data.update);
                         return true;
                     }
                     default:
@@ -136,36 +119,36 @@
         }
     }
 
-    static final class ScanHandlerFoundParams extends org.chromium.mojo.bindings.Struct {
+    static final class ScanHandlerUpdateParams 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;
+        public Update update;
     
-        private ScanHandlerFoundParams(int version) {
+        private ScanHandlerUpdateParams(int version) {
             super(STRUCT_SIZE, version);
         }
     
-        public ScanHandlerFoundParams() {
+        public ScanHandlerUpdateParams() {
             this(0);
         }
     
-        public static ScanHandlerFoundParams deserialize(org.chromium.mojo.bindings.Message message) {
+        public static ScanHandlerUpdateParams 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) {
+        public static ScanHandlerUpdateParams 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);
+            ScanHandlerUpdateParams result = new ScanHandlerUpdateParams(mainDataHeader.elementsOrVersion);
             if (mainDataHeader.elementsOrVersion >= 0) {
                 org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(8, false);
-                result.service = Service.decode(decoder1);
+                result.update = Update.decode(decoder1);
             }
             return result;
         }
@@ -174,7 +157,7 @@
         @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);
+            encoder0.encode(update, 8, false);
         }
     
         /**
@@ -188,8 +171,8 @@
                 return false;
             if (getClass() != object.getClass())
                 return false;
-            ScanHandlerFoundParams other = (ScanHandlerFoundParams) object;
-            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.service, other.service))
+            ScanHandlerUpdateParams other = (ScanHandlerUpdateParams) object;
+            if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.update, other.update))
                 return false;
             return true;
         }
@@ -201,76 +184,7 @@
         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);
+            result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(update);
             return result;
         }
     }
diff --git a/java/generated-src/io/v/mojo/discovery/Update.java b/java/generated-src/io/v/mojo/discovery/Update.java
new file mode 100644
index 0000000..97a9bdc
--- /dev/null
+++ b/java/generated-src/io/v/mojo/discovery/Update.java
@@ -0,0 +1,94 @@
+// 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 Update 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 int updateType;
+
+    private Update(int version) {
+        super(STRUCT_SIZE, version);
+    }
+
+    public Update() {
+        this(0);
+    }
+
+    public static Update deserialize(org.chromium.mojo.bindings.Message message) {
+        return decode(new org.chromium.mojo.bindings.Decoder(message));
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Update decode(org.chromium.mojo.bindings.Decoder decoder0) {
+        if (decoder0 == null) {
+            return null;
+        }
+        org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+        Update result = new Update(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) {
+            result.updateType = decoder0.readInt(16);
+        }
+        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);
+        encoder0.encode(updateType, 16);
+    }
+
+    /**
+     * @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;
+        Update other = (Update) object;
+        if (!org.chromium.mojo.bindings.BindingsHelper.equals(this.service, other.service))
+            return false;
+        if (this.updateType != other.updateType)
+            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 + org.chromium.mojo.bindings.BindingsHelper.hashCode(updateType);
+        return result;
+    }
+}
+
diff --git a/java/generated-src/io/v/mojo/discovery/UpdateType.java b/java/generated-src/io/v/mojo/discovery/UpdateType.java
new file mode 100644
index 0000000..279da0b
--- /dev/null
+++ b/java/generated-src/io/v/mojo/discovery/UpdateType.java
@@ -0,0 +1,24 @@
+// 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 UpdateType {
+
+    public static final int FOUND = (int) (1L);
+    public static final int LOST = FOUND + 1;
+
+    private UpdateType() {}
+
+}
diff --git a/java/generated-src/mojom/vanadium/discovery.mojom.srcjar b/java/generated-src/mojom/vanadium/discovery.mojom.srcjar
index e2f880f..f97e92e 100644
--- a/java/generated-src/mojom/vanadium/discovery.mojom.srcjar
+++ b/java/generated-src/mojom/vanadium/discovery.mojom.srcjar
Binary files differ
diff --git a/java/src/main/java/io/v/mojo/discovery/AdvertiserImpl.java b/java/src/main/java/io/v/mojo/discovery/AdvertiserImpl.java
index 00f327a..7f5ff24 100644
--- a/java/src/main/java/io/v/mojo/discovery/AdvertiserImpl.java
+++ b/java/src/main/java/io/v/mojo/discovery/AdvertiserImpl.java
@@ -11,6 +11,7 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
+import io.v.v23.discovery.Attachments;
 import org.chromium.mojo.system.MojoException;
 
 import java.lang.Override;
@@ -22,7 +23,6 @@
 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;
@@ -35,7 +35,7 @@
 
     private final AtomicInteger nextAdvertiser = new AtomicInteger(0);
 
-    private final Map<Integer, CancelableVContext> contextMap = new HashMap<>();
+    private final Map<Integer, VContext> contextMap = new HashMap<>();
 
     public AdvertiserImpl(VDiscovery discovery, VContext rootCtx) {
         this.discovery = discovery;
@@ -45,12 +45,12 @@
     public void advertise(Service service, String[] visibility, final AdvertiseResponse callback) {
         synchronized (this) {
             final Integer nextValue = nextAdvertiser.getAndAdd(1);
-            CancelableVContext ctx = rootCtx.withCancel();
+            VContext 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));
+                    new Attributes(service.attrs), Arrays.asList(service.addrs), new Attachments());
             if (service.attrs == null) {
                 vService.setAttrs(new Attributes(new HashMap<String, String>()));
             }
@@ -85,7 +85,7 @@
     @Override
     public void stop(int h, StopResponse response) {
         synchronized (this) {
-            CancelableVContext ctx = contextMap.get(h);
+            VContext ctx = contextMap.get(h);
             if (ctx != null) {
                 contextMap.remove(h);
                 ctx.cancel();
diff --git a/java/src/main/java/io/v/mojo/discovery/ScannerImpl.java b/java/src/main/java/io/v/mojo/discovery/ScannerImpl.java
index 5e854d6..fb2431e 100644
--- a/java/src/main/java/io/v/mojo/discovery/ScannerImpl.java
+++ b/java/src/main/java/io/v/mojo/discovery/ScannerImpl.java
@@ -5,8 +5,10 @@
 package io.v.mojo.discovery;
 
 import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
+import io.v.v23.InputChannelCallback;
 import org.chromium.mojo.system.MojoException;
 
 import java.util.HashMap;
@@ -14,9 +16,7 @@
 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;
@@ -26,7 +26,7 @@
     private final VContext rootCtx;
 
 
-    private final Map<Integer, CancelableVContext> contextMap  = new HashMap<>();
+    private final Map<Integer, VContext> contextMap  = new HashMap<>();
 
     private final AtomicInteger nextScanner = new AtomicInteger(0);
 
@@ -39,33 +39,31 @@
         synchronized (this) {
             System.out.println("Got a scan call");
             int handle = nextScanner.getAndAdd(1);
-            CancelableVContext ctx = rootCtx.withCancel();
+            VContext ctx = rootCtx.withCancel();
             contextMap.put(handle, ctx);
             ListenableFuture<Void> done = InputChannels.withCallback(discovery.scan(ctx, query),
-                    new Callback<Update>() {
+                    new InputChannelCallback<io.v.v23.discovery.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);
+                        public ListenableFuture<Void> onNext(io.v.v23.discovery.Update update) {
+                            if (update instanceof io.v.v23.discovery.Update.Found) {
+                                io.v.v23.discovery.Update.Found found = (io.v.v23.discovery.Update.Found) update;
+                                Service mService = toMojoService(found.getElem().getService());
+                                Update mUpdate = new Update();
+                                mUpdate.service = mService;
+                                mUpdate.updateType = UpdateType.FOUND;
+                                scanHandler.update(mUpdate);
                             } else {
-                                Update.Lost lost = (Update.Lost) update;
-                                scanHandler.lost(lost.getElem().getInstanceId());
+                                io.v.v23.discovery.Update.Lost lost = (io.v.v23.discovery.Update.Lost) update;
+                                Service mService = toMojoService(lost.getElem().getService());
+                                Update mUpdate = new Update();
+                                mUpdate.service = mService;
+                                mUpdate.updateType = UpdateType.LOST;
+                                scanHandler.update(mUpdate);
                             }
+
+                            return Futures.immediateFuture(null);
                         }
 
-                        @Override
-                        public void onFailure(VException t) {
-
-                        }
                     });
             System.out.println("Returning a scan call");
             callback.call(handle, null);
@@ -75,7 +73,7 @@
     @Override
     public void stop(int h, StopResponse response) {
         synchronized (this) {
-            CancelableVContext ctx = contextMap.get(h);
+            VContext ctx = contextMap.get(h);
             if (ctx != null) {
                 contextMap.remove(h);
                 ctx.cancel();
@@ -89,4 +87,15 @@
 
     @Override
     public void onConnectionError(MojoException e) {}
+
+    private static Service toMojoService(io.v.v23.discovery.Service vService) {
+      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();
+      return mService;
+    }
 }
diff --git a/java/src/main/java/io/v/mojo/discovery/VDiscoveryApp.java b/java/src/main/java/io/v/mojo/discovery/VDiscoveryApp.java
index 6bd3a71..10a5219 100644
--- a/java/src/main/java/io/v/mojo/discovery/VDiscoveryApp.java
+++ b/java/src/main/java/io/v/mojo/discovery/VDiscoveryApp.java
@@ -65,7 +65,7 @@
 
     @Override
     public void quit() {
-        V.shutdown();
+        rootCtx.cancel();
     }
 
     public static void mojoMain(Context context, Core core,
diff --git a/lib/client_impl.dart b/lib/client_impl.dart
index 458ddab..476cd19 100644
--- a/lib/client_impl.dart
+++ b/lib/client_impl.dart
@@ -15,10 +15,9 @@
   }
 
   Future<Scanner> scan(String query) async {
-    StreamController<Service> onFound = new StreamController<Service>();
-    StreamController<String> onLost = new StreamController<String>();
+    StreamController<Update> onUpdate = new StreamController<Update>();
     ScanHandlerStub handlerStub = new ScanHandlerStub.unbound();
-    handlerStub.impl = new _ScanHandler(onFound, onLost);
+    handlerStub.impl = new _ScanHandler(onUpdate);
 
     ScannerScanResponseParams scanResponse =
         await _scannerProxy.ptr.scan(query, handlerStub);
@@ -29,7 +28,7 @@
     Future stop() {
       return _scannerProxy.ptr.stop(scanResponse.handle);
     }
-    return new _Scanner(stop, onFound.stream, onLost.stream);
+    return new _Scanner(stop, onUpdate.stream);
   }
 
   Future<Advertiser> advertise(Service service,
@@ -49,11 +48,10 @@
 }
 
 class _Scanner implements Scanner {
-  final Stream<Service> onFound;
-  final Stream<String> onLost;
+  final Stream<Update> onUpdate;
 
   final _StopFunction _stop;
-  _Scanner(this._stop, this.onFound, this.onLost) {}
+  _Scanner(this._stop, this.onUpdate) {}
 
   Future stop() {
     return _stop();
@@ -70,16 +68,11 @@
 }
 
 class _ScanHandler extends ScanHandler {
-  StreamController<Service> _onFound;
-  StreamController<String> _onLost;
+  StreamController<Update> _onUpdate;
 
-  _ScanHandler(this._onFound, this._onLost);
+  _ScanHandler(this._onUpdate);
 
-  found(Service s) {
-    _onFound.add(s);
-  }
-
-  lost(String instanceId) {
-    _onLost.add(instanceId);
+  update(Update update) {
+    _onUpdate.add(update);
   }
 }
diff --git a/lib/discovery.dart b/lib/discovery.dart
index eb40093..0e5a38c 100644
--- a/lib/discovery.dart
+++ b/lib/discovery.dart
@@ -8,7 +8,8 @@
 import 'package:mojo/bindings.dart' as bindings;
 import 'gen/dart-gen/mojom/lib/discovery/discovery.mojom.dart';
 
-export 'gen/dart-gen/mojom/lib/discovery/discovery.mojom.dart' show Service;
+export 'gen/dart-gen/mojom/lib/discovery/discovery.mojom.dart'
+    show Service, Update, UpdateType;
 
 part 'client_impl.dart';
 
@@ -27,7 +28,7 @@
   /// query and then stops scanning.
   ///
   ///    Scanner scanner = client.scan('v.InterfaceName = "v.io/i" AND v.Attrs["a"] = "v"');
-  ///    Service firstFoundService = await scanner.onFound.first;
+  ///    Service firstFoundService = await scanner.onUpdate.firstWhere((update) => update.updateType == UpdateTypes.found).service;
   ///    scanner.stop();
   ///
   /// The query is a WHERE expression of a syncQL query against advertised services, where
@@ -42,7 +43,7 @@
   /// An empty or null [visibility] means that there are no restrictions on visibility.
   /// Advertising will continue until [stop] is called on the [Advertiser] handle.
   ///
-  /// If service.InstanceId is not specified, a random unique identifier be
+  /// If service.InstanceId is not specified, a random unique identifier will be
   /// assigned to it. Any change to service will not be applied after advertising starts.
   ///
   /// It is an error to have simultaneously active advertisements for two identical
@@ -61,11 +62,8 @@
 
 /// Handle to a scan call.
 abstract class Scanner {
-  /// A stream of [Service] objects as they are discovered by the scanner.
-  Stream<Service> get onFound;
-
-  /// A stream of instanceIds for services that are no longer advertised.
-  Stream<String> get onLost;
+  /// A stream of [Update] objects as services are found or lost by the scanner.
+  Stream<Update> get onUpdate;
 
   /// Stops scanning.
   Future stop();
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 3d27cd9..81d0426 100644
--- a/lib/gen/dart-gen/mojom/lib/discovery/discovery.mojom.dart
+++ b/lib/gen/dart-gen/mojom/lib/discovery/discovery.mojom.dart
@@ -8,6 +8,55 @@
 
 import 'package:mojo/bindings.dart' as bindings;
 import 'package:mojo/core.dart' as core;
+class UpdateType extends bindings.MojoEnum {
+  static const found = const UpdateType._(1);
+  static const lost = const UpdateType._(2);
+
+  const UpdateType._(int v) : super(v);
+
+  static const Map<String, UpdateType> valuesMap = const {
+    "found": found,
+    "lost": lost,
+  };
+  static const List<UpdateType> values = const [
+    found,
+    lost,
+  ];
+
+  static UpdateType valueOf(String name) => valuesMap[name];
+
+  factory UpdateType(int v) {
+    switch (v) {
+      case 1:
+        return found;
+      case 2:
+        return lost;
+      default:
+        return null;
+    }
+  }
+
+  static UpdateType decode(bindings.Decoder decoder0, int offset) {
+    int v = decoder0.decodeUint32(offset);
+    UpdateType result = new UpdateType(v);
+    if (result == null) {
+      throw new bindings.MojoCodecError(
+          'Bad value $v for enum UpdateType.');
+    }
+    return result;
+  }
+
+  String toString() {
+    switch(this) {
+      case found:
+        return 'UpdateType.found';
+      case lost:
+        return 'UpdateType.lost';
+    }
+  }
+
+  int toJson() => value;
+}
 
 
 
@@ -186,6 +235,87 @@
 }
 
 
+class Update extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(24, 0)
+  ];
+  Service service = null;
+  UpdateType updateType = null;
+
+  Update() : super(kVersions.last.size);
+
+  static Update deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static Update decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    Update result = new Update();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.service = Service.decode(decoder1);
+    }
+    if (mainDataHeader.version >= 0) {
+      
+        result.updateType = UpdateType.decode(decoder0, 16);
+        if (result.updateType == null) {
+          throw new bindings.MojoCodecError(
+            'Trying to decode null union for non-nullable UpdateType.');
+        }
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    
+    encoder0.encodeStruct(service, 8, false);
+    
+    encoder0.encodeEnum(updateType, 16);
+  }
+
+  String toString() {
+    return "Update("
+           "service: $service" ", "
+           "updateType: $updateType" ")";
+  }
+
+  Map toJson() {
+    Map map = new Map();
+    map["service"] = service;
+    map["updateType"] = updateType;
+    return map;
+  }
+}
+
+
 class Error extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(32, 0)
@@ -873,15 +1003,15 @@
 }
 
 
-class ScanHandlerFoundParams extends bindings.Struct {
+class ScanHandlerUpdateParams extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
   ];
-  Service service = null;
+  Update update = null;
 
-  ScanHandlerFoundParams() : super(kVersions.last.size);
+  ScanHandlerUpdateParams() : super(kVersions.last.size);
 
-  static ScanHandlerFoundParams deserialize(bindings.Message message) {
+  static ScanHandlerUpdateParams deserialize(bindings.Message message) {
     var decoder = new bindings.Decoder(message);
     var result = decode(decoder);
     if (decoder.excessHandles != null) {
@@ -890,11 +1020,11 @@
     return result;
   }
 
-  static ScanHandlerFoundParams decode(bindings.Decoder decoder0) {
+  static ScanHandlerUpdateParams decode(bindings.Decoder decoder0) {
     if (decoder0 == null) {
       return null;
     }
-    ScanHandlerFoundParams result = new ScanHandlerFoundParams();
+    ScanHandlerUpdateParams result = new ScanHandlerUpdateParams();
 
     var mainDataHeader = decoder0.decodeStructDataHeader();
     if (mainDataHeader.version <= kVersions.last.version) {
@@ -917,7 +1047,7 @@
     if (mainDataHeader.version >= 0) {
       
       var decoder1 = decoder0.decodePointer(8, false);
-      result.service = Service.decode(decoder1);
+      result.update = Update.decode(decoder1);
     }
     return result;
   }
@@ -925,84 +1055,17 @@
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
     
-    encoder0.encodeStruct(service, 8, false);
+    encoder0.encodeStruct(update, 8, false);
   }
 
   String toString() {
-    return "ScanHandlerFoundParams("
-           "service: $service" ")";
+    return "ScanHandlerUpdateParams("
+           "update: $update" ")";
   }
 
   Map toJson() {
     Map map = new Map();
-    map["service"] = service;
-    return map;
-  }
-}
-
-
-class ScanHandlerLostParams extends bindings.Struct {
-  static const List<bindings.StructDataHeader> kVersions = const [
-    const bindings.StructDataHeader(16, 0)
-  ];
-  String instanceId = null;
-
-  ScanHandlerLostParams() : super(kVersions.last.size);
-
-  static ScanHandlerLostParams deserialize(bindings.Message message) {
-    var decoder = new bindings.Decoder(message);
-    var result = decode(decoder);
-    if (decoder.excessHandles != null) {
-      decoder.excessHandles.forEach((h) => h.close());
-    }
-    return result;
-  }
-
-  static ScanHandlerLostParams decode(bindings.Decoder decoder0) {
-    if (decoder0 == null) {
-      return null;
-    }
-    ScanHandlerLostParams result = new ScanHandlerLostParams();
-
-    var mainDataHeader = decoder0.decodeStructDataHeader();
-    if (mainDataHeader.version <= kVersions.last.version) {
-      // Scan in reverse order to optimize for more recent versions.
-      for (int i = kVersions.length - 1; i >= 0; --i) {
-        if (mainDataHeader.version >= kVersions[i].version) {
-          if (mainDataHeader.size == kVersions[i].size) {
-            // Found a match.
-            break;
-          }
-          throw new bindings.MojoCodecError(
-              'Header size doesn\'t correspond to known version size.');
-        }
-      }
-    } else if (mainDataHeader.size < kVersions.last.size) {
-      throw new bindings.MojoCodecError(
-        'Message newer than the last known version cannot be shorter than '
-        'required by the last known version.');
-    }
-    if (mainDataHeader.version >= 0) {
-      
-      result.instanceId = decoder0.decodeString(8, false);
-    }
-    return result;
-  }
-
-  void encode(bindings.Encoder encoder) {
-    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    
-    encoder0.encodeString(instanceId, 8, false);
-  }
-
-  String toString() {
-    return "ScanHandlerLostParams("
-           "instanceId: $instanceId" ")";
-  }
-
-  Map toJson() {
-    Map map = new Map();
-    map["instanceId"] = instanceId;
+    map["update"] = update;
     return map;
   }
 }
@@ -1556,13 +1619,11 @@
   int get version => 0;
 }
 
-const int kScanHandler_found_name = 0;
-const int kScanHandler_lost_name = 1;
+const int kScanHandler_update_name = 0;
 const String ScanHandlerName = "v23::discovery::ScanHandler";
 
 abstract class ScanHandler {
-  void found(Service service);
-  void lost(String instanceId);
+  void update(Update update);
 
 }
 
@@ -1604,24 +1665,14 @@
   ScanHandlerProxyImpl _proxyImpl;
 
   _ScanHandlerProxyCalls(this._proxyImpl);
-    void found(Service service) {
+    void update(Update update) {
       if (!_proxyImpl.isBound) {
         _proxyImpl.proxyError("The Proxy is closed.");
         return;
       }
-      var params = new ScanHandlerFoundParams();
-      params.service = service;
-      _proxyImpl.sendMessage(params, kScanHandler_found_name);
-    }
-  
-    void lost(String instanceId) {
-      if (!_proxyImpl.isBound) {
-        _proxyImpl.proxyError("The Proxy is closed.");
-        return;
-      }
-      var params = new ScanHandlerLostParams();
-      params.instanceId = instanceId;
-      _proxyImpl.sendMessage(params, kScanHandler_lost_name);
+      var params = new ScanHandlerUpdateParams();
+      params.update = update;
+      _proxyImpl.sendMessage(params, kScanHandler_update_name);
     }
   
 }
@@ -1715,15 +1766,10 @@
     }
     assert(_impl != null);
     switch (message.header.type) {
-      case kScanHandler_found_name:
-        var params = ScanHandlerFoundParams.deserialize(
+      case kScanHandler_update_name:
+        var params = ScanHandlerUpdateParams.deserialize(
             message.payload);
-        _impl.found(params.service);
-        break;
-      case kScanHandler_lost_name:
-        var params = ScanHandlerLostParams.deserialize(
-            message.payload);
-        _impl.lost(params.instanceId);
+        _impl.update(params.update);
         break;
       default:
         throw new bindings.MojoCodecError("Unexpected message name");
diff --git a/mojom/vanadium/discovery.mojom b/mojom/vanadium/discovery.mojom
index 7a716fb..d638974 100644
--- a/mojom/vanadium/discovery.mojom
+++ b/mojom/vanadium/discovery.mojom
@@ -23,6 +23,16 @@
   array<string> Addrs;
 };
 
+enum UpdateType {
+	  found = 1,
+	  lost,
+};
+
+struct Update {
+  Service service;
+  UpdateType update_type;
+};
+
 struct Error {
   string id;
   int32 action;
@@ -71,9 +81,6 @@
 // the scan.
 [ServiceName="v23::discovery::ScanHandler"]
 interface ScanHandler {
-  // Found will be called when a Service is found.
-  Found(Service service);
-
-  // Lost will be called when a service is lost.
-  Lost(string instanceId);
+  // Update will be called when a Service is found or lost.
+  Update(Update update);
 };
diff --git a/pubspec.yaml b/pubspec.yaml
index 079085f..726e4a4 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Discovery is a discovery system for developers that makes it easy to advertise apps and scan for them. It works over MDNS and BLE.
 homepage: https://github.com/vanadium/mojo.discovery
 name: v23discovery
-version: 0.0.10
+version: 0.0.11
 dependencies:
   mojo_sdk: 0.2.7
 dev_dependencies: