Merge "jni: Validate v.io/v23/security/access.AccessTagCaveat in Java"
diff --git a/impl/google/discovery/jni.go b/impl/google/discovery/jni.go
index 77dab52..666c9f1 100644
--- a/impl/google/discovery/jni.go
+++ b/impl/google/discovery/jni.go
@@ -7,14 +7,12 @@
 package discovery
 
 import (
-	"bytes"
-	"encoding/binary"
 	"unsafe"
 
-	"v.io/v23/context"
 	"v.io/v23/discovery"
 	"v.io/v23/security"
 	"v.io/v23/verror"
+
 	idiscovery "v.io/x/ref/lib/discovery"
 
 	jchannel "v.io/x/jni/impl/google/channel"
@@ -27,26 +25,15 @@
 import "C"
 
 var (
-	updateSign  = jutil.ClassSign("io.v.v23.discovery.Update")
-	contextSign = jutil.ClassSign("io.v.v23.context.VContext")
+	adIdSign          = jutil.ClassSign("io.v.v23.discovery.AdId")
+	advertisementSign = jutil.ClassSign("io.v.v23.discovery.Advertisement")
 
-	// Global reference for java.util.UUID class.
-	jUUIDClass jutil.Class
-
-	// Global reference io.v.impl.google.lib.discovery.ScanHandler
-	jScanHandlerClass jutil.Class
-
-	// Global reference io.v.v23.discovery.Service
-	jServiceClass jutil.Class
-
-	// Global reference io.v.v23.security.BlessingPattern
-	jBlessingPatternClass jutil.Class
-
-	// Global reference io.v.v23.discovery.Update
-	jUpdateClass jutil.Class
-
-	// Global reference io.v.impl.google.lib.discovery.VDiscoveryImpl
-	jVDiscoveryImplClass jutil.Class
+	jAdIdClass            jutil.Class // io.v.v23.discovery.AdId
+	jAdvertisementClass   jutil.Class // io.v.v23.discovery.Advertisement
+	jBlessingPatternClass jutil.Class // io.v.v23.security.BlessingPattern
+	jDiscoveryImplClass   jutil.Class // io.v.impl.google.lib.discovery.DiscoveryImpl
+	jUpdateImplClass      jutil.Class // io.v.impl.google.lib.discovery.UpdateImpl
+	jUUIDClass            jutil.Class // java.util.UUID
 )
 
 // Init initializes the JNI code with the given Java environment. This method
@@ -56,15 +43,11 @@
 	// necessary because JNI gets access to the class loader only in the system
 	// thread, so we aren't able to invoke FindClass in other threads.
 	var err error
-	jUUIDClass, err = jutil.JFindClass(env, "java/util/UUID")
+	jAdIdClass, err = jutil.JFindClass(env, "io/v/v23/discovery/AdId")
 	if err != nil {
 		return err
 	}
-	jScanHandlerClass, err = jutil.JFindClass(env, "io/v/impl/google/lib/discovery/ScanHandler")
-	if err != nil {
-		return err
-	}
-	jServiceClass, err = jutil.JFindClass(env, "io/v/v23/discovery/Service")
+	jAdvertisementClass, err = jutil.JFindClass(env, "io/v/v23/discovery/Advertisement")
 	if err != nil {
 		return err
 	}
@@ -72,159 +55,135 @@
 	if err != nil {
 		return err
 	}
-	jUpdateClass, err = jutil.JFindClass(env, "io/v/v23/discovery/Update")
+	jDiscoveryImplClass, err = jutil.JFindClass(env, "io/v/impl/google/lib/discovery/DiscoveryImpl")
 	if err != nil {
 		return err
 	}
-
-	jVDiscoveryImplClass, err = jutil.JFindClass(env, "io/v/impl/google/lib/discovery/VDiscoveryImpl")
+	jUpdateImplClass, err = jutil.JFindClass(env, "io/v/impl/google/lib/discovery/UpdateImpl")
 	if err != nil {
 		return err
 	}
-
+	jUUIDClass, err = jutil.JFindClass(env, "java/util/UUID")
+	if err != nil {
+		return err
+	}
 	return nil
 }
 
-func convertStringtoUUID(jenv *C.JNIEnv, _ C.jclass, jName C.jstring, generator func(string) idiscovery.Uuid) C.jobject {
+//export Java_io_v_impl_google_lib_discovery_UUIDUtil_serviceUUID
+func Java_io_v_impl_google_lib_discovery_UUIDUtil_serviceUUID(jenv *C.JNIEnv, _ C.jclass, jName C.jstring) C.jobject {
 	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
 	name := jutil.GoString(env, jutil.Object(uintptr(unsafe.Pointer(jName))))
-	uuid := generator(name)
-	buf := bytes.NewBuffer(uuid)
-	var high, low int64
-	binary.Read(buf, binary.BigEndian, &high)
-	binary.Read(buf, binary.BigEndian, &low)
-	jUUID, err := jutil.NewObject(env, jUUIDClass, []jutil.Sign{jutil.LongSign, jutil.LongSign}, high, low)
+	jUuid, err := javaUUID(env, idiscovery.NewServiceUUID(name))
 	if err != nil {
 		jutil.JThrowV(env, err)
 		return nil
 	}
-	return C.jobject(unsafe.Pointer(jUUID))
+	return C.jobject(unsafe.Pointer(jUuid))
 }
 
-//export Java_io_v_impl_google_lib_discovery_UUIDUtil_UUIDForInterfaceName
-func Java_io_v_impl_google_lib_discovery_UUIDUtil_UUIDForInterfaceName(jenv *C.JNIEnv, jclass C.jclass, jName C.jstring) C.jobject {
-	return convertStringtoUUID(jenv, jclass, jName, idiscovery.NewServiceUUID)
-}
-
-//export Java_io_v_impl_google_lib_discovery_UUIDUtil_UUIDForAttributeKey
-func Java_io_v_impl_google_lib_discovery_UUIDUtil_UUIDForAttributeKey(jenv *C.JNIEnv, jclass C.jclass, jName C.jstring) C.jobject {
-	converter := func(s string) idiscovery.Uuid {
-		return idiscovery.NewAttributeUUID(s)
-	}
-	return convertStringtoUUID(jenv, jclass, jName, converter)
-}
-
-func doAdvertise(ctx *context.T, ds discovery.T, trigger *idiscovery.Trigger, service discovery.Service, visibility []security.BlessingPattern, jService jutil.Object, jDoneCallback jutil.Object) (jutil.Object, error) {
-	// Blocking call, so don't call GetEnv() beforehand.
-	done, err := ds.Advertise(ctx, &service, visibility)
-	env, freeFunc := jutil.GetEnv()
-	defer freeFunc()
-	defer jutil.DeleteGlobalRef(env, jService)
-	if err != nil {
-		jutil.DeleteGlobalRef(env, jDoneCallback)
-		return jutil.NullObject, err
-	}
-	// Copy back service.InstanceId to jService since it's the only field that would be updated.
-	if err = jutil.CallVoidMethod(env, jService, "setInstanceId", []jutil.Sign{jutil.StringSign}, service.InstanceId); err != nil {
-		jutil.DeleteGlobalRef(env, jDoneCallback)
-		return jutil.NullObject, err
-	}
-	jContext, err := jcontext.JavaContext(env, ctx, nil)
-	if err != nil {
-		return jutil.NullObject, err
-	}
-	listenableFutureSign := jutil.ClassSign("com.google.common.util.concurrent.ListenableFuture")
-	jDoneFuture, err := jutil.CallObjectMethod(env, jDoneCallback, "getFuture", []jutil.Sign{contextSign}, listenableFutureSign, jContext)
-	if err != nil {
-		jutil.DeleteGlobalRef(env, jDoneCallback)
-		return jutil.NullObject, err
-	}
-	trigger.Add(func() {
-		env, freeFunc := jutil.GetEnv()
-		defer freeFunc()
-		jutil.CallbackOnSuccess(env, jDoneCallback, jutil.NullObject)
-		jutil.DeleteGlobalRef(env, jDoneCallback)
-	}, done)
-	// Must grab a global reference as we free up the env and all local references that come along
-	// with it.
-	return jutil.NewGlobalRef(env, jDoneFuture), nil // Un-refed in DoAsyncCall
-}
-
-//export Java_io_v_impl_google_lib_discovery_VDiscoveryImpl_nativeAdvertise
-func Java_io_v_impl_google_lib_discovery_VDiscoveryImpl_nativeAdvertise(jenv *C.JNIEnv, jDiscovery C.jobject, goDiscoveryPtr C.jlong, goTriggerPtr C.jlong, jContext C.jobject, jServiceObj C.jobject, jVisibilityObj C.jobject, jStartCallbackObj C.jobject, jDoneCallbackObj C.jobject) {
+//export Java_io_v_impl_google_lib_discovery_UUIDUtil_attributeUUID
+func Java_io_v_impl_google_lib_discovery_UUIDUtil_attributeUUID(jenv *C.JNIEnv, _ C.jclass, jName C.jstring) C.jobject {
 	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
-	jService := jutil.Object(uintptr(unsafe.Pointer(jServiceObj)))
+	name := jutil.GoString(env, jutil.Object(uintptr(unsafe.Pointer(jName))))
+	jUuid, err := javaUUID(env, idiscovery.NewAttributeUUID(name))
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return nil
+	}
+	return C.jobject(unsafe.Pointer(jUuid))
+}
+
+//export Java_io_v_impl_google_lib_discovery_DiscoveryImpl_nativeAdvertise
+func Java_io_v_impl_google_lib_discovery_DiscoveryImpl_nativeAdvertise(jenv *C.JNIEnv, _ C.jobject, dPtr C.jlong, jCtx C.jobject, jAdObj C.jobject, jVisibilityObj C.jobject, jCbObj C.jobject) {
+	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
+	ctx, _, err := jcontext.GoContext(env, jutil.Object(uintptr(unsafe.Pointer(jCtx))))
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return
+	}
+
+	d := *(*discovery.T)(jutil.NativePtr(dPtr))
+	jAd := jutil.Object(uintptr(unsafe.Pointer(jAdObj)))
 	jVisibility := jutil.Object(uintptr(unsafe.Pointer(jVisibilityObj)))
-	jStartCallback := jutil.Object(uintptr(unsafe.Pointer(jStartCallbackObj)))
-	jDoneCallback := jutil.Object(uintptr(unsafe.Pointer(jDoneCallbackObj)))
-	ctx, _, err := jcontext.GoContext(env, jutil.Object(uintptr(unsafe.Pointer(jContext))))
+
+	var ad discovery.Advertisement
+	if err := jutil.GoVomCopy(env, jAd, jAdvertisementClass, &ad); err != nil {
+		jutil.JThrowV(env, err)
+		return
+	}
+
+	jVisibilityList, err := jutil.GoObjectList(env, jVisibility)
 	if err != nil {
-		jutil.CallbackOnFailure(env, jStartCallback, err)
+		jutil.JThrowV(env, err)
 		return
 	}
-	var service discovery.Service
-	if err := jutil.GoVomCopy(env, jService, jServiceClass, &service); err != nil {
-		jutil.CallbackOnFailure(env, jStartCallback, err)
-		return
-	}
-	varr, err := jutil.GoObjectList(env, jVisibility)
-	if err != nil {
-		jutil.CallbackOnFailure(env, jStartCallback, err)
-		return
-	}
-	visibility := make([]security.BlessingPattern, len(varr))
-	for i, jPattern := range varr {
+	visibility := make([]security.BlessingPattern, len(jVisibilityList))
+	for i, jPattern := range jVisibilityList {
 		if err := jutil.GoVomCopy(env, jPattern, jBlessingPatternClass, &visibility[i]); err != nil {
-			jutil.CallbackOnFailure(env, jStartCallback, err)
+			jutil.JThrowV(env, err)
 			return
 		}
 	}
-	ds := *(*discovery.T)(jutil.NativePtr(goDiscoveryPtr))
-	trigger := (*idiscovery.Trigger)(jutil.NativePtr(goTriggerPtr))
-	jService = jutil.NewGlobalRef(env, jService)           // Un-refed in doAdvertise
-	jDoneCallback = jutil.NewGlobalRef(env, jDoneCallback) // Un-refed in doAdvertise
-	jutil.DoAsyncCall(env, jStartCallback, func() (jutil.Object, error) {
-		return doAdvertise(ctx, ds, trigger, service, visibility, jService, jDoneCallback)
+
+	done, err := d.Advertise(ctx, &ad, visibility)
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return
+	}
+
+	// Copy back the advertisement id.
+	jId, err := jutil.JVomCopy(env, &ad.Id, jAdIdClass)
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return
+	}
+	if err = jutil.CallVoidMethod(env, jAd, "setId", []jutil.Sign{adIdSign}, jId); err != nil {
+		jutil.JThrowV(env, err)
+		return
+	}
+
+	jCb := jutil.Object(uintptr(unsafe.Pointer(jCbObj)))
+	jutil.DoAsyncCall(env, jCb, func() (jutil.Object, error) {
+		<-done
+		return jutil.NullObject, nil
 	})
 }
 
-//export Java_io_v_impl_google_lib_discovery_VDiscoveryImpl_nativeScan
-func Java_io_v_impl_google_lib_discovery_VDiscoveryImpl_nativeScan(jenv *C.JNIEnv, jDiscovery C.jobject, goDiscoveryPtr C.jlong, jContext C.jobject, jQuery C.jstring) C.jobject {
+//export Java_io_v_impl_google_lib_discovery_DiscoveryImpl_nativeScan
+func Java_io_v_impl_google_lib_discovery_DiscoveryImpl_nativeScan(jenv *C.JNIEnv, _ C.jobject, dPtr C.jlong, jCtx C.jobject, jQuery C.jstring) C.jobject {
 	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
-	ctx, cancel, err := jcontext.GoContext(env, jutil.Object(uintptr(unsafe.Pointer(jContext))))
+	ctx, _, err := jcontext.GoContext(env, jutil.Object(uintptr(unsafe.Pointer(jCtx))))
 	if err != nil {
 		jutil.JThrowV(env, err)
 		return nil
 	}
-	query := jutil.GoString(env, jutil.Object(uintptr(unsafe.Pointer(jQuery))))
-	ds := *(*discovery.T)(jutil.NativePtr(goDiscoveryPtr))
 
-	var scanChannel <-chan discovery.Update
-	var scanError error
-	scanDone := make(chan bool)
-	go func() {
-		scanChannel, scanError = ds.Scan(ctx, query)
-		close(scanDone)
-	}()
-	jChannel, err := jchannel.JavaInputChannel(env, ctx, cancel, func() (jutil.Object, error) {
-		// A few blocking calls below - don't call GetEnv() before they complete.
-		<-scanDone
-		if scanError != nil {
-			return jutil.NullObject, scanError
-		}
-		update, ok := <-scanChannel
+	d := *(*discovery.T)(jutil.NativePtr(dPtr))
+	query := jutil.GoString(env, jutil.Object(uintptr(unsafe.Pointer(jQuery))))
+
+	scanCh, err := d.Scan(ctx, query)
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return nil
+	}
+
+	jChannel, err := jchannel.JavaInputChannel(env, ctx, nil, func() (jutil.Object, error) {
+		update, ok := <-scanCh
 		if !ok {
 			return jutil.NullObject, verror.NewErrEndOfFile(ctx)
 		}
+
 		env, freeFunc := jutil.GetEnv()
 		defer freeFunc()
-		jUpdate, err := jutil.JVomCopy(env, update, jUpdateClass)
+
+		jUpdate, err := javaUpdate(env, update)
 		if err != nil {
 			return jutil.NullObject, err
 		}
 		// Must grab a global reference as we free up the env and all local references that come
 		// along with it.
-		return jutil.NewGlobalRef(env, jUpdate), nil // Un-refed by InputChannelImpl_nativeRecv
+		return jutil.NewGlobalRef(env, jUpdate), nil // un-refed by InputChannelImpl_nativeRecv
 	})
 	if err != nil {
 		jutil.JThrowV(env, err)
@@ -233,8 +192,55 @@
 	return C.jobject(unsafe.Pointer(jChannel))
 }
 
-//export Java_io_v_impl_google_lib_discovery_VDiscoveryImpl_nativeFinalize
-func Java_io_v_impl_google_lib_discovery_VDiscoveryImpl_nativeFinalize(jenv *C.JNIEnv, jDiscovery C.jobject, goDiscoveryPtr C.jlong, goTriggerPtr C.jlong) {
-	jutil.GoUnref(jutil.NativePtr(goDiscoveryPtr))
-	jutil.GoUnref(jutil.NativePtr(goTriggerPtr))
+//export Java_io_v_impl_google_lib_discovery_DiscoveryImpl_nativeFinalize
+func Java_io_v_impl_google_lib_discovery_DiscoveryImpl_nativeFinalize(jenv *C.JNIEnv, _ C.jobject, dPtr C.jlong) {
+	jutil.GoUnref(jutil.NativePtr(dPtr))
+}
+
+//export Java_io_v_impl_google_lib_discovery_UpdateImpl_nativeAttachment
+func Java_io_v_impl_google_lib_discovery_UpdateImpl_nativeAttachment(jenv *C.JNIEnv, _ C.jobject, uPtr C.jlong, jCtx C.jobject, jName C.jstring, jCbObj C.jobject) {
+	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
+	ctx, _, err := jcontext.GoContext(env, jutil.Object(uintptr(unsafe.Pointer(jCtx))))
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return
+	}
+
+	update := *(*discovery.Update)(jutil.NativePtr(uPtr))
+	name := jutil.GoString(env, jutil.Object(uintptr(unsafe.Pointer(jName))))
+
+	jCb := jutil.Object(uintptr(unsafe.Pointer(jCbObj)))
+	jutil.DoAsyncCall(env, jCb, func() (jutil.Object, error) {
+		dataOrErr := <-update.Attachment(ctx, name)
+		if dataOrErr.Error != nil {
+			return jutil.NullObject, err
+		}
+
+		env, freeFunc := jutil.GetEnv()
+		defer freeFunc()
+
+		jData, err := jutil.JByteArray(env, dataOrErr.Data)
+		if err != nil {
+			return jutil.NullObject, err
+		}
+		// Must grab a global reference as we free up the env and all local references that come
+		// along with it.
+		return jutil.NewGlobalRef(env, jData), nil // Un-refed in DoAsyncCall
+	})
+}
+
+//export Java_io_v_impl_google_lib_discovery_UpdateImpl_nativeFinalize
+func Java_io_v_impl_google_lib_discovery_UpdateImpl_nativeFinalize(jenv *C.JNIEnv, _ C.jobject, uPtr C.jlong) {
+	jutil.GoUnref(jutil.NativePtr(uPtr))
+}
+
+//export Java_io_v_impl_google_lib_discovery_DiscoveryTestUtil_injectMockDiscovery
+func Java_io_v_impl_google_lib_discovery_DiscoveryTestUtil_injectMockDiscovery(jenv *C.JNIEnv, _ C.jclass, jCtx C.jobject) {
+	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
+	ctx, _, err := jcontext.GoContext(env, jutil.Object(uintptr(unsafe.Pointer(jCtx))))
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return
+	}
+	injectMockDiscovery(ctx)
 }
diff --git a/impl/google/discovery/plugins/jni.go b/impl/google/discovery/plugins/jni.go
new file mode 100644
index 0000000..7687b42
--- /dev/null
+++ b/impl/google/discovery/plugins/jni.go
@@ -0,0 +1,65 @@
+// Copyright 2016 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.
+
+// +build android
+
+package plugins
+
+import (
+	"unsafe"
+
+	idiscovery "v.io/x/ref/lib/discovery"
+
+	jutil "v.io/x/jni/util"
+)
+
+// #include "jni.h"
+import "C"
+
+var (
+	androidContextSign = jutil.ClassSign("android.content.Context")
+	adInfoSign         = jutil.ClassSign("io.v.x.ref.lib.discovery.AdInfo")
+	scanHandlerSign    = jutil.ClassSign("io.v.impl.google.lib.discovery.Plugin.ScanHandler")
+
+	jAdInfoClass            jutil.Class // io.v.x.ref.lib.discovery.AdInfo
+	jNativeScanHandlerClass jutil.Class // io.v.android.impl.google.discovery.plugins.NativeScanHandler
+	jBlePluginClass         jutil.Class // io.v.android.impl.google.discovery.plugins.ble.BlePlugin
+)
+
+func Init(env jutil.Env) error {
+	var err error
+	jAdInfoClass, err = jutil.JFindClass(env, "io/v/x/ref/lib/discovery/AdInfo")
+	if err != nil {
+		return err
+	}
+	jNativeScanHandlerClass, err = jutil.JFindClass(env, "io/v/android/impl/google/discovery/plugins/NativeScanHandler")
+	if err != nil {
+		return err
+	}
+	jBlePluginClass, err = jutil.JFindClass(env, "io/v/android/impl/google/discovery/plugins/ble/BlePlugin")
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+//export Java_io_v_android_impl_google_discovery_plugins_NativeScanHandler_nativeHandleUpdate
+func Java_io_v_android_impl_google_discovery_plugins_NativeScanHandler_nativeHandleUpdate(jenv *C.JNIEnv, _ C.jobject, chPtr C.jlong, jAdInfoObj C.jobject) {
+	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
+	ch := (*(*chan<- *idiscovery.AdInfo)(jutil.NativePtr(chPtr)))
+
+	jAdInfo := jutil.Object(uintptr(unsafe.Pointer(jAdInfoObj)))
+
+	var adInfo idiscovery.AdInfo
+	if err := jutil.GoVomCopy(env, jAdInfo, jAdInfoClass, &adInfo); err != nil {
+		jutil.JThrowV(env, err)
+		return
+	}
+	ch <- &adInfo
+}
+
+//export Java_io_v_android_impl_google_discovery_plugins_NativeScanHandler_nativeFinalize
+func Java_io_v_android_impl_google_discovery_plugins_NativeScanHandler_nativeFinalize(jenv *C.JNIEnv, _ C.jobject, chPtr C.jlong) {
+	jutil.GoUnref(jutil.NativePtr(chPtr))
+}
diff --git a/impl/google/discovery/plugins/plugin.go b/impl/google/discovery/plugins/plugin.go
new file mode 100644
index 0000000..06b1eb1
--- /dev/null
+++ b/impl/google/discovery/plugins/plugin.go
@@ -0,0 +1,120 @@
+// Copyright 2016 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.
+
+// +build android
+
+package plugins
+
+import (
+	"runtime"
+
+	"v.io/v23/context"
+
+	idiscovery "v.io/x/ref/lib/discovery"
+
+	jutil "v.io/x/jni/util"
+)
+
+// #include "jni.h"
+import "C"
+
+func NewBlePluginFactory(env jutil.Env, jAndroidContext jutil.Object) func(string) (idiscovery.Plugin, error) {
+	return newPluginFactory(env, jAndroidContext, jBlePluginClass)
+}
+
+type plugin struct {
+	jPlugin jutil.Object
+	trigger *idiscovery.Trigger
+}
+
+func (p *plugin) Advertise(ctx *context.T, adinfo *idiscovery.AdInfo, done func()) error {
+	env, freeFunc := jutil.GetEnv()
+	defer freeFunc()
+
+	jAdInfo, err := jutil.JVomCopy(env, adinfo, jAdInfoClass)
+	if err != nil {
+		done()
+		return err
+	}
+	err = jutil.CallVoidMethod(env, p.jPlugin, "startAdvertising", []jutil.Sign{adInfoSign}, jAdInfo)
+	if err != nil {
+		done()
+		return err
+	}
+
+	jAdInfo = jutil.NewGlobalRef(env, jAdInfo)
+	stop := func() {
+		env, freeFunc := jutil.GetEnv()
+		defer freeFunc()
+
+		jutil.CallVoidMethod(env, p.jPlugin, "stopAdvertising", []jutil.Sign{adInfoSign}, jAdInfo)
+		jutil.DeleteGlobalRef(env, jAdInfo)
+		done()
+	}
+	p.trigger.Add(stop, ctx.Done())
+	return nil
+}
+
+func (p *plugin) Scan(ctx *context.T, interfaceName string, ch chan<- *idiscovery.AdInfo, done func()) error {
+	env, freeFunc := jutil.GetEnv()
+	defer freeFunc()
+
+	jNativeScanHandler, err := jutil.NewObject(env, jNativeScanHandlerClass, []jutil.Sign{jutil.LongSign}, int64(jutil.PtrValue(&ch)))
+	if err != nil {
+		done()
+		return err
+	}
+	err = jutil.CallVoidMethod(env, p.jPlugin, "startScan", []jutil.Sign{jutil.StringSign, scanHandlerSign}, interfaceName, jNativeScanHandler)
+	if err != nil {
+		done()
+		return err
+	}
+
+	jutil.GoRef(&ch) // Will be unrefed when jNativeScanHandler is finalized.
+	jNativeScanHandler = jutil.NewGlobalRef(env, jNativeScanHandler)
+	stop := func() {
+		env, freeFunc := jutil.GetEnv()
+		defer freeFunc()
+
+		jutil.CallVoidMethod(env, p.jPlugin, "stopScan", []jutil.Sign{scanHandlerSign}, jNativeScanHandler)
+		jutil.DeleteGlobalRef(env, jNativeScanHandler)
+		done()
+	}
+	p.trigger.Add(stop, ctx.Done())
+	return nil
+}
+
+func newPluginFactory(env jutil.Env, jAndroidContext jutil.Object, jPluginClass jutil.Class) func(string) (idiscovery.Plugin, error) {
+	// Reference Android Context; it will be de-referenced when the factory
+	// is garbage-collected.
+	jAndroidContext = jutil.NewGlobalRef(env, jAndroidContext)
+	factory := func(host string) (idiscovery.Plugin, error) {
+		env, freeFunc := jutil.GetEnv()
+		defer freeFunc()
+
+		jHost := jutil.JString(env, host)
+		jPlugin, err := jutil.NewObject(env, jPluginClass, []jutil.Sign{jutil.StringSign, androidContextSign}, jHost, jAndroidContext)
+		if err != nil {
+			return nil, err
+		}
+		// Reference Plugin; it will be de-referenced when the plugin is garbage-collected.
+		jPlugin = jutil.NewGlobalRef(env, jPlugin)
+		p := &plugin{
+			jPlugin: jPlugin,
+			trigger: idiscovery.NewTrigger(),
+		}
+		runtime.SetFinalizer(p, func(*plugin) {
+			env, freeFunc := jutil.GetEnv()
+			jutil.DeleteGlobalRef(env, p.jPlugin)
+			freeFunc()
+		})
+		return p, nil
+	}
+	runtime.SetFinalizer(&factory, func(*func(string) (idiscovery.Plugin, error)) {
+		env, freeFunc := jutil.GetEnv()
+		defer freeFunc()
+		jutil.DeleteGlobalRef(env, jAndroidContext)
+	})
+	return factory
+}
diff --git a/impl/google/discovery/util.go b/impl/google/discovery/util.go
index 65566d9..b776ea8 100644
--- a/impl/google/discovery/util.go
+++ b/impl/google/discovery/util.go
@@ -7,25 +7,58 @@
 package discovery
 
 import (
-	jutil "v.io/x/jni/util"
+	"bytes"
+	"encoding/binary"
 
+	"v.io/v23/context"
 	"v.io/v23/discovery"
-	idiscovery "v.io/x/ref/lib/discovery"
-)
 
-// #include "jni.h"
-import "C"
+	idiscovery "v.io/x/ref/lib/discovery"
+	fdiscovery "v.io/x/ref/lib/discovery/factory"
+	"v.io/x/ref/lib/discovery/plugins/mock"
+
+	jutil "v.io/x/jni/util"
+)
 
 // JavaDiscovery converts a Go discovery instance into a Java discovery instance.
 func JavaDiscovery(env jutil.Env, d discovery.T) (jutil.Object, error) {
-	trigger := idiscovery.NewTrigger()
-	// These reference will get unrefed when the jDiscovery object below is finalized.
-	jutil.GoRef(trigger)
-	jutil.GoRef(&d)
-
-	jDiscovery, err := jutil.NewObject(env, jVDiscoveryImplClass, []jutil.Sign{jutil.LongSign, jutil.LongSign}, int64(jutil.PtrValue(&d)), int64(jutil.PtrValue(trigger)))
+	jDiscovery, err := jutil.NewObject(env, jDiscoveryImplClass, []jutil.Sign{jutil.LongSign}, int64(jutil.PtrValue(&d)))
 	if err != nil {
 		return jutil.NullObject, err
 	}
+	jutil.GoRef(&d) // Will be unrefed when jDiscovery is finalized.
 	return jDiscovery, nil
 }
+
+// javaUpdate converts a Go update instance into a Java update instance.
+func javaUpdate(env jutil.Env, update discovery.Update) (jutil.Object, error) {
+	jAd, err := jutil.JVomCopy(env, update.Advertisement(), jAdvertisementClass)
+	if err != nil {
+		return jutil.NullObject, err
+	}
+	jUpdate, err := jutil.NewObject(env, jUpdateImplClass, []jutil.Sign{jutil.LongSign, jutil.BoolSign, advertisementSign}, int64(jutil.PtrValue(&update)), update.IsLost(), jAd)
+	if err != nil {
+		return jutil.NullObject, err
+	}
+	jutil.GoRef(&update) // Will be unrefed when jUpdate is finalized.
+	return jUpdate, nil
+}
+
+// javaUUID converts a Go UUID into a Java UUID instance.
+func javaUUID(env jutil.Env, uuid idiscovery.Uuid) (jutil.Object, error) {
+	var high, low int64
+	buf := bytes.NewReader(uuid)
+	binary.Read(buf, binary.BigEndian, &high)
+	binary.Read(buf, binary.BigEndian, &low)
+	return jutil.NewObject(env, jUUIDClass, []jutil.Sign{jutil.LongSign, jutil.LongSign}, high, low)
+}
+
+// injectMockDiscovery injects a discovery instance with a mock plugin into a runtime.
+func injectMockDiscovery(ctx *context.T) error {
+	df, err := idiscovery.NewFactory(ctx, mock.New())
+	if err != nil {
+		return err
+	}
+	fdiscovery.InjectFactory(df)
+	return nil
+}
diff --git a/jni_android.go b/jni_android.go
index d853f68..3c87303 100644
--- a/jni_android.go
+++ b/jni_android.go
@@ -10,10 +10,10 @@
 	"unsafe"
 
 	"v.io/x/lib/vlog"
-	"v.io/x/ref/lib/discovery/factory"
+	dfactory "v.io/x/ref/lib/discovery/factory"
 	_ "v.io/x/ref/runtime/factories/android"
 
-	jdiscovery "v.io/x/jni/libs/discovery"
+	jdplugins "v.io/x/jni/impl/google/discovery/plugins"
 	jutil "v.io/x/jni/util"
 )
 
@@ -35,10 +35,10 @@
 	// This assumes that vlog.Log is the underlying logging system for.
 	vlog.Log.Configure(vlog.OverridePriorConfiguration(true), vlog.LogToStderr(false), vlog.AlsoLogToStderr(false), level, vmodule)
 
-	// Setup discovery.
-	if err := jdiscovery.Init(env); err != nil {
+	// Setup discovery plugins.
+	if err := jdplugins.Init(env); err != nil {
 		jutil.JThrowV(env, err)
 		return
 	}
-	factory.SetBleFactory(jdiscovery.NewBleCreator(env, jutil.Object(uintptr(unsafe.Pointer(jAndroidContext)))))
+	dfactory.SetBleFactory(jdplugins.NewBlePluginFactory(env, jutil.Object(uintptr(unsafe.Pointer(jAndroidContext)))))
 }
diff --git a/libs/discovery/jni.go b/libs/discovery/jni.go
deleted file mode 100644
index b9a1c27..0000000
--- a/libs/discovery/jni.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build android
-
-package discovery
-
-import (
-	"unsafe"
-
-	jutil "v.io/x/jni/util"
-	"v.io/x/ref/lib/discovery"
-)
-
-// #include "jni.h"
-import "C"
-
-var (
-	androidContextSign = jutil.ClassSign("android.content.Context")
-	contextSign        = jutil.ClassSign("io.v.v23.context.VContext")
-	advertisementSign  = jutil.ClassSign("io.v.x.ref.lib.discovery.Advertisement")
-	scanHandlerSign    = jutil.ClassSign("io.v.impl.google.lib.discovery.ScanHandler")
-
-	// Global reference for io.v.x.ref.lib.discovery.Advertisement
-	jAdvertisementClass jutil.Class
-	// Global reference for io.v.android.libs.discovery.ble.BlePlugin
-	jBlePluginClass jutil.Class
-	// Global reference for io.v.android.libs.discovery.ble.NativeScanHandler
-	jNativeScanHandlerClass jutil.Class
-)
-
-func Init(env jutil.Env) error {
-	var err error
-
-	jAdvertisementClass, err = jutil.JFindClass(env, "io/v/x/ref/lib/discovery/Advertisement")
-	if err != nil {
-		return err
-	}
-	jBlePluginClass, err = jutil.JFindClass(env, "io/v/android/libs/discovery/ble/BlePlugin")
-	if err != nil {
-		return err
-	}
-	jNativeScanHandlerClass, err = jutil.JFindClass(env, "io/v/android/libs/discovery/ble/NativeScanHandler")
-	return err
-}
-
-//export Java_io_v_android_libs_discovery_ble_NativeScanHandler_nativeHandleUpdate
-func Java_io_v_android_libs_discovery_ble_NativeScanHandler_nativeHandleUpdate(jenv *C.JNIEnv, _ C.jobject, jAdvObj C.jobject, goPtr C.jlong) {
-	env := jutil.Env(uintptr(unsafe.Pointer(jenv)))
-	ch := (*(*chan<- discovery.Advertisement)(jutil.NativePtr(goPtr)))
-
-	jAdv := jutil.Object(uintptr(unsafe.Pointer(jAdvObj)))
-	var adv discovery.Advertisement
-	if err := jutil.GoVomCopy(env, jAdv, jAdvertisementClass, &adv); err != nil {
-		jutil.JThrowV(env, err)
-		return
-	}
-	ch <- adv
-}
-
-//export Java_io_v_android_libs_discovery_ble_NativeScanHandler_nativeFinalize
-func Java_io_v_android_libs_discovery_ble_NativeScanHandler_nativeFinalize(jenv *C.JNIEnv, _ C.jobject, goPtr C.jlong) {
-	jutil.GoUnref(jutil.NativePtr(goPtr))
-}
diff --git a/libs/discovery/plugin.go b/libs/discovery/plugin.go
deleted file mode 100644
index 22f7fbb..0000000
--- a/libs/discovery/plugin.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build android
-
-package discovery
-
-import (
-	"runtime"
-
-	"v.io/v23/context"
-
-	"v.io/x/ref/lib/discovery"
-
-	jutil "v.io/x/jni/util"
-	jcontext "v.io/x/jni/v23/context"
-)
-
-// #include "jni.h"
-import "C"
-
-func NewBleCreator(env jutil.Env, context jutil.Object) func(string) (discovery.Plugin, error) {
-	// Reference Android Context; it will be de-referenced when the plugin
-	// created below is garbage-collected (through the finalizer callback we
-	// setup in the function below).  This function should only be executed once.
-	jContext := jutil.NewGlobalRef(env, context)
-	return func(host string) (discovery.Plugin, error) {
-		env, freeFunc := jutil.GetEnv()
-		defer freeFunc()
-		jPlugin, err := jutil.NewObject(env, jBlePluginClass, []jutil.Sign{androidContextSign}, jContext)
-
-		jutil.DeleteGlobalRef(env, jContext)
-		if err != nil {
-			return nil, err
-		}
-		// Reference Android BlePlugin; it will be de-referenced when the plugin
-		// created below is garbage-collected (through the finalizer callback we
-		// setup below).
-		jPlugin = jutil.NewGlobalRef(env, jPlugin)
-		p := &plugin{
-			trigger: discovery.NewTrigger(),
-			jPlugin: jPlugin,
-		}
-		runtime.SetFinalizer(p, func(p *plugin) {
-			env, freeFunc := jutil.GetEnv()
-			defer freeFunc()
-			jutil.DeleteGlobalRef(env, p.jPlugin)
-		})
-		return p, nil
-	}
-
-}
-
-type plugin struct {
-	trigger *discovery.Trigger
-	jPlugin jutil.Object
-}
-
-func (p *plugin) Advertise(ctx *context.T, ad discovery.Advertisement, done func()) error {
-	env, freeFunc := jutil.GetEnv()
-	defer freeFunc()
-	jContext, err := jcontext.JavaContext(env, ctx, nil)
-	if err != nil {
-		return err
-	}
-	jAdv, err := jutil.JVomCopy(env, ad, jAdvertisementClass)
-	if err != nil {
-		return err
-	}
-
-	err = jutil.CallVoidMethod(env, p.jPlugin, "addAdvertisement", []jutil.Sign{contextSign, advertisementSign}, jContext, jAdv)
-
-	p.trigger.Add(done, ctx.Done())
-
-	return err
-}
-
-func (p *plugin) Scan(ctx *context.T, interfaceName string, ch chan<- discovery.Advertisement, done func()) error {
-	env, freeFunc := jutil.GetEnv()
-	defer freeFunc()
-	jContext, err := jcontext.JavaContext(env, ctx, nil)
-	if err != nil {
-		return err
-	}
-
-	jutil.GoRef(&ch)
-	jNativeScanHandler, err := jutil.NewObject(env, jNativeScanHandlerClass, []jutil.Sign{jutil.LongSign}, int64(jutil.PtrValue(&ch)))
-	if err != nil {
-		return err
-	}
-
-	err = jutil.CallVoidMethod(env, p.jPlugin, "addScanner", []jutil.Sign{contextSign, jutil.StringSign, scanHandlerSign}, jContext, interfaceName, jNativeScanHandler)
-	if err != nil {
-		return err
-	}
-	p.trigger.Add(done, ctx.Done())
-	return nil
-}