veyron/runtimes/google/ipc/jni: Add streaming support to Java Veyron Runtime.

Change-Id: I6cf0058da5c6f44209d397e49c4e12c4776dc790
diff --git a/runtimes/google/ipc/jni/arg_getter.go b/runtimes/google/ipc/jni/arg_getter.go
index 25c2003..7bd27c5 100644
--- a/runtimes/google/ipc/jni/arg_getter.go
+++ b/runtimes/google/ipc/jni/arg_getter.go
@@ -1,14 +1,11 @@
-// +build android
-
 package main
 
 import (
 	"fmt"
 	"path"
 	"reflect"
-	"strings"
 
-	// Imported IDLs.  Please add a link to all IDLs you care about here,
+	// Imported VDLs.  Please add a link to all VDLs you care about here,
 	// and add all interfaces you care about to the init() function below.
 	"veyron/examples/fortune"
 	"veyron2/ipc"
@@ -22,7 +19,7 @@
 // A list of all registered argGetter-s.
 var register map[string]*argGetter = make(map[string]*argGetter)
 
-// registerInterface registers the provided IDL client or server interface
+// registerInterface registers the provided VDL client or server interface
 // so that its methods' arguments can be created on-the-fly.
 func registerInterface(ifacePtr interface{}) {
 	t := reflect.TypeOf(ifacePtr)
@@ -34,137 +31,207 @@
 		panic(fmt.Sprintf("expected interface type for %q, got: %v", ifacePtr, t.Kind()))
 	}
 
+	contextType := reflect.TypeOf((*ipc.Context)(nil)).Elem()
+	optType := reflect.TypeOf(([]ipc.ClientOpt)(nil))
 	// Create a new arg getter.
-	methods := make(map[string][]methodInfo)
+	methods := make(map[string][]*methodArgs)
 	for i := 0; i < t.NumMethod(); i++ {
 		m := t.Method(i)
-		in := make([]reflect.Type, m.Type.NumIn()-1)
-		idx := 0
-		contextType := reflect.TypeOf((*ipc.ServerContext)(nil)).Elem()
-		optType := reflect.TypeOf((*ipc.CallOpt)(nil)).Elem()
+		var mArgs methodArgs
 		for j := 0; j < m.Type.NumIn(); j++ {
 			argType := m.Type.In(j)
-			if j == 0 && argType == contextType { // skip the Context argument.
+			if j == 0 && argType == contextType { // (service) Context argument - ignore it.
 				continue
 			}
-			if j == m.Type.NumIn()-1 && argType == optType { // skip the CallOption argument.
-				continue
+			if j == m.Type.NumIn()-1 {
+				if argType.Kind() == reflect.Interface { // (service) stream argument.
+					if err := fillStreamArgs(argType, &mArgs); err != nil {
+						panic(err.Error())
+					}
+					continue
+				}
+				if argType == optType { // (client) CallOption argument - ignore it.
+					continue
+				}
 			}
-			in[idx] = argType
-			idx++
+			mArgs.inTypes = append(mArgs.inTypes, argType)
 		}
-		out := make([]reflect.Type, m.Type.NumOut()-1) // skip error argument
 		for j := 0; j < m.Type.NumOut()-1; j++ {
-			out[j] = m.Type.Out(j)
+			argType := m.Type.Out(j)
+			if j == 0 && argType.Kind() == reflect.Interface { // (client) stream argument
+				if err := fillStreamArgs(argType, &mArgs); err != nil {
+					panic(err.Error())
+				}
+				continue
+			}
+			mArgs.outTypes = append(mArgs.outTypes, argType)
 		}
-		mis := methods[m.Name]
-		mis = append(mis, methodInfo{
-			inTypes:  in,
-			outTypes: out,
-		})
-		methods[m.Name] = mis
+		methods[m.Name] = append(methods[m.Name], &mArgs)
 	}
 	path := path.Join(t.PkgPath(), t.Name())
 	register[path] = &argGetter{
 		methods: methods,
-		idlPath: path,
+		vdlPath: path,
 	}
 }
 
-// newPtrInstance returns the pointer to the new instance of the provided type.
-func newPtrInstance(t reflect.Type) interface{} {
-	return reflect.New(t).Interface()
+// fillStreamArgs fills in stream argument types for the provided stream.
+func fillStreamArgs(stream reflect.Type, mArgs *methodArgs) error {
+	// Get the stream send type.
+	if mSend, ok := stream.MethodByName("Send"); ok {
+		if mSend.Type.NumIn() != 1 {
+			return fmt.Errorf("Illegal number of arguments for Send method in stream %v", stream)
+		}
+		mArgs.streamSendType = mSend.Type.In(0)
+	}
+	// Get the stream recv type.
+	if mRecv, ok := stream.MethodByName("Recv"); ok {
+		if mRecv.Type.NumOut() != 2 {
+			return fmt.Errorf("Illegal number of arguments for Recv method in stream %v", stream)
+		}
+		mArgs.streamRecvType = mRecv.Type.Out(0)
+	}
+	if mArgs.streamSendType == nil && mArgs.streamRecvType == nil {
+		return fmt.Errorf("Both stream in and out arguments cannot be nil in stream %v", stream)
+	}
+	// Get the stream finish types.
+	if mFinish, ok := stream.MethodByName("Finish"); ok && mFinish.Type.NumOut() > 1 {
+		for i := 0; i < mFinish.Type.NumOut()-1; i++ {
+			mArgs.streamFinishTypes = append(mArgs.streamFinishTypes, mFinish.Type.Out(i))
+		}
+	}
+	return nil
 }
 
-// newArgGetter returns the argument getter for the provided IDL interface.
-func newArgGetter(javaIdlIfacePath string) *argGetter {
-	return register[strings.Join(strings.Split(javaIdlIfacePath, ".")[1:], "/")]
+// newArgGetter returns the argument getter for the provided VDL interface.
+func newArgGetter(vdlIfacePath string) *argGetter {
+	return register[vdlIfacePath]
 }
 
 // argGetter serves method arguments for a specific interface.
 type argGetter struct {
-	methods map[string][]methodInfo
-	idlPath string
+	methods map[string][]*methodArgs
+	vdlPath string
 }
 
-// methodInfo contains argument type information for a method belonging to an interface.
-type methodInfo struct {
-	inTypes  []reflect.Type
-	outTypes []reflect.Type
-}
-
-func (m methodInfo) String() string {
-	in := fmt.Sprintf("[%d]", len(m.inTypes))
-	out := fmt.Sprintf("[%d]", len(m.outTypes))
-	for _, t := range m.inTypes {
-		in = in + ", " + t.Name()
+func (ag *argGetter) String() (ret string) {
+	ret = "VDLPath: " + ag.vdlPath
+	for k, v := range ag.methods {
+		for _, m := range v {
+			ret += "; "
+			ret += fmt.Sprintf("Method: %s, Args: %v", k, m)
+		}
 	}
-	for _, t := range m.outTypes {
-		out = out + ", " + t.Name()
-	}
-	return fmt.Sprintf("(%s; %s)", in, out)
+	return
 }
 
-// findMethod returns the method type information for the given method, or nil if
+// FindMethod returns the method type information for the given method, or nil if
 // the method doesn't exist.
-func (ag *argGetter) findMethod(method string, numInArgs int) *methodInfo {
+func (ag *argGetter) FindMethod(method string, numInArgs int) *methodArgs {
 	ms, ok := ag.methods[method]
 	if !ok {
 		return nil
 	}
-	var m *methodInfo
+	var m *methodArgs
 	for _, mi := range ms {
 		if len(mi.inTypes) == numInArgs {
-			m = &mi
+			m = mi
 			break
 		}
 	}
 	return m
 }
 
-// GetInArgTypes returns types of all input arguments for the given method.
-func (ag *argGetter) GetInArgTypes(method string, numInArgs int) ([]reflect.Type, error) {
-	m := ag.findMethod(method, numInArgs)
-	if m == nil {
-		return nil, fmt.Errorf("couldn't find method %q with %d args in path %s", method, numInArgs, ag.idlPath)
-	}
-	return m.inTypes, nil
+// argGetters is a cache of created argument getters, keyed by VDL interface path.
+var argGetters map[string]*argGetter = make(map[string]*argGetter)
+
+// method contains argument type information for a method belonging to an interface.
+type methodArgs struct {
+	inTypes           []reflect.Type
+	outTypes          []reflect.Type
+	streamSendType    reflect.Type
+	streamRecvType    reflect.Type
+	streamFinishTypes []reflect.Type
 }
 
-// GenInArgPtrs returns pointers to instances of all input arguments for the given method.
-func (ag *argGetter) GetInArgPtrs(method string, numInArgs int) (argptrs []interface{}, err error) {
-	m := ag.findMethod(method, numInArgs)
-	if m == nil {
-		return nil, fmt.Errorf("couldn't find method %q with %d args in path %s", method, numInArgs, ag.idlPath)
+func (m *methodArgs) String() string {
+	in := fmt.Sprintf("[%d]", len(m.inTypes))
+	out := fmt.Sprintf("[%d]", len(m.outTypes))
+	streamFinish := fmt.Sprintf("[%d]", len(m.streamFinishTypes))
+	streamSend := "<nil>"
+	streamRecv := "<nil>"
+	for idx, t := range m.inTypes {
+		if idx > 0 {
+			in += ", "
+		}
+		in += t.Name()
 	}
-	argptrs = make([]interface{}, len(m.inTypes))
+	for idx, t := range m.outTypes {
+		if idx > 0 {
+			out += ", "
+		}
+		out += t.Name()
+	}
+	if m.streamSendType != nil {
+		streamSend = m.streamSendType.Name()
+	}
+	if m.streamRecvType != nil {
+		streamRecv = m.streamRecvType.Name()
+	}
+	for idx, t := range m.streamFinishTypes {
+		if idx > 0 {
+			streamFinish += ", "
+		}
+		streamFinish += t.Name()
+	}
+	return fmt.Sprintf("(In: %s; Out: %s; streamSend: %s; StreamRecv: %s; StreamFinish: %s)", in, out, streamSend, streamRecv, streamFinish)
+}
+
+func (m *methodArgs) IsStreaming() bool {
+	return m.streamSendType != nil || m.streamRecvType != nil
+}
+
+// GenInPtrs returns pointers to instances of all input arguments.
+func (m *methodArgs) InPtrs() []interface{} {
+	argptrs := make([]interface{}, len(m.inTypes))
 	for i, arg := range m.inTypes {
 		argptrs[i] = newPtrInstance(arg)
 	}
-	return
+	return argptrs
 }
 
-// GetOurArgTypes returns types of all output arguments for the given method.
-func (ag *argGetter) GetOutArgTypes(method string, numInArgs int) ([]reflect.Type, error) {
-	m := ag.findMethod(method, numInArgs)
-	if m == nil {
-		return nil, fmt.Errorf("couldn't find method %q with %d args in path %s", method, numInArgs, ag.idlPath)
-	}
-	return m.outTypes, nil
-}
-
-// GetOutArgPtrs returns pointers to instances of all output arguments for the given method.
-func (ag *argGetter) GetOutArgPtrs(method string, numInArgs int) (argptrs []interface{}, err error) {
-	m := ag.findMethod(method, numInArgs)
-	if m == nil {
-		return nil, fmt.Errorf("couldn't find method %q with %d args in path %s", method, numInArgs, ag.idlPath)
-	}
-	argptrs = make([]interface{}, len(m.outTypes))
+// OutPtrs returns pointers to instances of all output arguments.
+func (m *methodArgs) OutPtrs() []interface{} {
+	argptrs := make([]interface{}, len(m.outTypes))
 	for i, arg := range m.outTypes {
 		argptrs[i] = newPtrInstance(arg)
 	}
-	return
+	return argptrs
 }
 
-// argGetters is a cache of created argument getters, keyed by IDL interface path.
-var argGetters map[string]*argGetter = make(map[string]*argGetter)
+// StreamSendPtr returns a pointer to an instance of a stream send type.
+func (m *methodArgs) StreamSendPtr() interface{} {
+	return newPtrInstance(m.streamSendType)
+}
+
+// StreamRecvPtr returns a pointer to an instance of a stream recv type.
+func (m *methodArgs) StreamRecvPtr() interface{} {
+	return newPtrInstance(m.streamRecvType)
+}
+
+// StreamFinishPtrs returns pointers to instances of stream finish types.
+func (m *methodArgs) StreamFinishPtrs() []interface{} {
+	argptrs := make([]interface{}, len(m.streamFinishTypes))
+	for i, arg := range m.streamFinishTypes {
+		argptrs[i] = newPtrInstance(arg)
+	}
+	return argptrs
+}
+
+// newPtrInstance returns the pointer to the new instance of the provided type.
+func newPtrInstance(t reflect.Type) interface{} {
+	if t == nil {
+		return nil
+	}
+	return reflect.New(t).Interface()
+}
diff --git a/runtimes/google/ipc/jni/arg_getter_test.go b/runtimes/google/ipc/jni/arg_getter_test.go
new file mode 100644
index 0000000..4694ab6
--- /dev/null
+++ b/runtimes/google/ipc/jni/arg_getter_test.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+	"reflect"
+	"testing"
+
+	"veyron2/vdl/test_base"
+)
+
+func init() {
+	registerInterface((*test_base.ServiceB)(nil))
+}
+
+func compareType(t *testing.T, method string, got, want interface{}, argKind string) {
+	if gotT, wantT := reflect.TypeOf(got), reflect.TypeOf(want); gotT != wantT {
+		t.Errorf("type mismatch in %q's %s argument: got %v , want %v ", method, argKind, gotT, wantT)
+	}
+}
+
+func compareTypes(t *testing.T, method string, got, want []interface{}, argKind string) {
+	if len(got) != len(want) {
+		 t.Errorf("mismatch in input arguments: got %v , want %v ", got, want)
+	}
+	for i, _ := range got {
+		compareType(t, method, got[i], want[i], argKind)
+	}
+}
+
+func TestGetter(t *testing.T) {
+	iface := "veyron2/vdl/test_base/ServiceB"
+	getter := newArgGetter(iface)
+	if getter == nil {
+		t.Fatalf("no getter for interface: %v ", iface)
+	}
+	if got, want := getter.vdlPath, iface; got != want {
+		t.Errorf("invalid pathname: got %v , want %v ", got, want)
+	}
+	data := []struct{
+		Method string
+		NumInArgs int
+		in, out []interface{}
+		sSend, sRecv interface{}
+		sFinish []interface{}
+	}{
+		{"MethodA1", 0, nil, nil, nil, nil, nil},
+		{"MethodA2", 2, []interface{}{(*int32)(nil), (*string)(nil)}, []interface{}{(*string)(nil)}, nil, nil, nil},
+		{"MethodA3", 1, []interface{}{(*int32)(nil)}, nil, nil, (*test_base.Scalars)(nil), []interface{}{(*string)(nil)}},
+		{"MethodA4", 1, []interface{}{(*int32)(nil)}, nil, (*int32)(nil), (*string)(nil), nil},
+		{"MethodB1", 2, []interface{}{(*test_base.Scalars)(nil), (*test_base.Composites)(nil)}, []interface{}{(*test_base.CompComp)(nil)}, nil, nil, nil},
+	}
+	for _, d := range data {
+		m := getter.FindMethod(d.Method, d.NumInArgs)
+		if m == nil {
+			t.Errorf("couldn't find method %q with %d args", d.Method, d.NumInArgs)
+			continue
+		}
+		// Compare arguments.
+		compareTypes(t, d.Method, m.InPtrs(), d.in, "input")
+		compareTypes(t, d.Method, m.OutPtrs(), d.out, "output")
+		compareType(t, d.Method, m.StreamSendPtr(), d.sSend, "stream send")
+		compareType(t, d.Method, m.StreamRecvPtr(), d.sRecv, "stream recv")
+		compareTypes(t, d.Method, m.StreamFinishPtrs(), d.sFinish, "stream finish")
+	}
+}
\ No newline at end of file
diff --git a/runtimes/google/ipc/jni/client.go b/runtimes/google/ipc/jni/client.go
index 10e4474..571f8ae 100644
--- a/runtimes/google/ipc/jni/client.go
+++ b/runtimes/google/ipc/jni/client.go
@@ -5,11 +5,11 @@
 import (
 	"encoding/json"
 	"fmt"
+	"strings"
 	"time"
 
 	"veyron2"
 	"veyron2/ipc"
-	"veyron2/rt"
 )
 
 // #include <stdlib.h>
@@ -20,17 +20,13 @@
 	client ipc.Client
 }
 
-func newClient() (*client, error) {
-	c, err := rt.R().NewClient()
-	if err != nil {
-		return nil, err
-	}
+func newClient(c ipc.Client) *client {
 	return &client{
 		client: c,
-	}, nil
+	}
 }
 
-func (c *client) StartCall(env *C.JNIEnv, name, method string, jArgs C.jobjectArray, jPath C.jstring, jTimeout C.jlong) (*clientCall, error) {
+func (c *client) StartCall(env *C.JNIEnv, jContext C.jobject, name, method string, jArgs C.jobjectArray, jPath C.jstring, jTimeout C.jlong) (*clientCall, error) {
 	// NOTE(spetrovic): In the long-term, we will decode JSON arguments into an
 	// array of vom.Value instances and send this array across the wire.
 
@@ -40,11 +36,15 @@
 		argStrs[i] = goString(env, C.jstring(C.GetObjectArrayElement(env, jArgs, C.jsize(i))))
 	}
 	// Get argument instances that correspond to the provided method.
-	getter := newArgGetter(goString(env, jPath))
+	getter := newArgGetter(strings.Join(strings.Split(goString(env, jPath), ".")[1:], "/"))
 	if getter == nil {
 		return nil, fmt.Errorf("couldn't find IDL interface corresponding to path %q", goString(env, jPath))
 	}
-	argptrs, err := getter.GetInArgPtrs(method, len(argStrs))
+	mArgs := getter.FindMethod(method, len(argStrs))
+	if mArgs == nil {
+		return nil, fmt.Errorf("couldn't find method %s with %d args in IDL interface at path %q, getter: %v", method, len(argStrs), goString(env, jPath), getter)
+	}
+	argptrs := mArgs.InPtrs()
 	if len(argptrs) != len(argStrs) {
 		return nil, fmt.Errorf("invalid number of arguments for method %s, want %d, have %d", method, len(argStrs), len(argptrs))
 	}
@@ -63,18 +63,18 @@
 	if int(jTimeout) >= 0 {
 		options = append(options, veyron2.CallTimeout(time.Duration(int(jTimeout))*time.Millisecond))
 	}
-	// Invoke StartCall
-	call, err := c.client.StartCall(name, method, args, options...)
+	context, err := newContext(env, jContext)
 	if err != nil {
 		return nil, err
 	}
-	resultptrs, err := getter.GetOutArgPtrs(method, len(argStrs))
+	// Invoke StartCall
+	call, err := c.client.StartCall(context, name, method, args, options...)
 	if err != nil {
 		return nil, err
 	}
 	return &clientCall{
-		call:       call,
-		resultptrs: resultptrs,
+		stream: newStream(call, mArgs),
+		call:   call,
 	}, nil
 }
 
@@ -83,24 +83,29 @@
 }
 
 type clientCall struct {
-	call       ipc.Call
-	resultptrs []interface{}
+	stream
+	call ipc.Call
 }
 
 func (c *clientCall) Finish(env *C.JNIEnv) (C.jobjectArray, error) {
+	var resultptrs []interface{}
+	if c.mArgs.IsStreaming() {
+		resultptrs = c.mArgs.StreamFinishPtrs()
+	} else {
+		resultptrs = c.mArgs.OutPtrs()
+	}
 	// argGetter doesn't store the (mandatory) error result, so we add it here.
 	var appErr error
-	if err := c.call.Finish(append(c.resultptrs, &appErr)...); err != nil {
+	if err := c.call.Finish(append(resultptrs, &appErr)...); err != nil {
 		// invocation error
 		return nil, fmt.Errorf("Invocation error: %v", err)
 	}
 	if appErr != nil { // application error
 		return nil, appErr
 	}
-
-	// JSON encode results.
-	jsonResults := make([][]byte, len(c.resultptrs))
-	for i, resultptr := range c.resultptrs {
+	// JSON encode the results.
+	jsonResults := make([][]byte, len(resultptrs))
+	for i, resultptr := range resultptrs {
 		// Remove the pointer from the result.  Simply *resultptr doesn't work
 		// as resultptr is of type interface{}.
 		result := derefOrDie(resultptr)
@@ -110,7 +115,6 @@
 			return nil, fmt.Errorf("error marshalling %q into JSON", resultptr)
 		}
 	}
-
 	// Convert to Java array of C.jstring.
 	ret := C.NewObjectArray(env, C.jsize(len(jsonResults)), jStringClass, nil)
 	for i, result := range jsonResults {
diff --git a/runtimes/google/ipc/jni/context.go b/runtimes/google/ipc/jni/context.go
new file mode 100644
index 0000000..cecb04e
--- /dev/null
+++ b/runtimes/google/ipc/jni/context.go
@@ -0,0 +1,38 @@
+// +build android
+
+package main
+
+import (
+	"fmt"
+	"runtime"
+)
+
+// #include "jni_wrapper.h"
+import "C"
+
+func newContext(env *C.JNIEnv, jContext C.jobject) (*context, error) {
+	var jVM *C.JavaVM
+	if status := C.GetJavaVM(env, &jVM); status != 0 {
+		return nil, fmt.Errorf("couldn't get Java VM from the (Java) environment")
+	}
+	// Reference Java context; it will be de-referenced when the go context
+	// created below is garbage-collected (through the finalizer callback we
+	// setup just below).
+	jContext = C.NewGlobalRef(env, jContext)
+	c := &context{
+		jVM:      jVM,
+		jContext: jContext,
+	}
+	runtime.SetFinalizer(c, func(c *context) {
+		var env *C.JNIEnv
+		C.AttachCurrentThread(c.jVM, &env, nil)
+		defer C.DetachCurrentThread(c.jVM)
+		C.DeleteGlobalRef(env, c.jContext)
+	})
+	return c, nil
+}
+
+type context struct {
+	jVM      *C.JavaVM
+	jContext C.jobject
+}
diff --git a/runtimes/google/ipc/jni/invoker.go b/runtimes/google/ipc/jni/invoker.go
index 4a8954c..74a1c90 100644
--- a/runtimes/google/ipc/jni/invoker.go
+++ b/runtimes/google/ipc/jni/invoker.go
@@ -6,6 +6,7 @@
 	"encoding/json"
 	"fmt"
 	"runtime"
+	"strings"
 
 	"veyron2/ipc"
 	"veyron2/security"
@@ -28,6 +29,9 @@
 // static jobject CallGetInterfacePath(JNIEnv* env, jobject obj, jmethodID id) {
 //   return (*env)->CallObjectMethod(env, obj, id);
 // }
+// static jobject CallNewServerCallObject(JNIEnv* env, jclass class, jmethodID id, jlong ref) {
+//   return (*env)->NewObject(env, class, id, ref);
+// }
 import "C"
 
 func newJNIInvoker(env *C.JNIEnv, jVM *C.JavaVM, jObj C.jobject) (ipc.Invoker, error) {
@@ -40,13 +44,13 @@
 	// Fetch the argGetter for the object.
 	pid := jMethodID(env, jIDLInvokerClass, "getInterfacePath", fmt.Sprintf("()%s", stringSign))
 	jPath := C.jstring(C.CallGetInterfacePath(env, jInvoker, pid))
-	getter := newArgGetter(goString(env, jPath))
+	getter := newArgGetter(strings.Join(strings.Split(goString(env, jPath), ".")[1:], "/"))
 	if getter == nil {
 		return nil, fmt.Errorf("couldn't find IDL interface corresponding to path %q", goString(env, jPath))
 	}
 	// Reference Java invoker; it will be de-referenced when the go invoker
 	// created below is garbage-collected (through the finalizer callback we
-	// also setup below).
+	// setup just below).
 	jInvoker = C.NewGlobalRef(env, jInvoker)
 	i := &jniInvoker{
 		jVM:       jVM,
@@ -74,7 +78,12 @@
 	// arguments into vom.Value objects, which we shall then de-serialize into
 	// Java objects (see Invoke comments below).  This approach is blocked on
 	// pending VOM encoder/decoder changes as well as Java (de)serializer.
-	argptrs, err = i.argGetter.GetInArgPtrs(method, numArgs)
+	mArgs := i.argGetter.FindMethod(method, numArgs)
+	if mArgs == nil {
+		err = fmt.Errorf("couldn't find IDL method %q with %d args", method, numArgs)
+		return
+	}
+	argptrs = mArgs.InPtrs()
 	// TODO(spetrovic): ask the Java object to give us the label.
 	label = security.AdminLabel
 	return
@@ -92,6 +101,16 @@
 	C.AttachCurrentThread(i.jVM, &env, nil)
 	defer C.DetachCurrentThread(i.jVM)
 
+	// Create a new Java server call instance.
+	mArgs := i.argGetter.FindMethod(method, len(argptrs))
+	if mArgs == nil {
+		err = fmt.Errorf("couldn't find IDL method %q with %d args", method, len(argptrs))
+	}
+	sCall := newServerCall(call, mArgs)
+	cid := jMethodID(env, jServerCallClass, "<init>", fmt.Sprintf("(%s)%s", longSign, voidSign))
+	jServerCall := C.CallNewServerCallObject(env, jServerCallClass, cid, ptrValue(sCall))
+	goRef(sCall) // unref-ed when jServerCall is garbage-collected
+
 	// Translate input args to JSON.
 	jArgs, err := i.encodeArgs(env, argptrs)
 	if err != nil {
@@ -101,7 +120,7 @@
 	const callSign = "Lcom/veyron2/ipc/ServerCall;"
 	const replySign = "Lcom/veyron/runtimes/google/ipc/IDLInvoker$InvokeReply;"
 	mid := jMethodID(env, C.GetObjectClass(env, i.jInvoker), "invoke", fmt.Sprintf("(%s%s[%s)%s", stringSign, callSign, stringSign, replySign))
-	jReply := C.CallInvokeMethod(env, i.jInvoker, mid, jString(env, camelCase(method)), nil, jArgs)
+	jReply := C.CallInvokeMethod(env, i.jInvoker, mid, jString(env, camelCase(method)), jServerCall, jArgs)
 	if err := jExceptionMsg(env); err != nil {
 		return nil, fmt.Errorf("error invoking Java method %q: %v", method, err)
 	}
@@ -142,11 +161,13 @@
 	errorID := jStringField(env, jReply, "errorID")
 	errorMsg := jStringField(env, jReply, "errorMsg")
 
-	// Get Go result instances.
-	argptrs, err := i.argGetter.GetOutArgPtrs(method, numArgs)
-	if err != nil {
-		return nil, fmt.Errorf("couldn't get arguments for method %q with %d input args: %v", method, numArgs, err)
+	// Get result instances.
+	mArgs := i.argGetter.FindMethod(method, numArgs)
+	if mArgs == nil {
+		return nil, fmt.Errorf("couldn't find method %q with %d input args: %v", method, numArgs)
 	}
+	argptrs := mArgs.OutPtrs()
+
 	// Check for app error.
 	if hasAppErr {
 		return resultsWithError(argptrs, verror.Make(verror.ID(errorID), errorMsg)), nil
diff --git a/runtimes/google/ipc/jni/jni.go b/runtimes/google/ipc/jni/jni.go
index 1fb6a61..c010140 100644
--- a/runtimes/google/ipc/jni/jni.go
+++ b/runtimes/google/ipc/jni/jni.go
@@ -5,11 +5,13 @@
 import (
 	"flag"
 	"fmt"
+	"io"
+	"time"
 	"unsafe"
 
+	"veyron2"
 	"veyron2/ipc"
 	"veyron2/rt"
-	"veyron2/verror"
 )
 
 // #include <stdlib.h>
@@ -19,122 +21,142 @@
 var (
 	// Global reference for com.veyron2.ipc.VeyronException class.
 	jVeyronExceptionClass C.jclass
+	// Global reference for com.veyron.runtimes.google.ipc.Runtime$ServerCall class.
+	jServerCallClass C.jclass
 	// Global reference for com.veyron.runtimes.google.ipc.IDLInvoker class.
 	jIDLInvokerClass C.jclass
 	// Global reference for java.lang.Throwable class.
 	jThrowableClass C.jclass
 	// Global reference for java.lang.String class.
 	jStringClass C.jclass
+	// Global reference for java.io.EOFException class.
+	jEOFExceptionClass C.jclass
 )
 
-// refs stores references to instances of various Go types, namely instances
-// that are referenced only by the Java code.  The only purpose of this store
-// is to prevent Go runtime from garbage collecting those instances.
-var refs = newSafeSet()
-
-// serverPtr returns the pointer to the provided server instance, as a Java's
-// C.jlong type.
-func serverPtr(s ipc.Server) C.jlong {
-	return C.jlong(uintptr(unsafe.Pointer(&s)))
-}
-
-// getServer returns the server referenced by the provided pointer, or nil if
-// the pointer is 0.
-func getServer(env *C.JNIEnv, ptr C.jlong) ipc.Server {
-	if ptr == C.jlong(0) {
-		jThrow(env, "Go server pointer is nil")
-		return nil
-	}
-	return *(*ipc.Server)(unsafe.Pointer(uintptr(ptr)))
-}
-
-// serverPtr returns the pointer to the provided client instance, as a Java's
-// C.jlong type.
-func clientPtr(c *client) C.jlong {
-	return C.jlong(uintptr(unsafe.Pointer(c)))
-}
-
-// getClient returns the client referenced by the provided pointer, or nil if
-// the pointer is 0.
-func getClient(env *C.JNIEnv, ptr C.jlong) *client {
-	if ptr == C.jlong(0) {
-		jThrow(env, "Go client pointer is nil")
-		return nil
-	}
-	return (*client)(unsafe.Pointer(uintptr(ptr)))
-}
-
-// serverPtr returns the pointer to the provided clientCall instance, as a
-// Java's C.jlong type.
-func clientCallPtr(c *clientCall) C.jlong {
-	return C.jlong(uintptr(unsafe.Pointer(c)))
-}
-
-// getCall returns the clientCall referenced by the provided pointer,
-// or nil if the pointer is 0.
-func getCall(env *C.JNIEnv, ptr C.jlong) *clientCall {
-	if ptr == C.jlong(0) {
-		jThrow(env, "Go client call pointer is nil")
-		return nil
-	}
-	return (*clientCall)(unsafe.Pointer(uintptr(ptr)))
-}
-
 //export JNI_OnLoad
 func JNI_OnLoad(jVM *C.JavaVM, reserved unsafe.Pointer) C.jint {
 	return C.JNI_VERSION_1_6
 }
 
-//export Java_com_veyron_runtimes_google_ipc_Runtime_nativeInit
-func Java_com_veyron_runtimes_google_ipc_Runtime_nativeInit(env *C.JNIEnv, jRuntime C.jclass) {
+//export Java_com_veyron_runtimes_google_ipc_Runtime_nativeGlobalInit
+func Java_com_veyron_runtimes_google_ipc_Runtime_nativeGlobalInit(env *C.JNIEnv, jRuntime C.jclass) {
 	// Cache global references to all Java classes used by the package.  This is
 	// 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.
 	jVeyronExceptionClass = jFindClassOrDie(env, "com/veyron2/ipc/VeyronException")
+	jServerCallClass = jFindClassOrDie(env, "com/veyron/runtimes/google/ipc/Runtime$ServerCall")
 	jIDLInvokerClass = jFindClassOrDie(env, "com/veyron/runtimes/google/ipc/IDLInvoker")
 	jThrowableClass = jFindClassOrDie(env, "java/lang/Throwable")
 	jStringClass = jFindClassOrDie(env, "java/lang/String")
+	jEOFExceptionClass = jFindClassOrDie(env, "java/io/EOFException")
 }
 
-//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Server_nativeInit
-func Java_com_veyron_runtimes_google_ipc_Runtime_00024Server_nativeInit(env *C.JNIEnv, jServer C.jobject) C.jlong {
-	s, err := rt.R().NewServer()
-	if err != nil {
-		jThrow(env, fmt.Sprintf("Couldn't get new server from go runtime: %v", err))
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeInit
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeInit(env *C.JNIEnv, jRuntime C.jobject, create C.jboolean) C.jlong {
+	r := rt.Init()
+	if create == C.JNI_TRUE {
+		var err error
+		r, err = rt.New()
+		if err != nil {
+			jThrowV(env, err)
+		}
+	}
+	goRef(&r)
+	return ptrValue(&r)
+}
+
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeNewClient
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeNewClient(env *C.JNIEnv, jRuntime C.jobject, goRuntimePtr C.jlong) C.jlong {
+	r, ok := ptr(goRuntimePtr).(*veyron2.Runtime)
+	if !ok || r == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go runtime with pointer: %d", int(goRuntimePtr)))
 		return C.jlong(0)
 	}
+	rc, err := (*r).NewClient()
+	if err != nil {
+		jThrowV(env, err)
+		return C.jlong(0)
+	}
+	c := newClient(rc)
+	goRef(c)
+	return ptrValue(c)
+}
 
-	// Ref.
-	refs.insert(s)
-	return serverPtr(s)
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeNewServer
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeNewServer(env *C.JNIEnv, jRuntime C.jobject, goRuntimePtr C.jlong) C.jlong {
+	r, ok := ptr(goRuntimePtr).(*veyron2.Runtime)
+	if !ok || r == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go runtime with pointer: %d", int(goRuntimePtr)))
+		return C.jlong(0)
+	}
+	s, err := (*r).NewServer()
+	if err != nil {
+		jThrowV(env, err)
+		return C.jlong(0)
+	}
+	goRef(&s)
+	return ptrValue(&s)
+}
+
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeGetClient
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeGetClient(env *C.JNIEnv, jRuntime C.jobject, goRuntimePtr C.jlong) C.jlong {
+	r, ok := ptr(goRuntimePtr).(*veyron2.Runtime)
+	if !ok || r == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go runtime with pointer: %d", int(goRuntimePtr)))
+		return C.jlong(0)
+	}
+	rc := (*r).Client()
+	c := newClient(rc)
+	goRef(c)
+	return ptrValue(c)
+}
+
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeNewContext
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeNewContext(env *C.JNIEnv, jRuntime C.jobject, goRuntimePtr C.jlong) C.jlong {
+	r, ok := ptr(goRuntimePtr).(*veyron2.Runtime)
+	if !ok || r == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go runtime with pointer: %d", int(goRuntimePtr)))
+		return C.jlong(0)
+	}
+	c := (*r).NewContext()
+	goRef(&c)
+	return ptrValue(&c)
+}
+
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeFinalize
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024Runtime_nativeFinalize(env *C.JNIEnv, jRuntime C.jobject, goRuntimePtr C.jlong) {
+	r, ok := ptr(goRuntimePtr).(*veyron2.Runtime)
+	if ok && r != nil {
+		goUnref(r)
+	}
 }
 
 //export Java_com_veyron_runtimes_google_ipc_Runtime_00024Server_nativeRegister
 func Java_com_veyron_runtimes_google_ipc_Runtime_00024Server_nativeRegister(env *C.JNIEnv, jServer C.jobject, goServerPtr C.jlong, prefix C.jstring, dispatcher C.jobject) {
-	s := getServer(env, goServerPtr)
-	if s == nil {
-		jThrow(env, fmt.Sprintf("Couldn't find Go server with pointer: %d", int(goServerPtr)))
+	s, ok := ptr(goServerPtr).(*ipc.Server)
+	if !ok || s == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go server with pointer: %d", int(goServerPtr)))
 		return
 	}
 	// Create a new Dispatcher
 	d, err := newJNIDispatcher(env, dispatcher)
 	if err != nil {
-		jThrow(env, err.Error())
+		jThrowV(env, err)
 		return
 	}
-	s.Register(goString(env, prefix), d)
+	(*s).Register(goString(env, prefix), d)
 }
 
 //export Java_com_veyron_runtimes_google_ipc_Runtime_00024Server_nativeListen
 func Java_com_veyron_runtimes_google_ipc_Runtime_00024Server_nativeListen(env *C.JNIEnv, server C.jobject, goServerPtr C.jlong, protocol C.jstring, address C.jstring) C.jstring {
-	s := getServer(env, goServerPtr)
-	if s == nil {
-		jThrow(env, fmt.Sprintf("Couldn't find Go server with pointer: %d", int(goServerPtr)))
+	s, ok := ptr(goServerPtr).(*ipc.Server)
+	if !ok || s == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go server with pointer: %d", int(goServerPtr)))
 		return nil
 	}
-	ep, err := s.Listen(goString(env, protocol), goString(env, address))
+	ep, err := (*s).Listen(goString(env, protocol), goString(env, address))
 	if err != nil {
-		jThrow(env, err.Error())
+		jThrowV(env, err)
 		return nil
 	}
 	return jString(env, ep.String())
@@ -142,114 +164,177 @@
 
 //export Java_com_veyron_runtimes_google_ipc_Runtime_00024Server_nativePublish
 func Java_com_veyron_runtimes_google_ipc_Runtime_00024Server_nativePublish(env *C.JNIEnv, server C.jobject, goServerPtr C.jlong, name C.jstring) {
-	s := getServer(env, goServerPtr)
-	if s == nil {
-		jThrow(env, fmt.Sprintf("Couldn't find Go server with pointer: %d", int(goServerPtr)))
+	s, ok := ptr(goServerPtr).(*ipc.Server)
+	if !ok || s == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go server with pointer: %d", int(goServerPtr)))
 		return
 	}
-	if err := s.Publish(goString(env, name)); err != nil {
-		jThrow(env, err.Error())
+	if err := (*s).Publish(goString(env, name)); err != nil {
+		jThrowV(env, err)
 		return
 	}
 }
 
 //export Java_com_veyron_runtimes_google_ipc_jni_Runtime_00024Server_nativeStop
 func Java_com_veyron_runtimes_google_ipc_jni_Runtime_00024Server_nativeStop(env *C.JNIEnv, server C.jobject, goServerPtr C.jlong) {
-	s := getServer(env, goServerPtr)
-	if s == nil {
-		jThrow(env, fmt.Sprintf("Couldn't find Go server with pointer: %d", int(goServerPtr)))
+	s, ok := ptr(goServerPtr).(*ipc.Server)
+	if !ok || s == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go server with pointer: %d", int(goServerPtr)))
 		return
 	}
-	if err := s.Stop(); err != nil {
-		jThrow(env, err.Error())
+	if err := (*s).Stop(); err != nil {
+		jThrowV(env, err)
 		return
 	}
 }
 
 //export Java_com_veyron_runtimes_google_ipc_jni_Runtime_00024Server_nativeFinalize
 func Java_com_veyron_runtimes_google_ipc_jni_Runtime_00024Server_nativeFinalize(env *C.JNIEnv, server C.jobject, goServerPtr C.jlong) {
-	s := getServer(env, goServerPtr)
-	if s != nil {
-		// Unref.
-		refs.delete(s)
+	s, ok := ptr(goServerPtr).(*ipc.Server)
+	if ok && s != nil {
+		goUnref(s)
 	}
 }
 
-//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Client_nativeInit
-func Java_com_veyron_runtimes_google_ipc_Runtime_00024Client_nativeInit(env *C.JNIEnv, jClient C.jobject) C.jlong {
-	c, err := newClient()
-	if err != nil {
-		jThrow(env, fmt.Sprintf("Couldn't get new client from go runtime: %v", err))
-		return C.jlong(0)
-	}
-	// Ref.
-	refs.insert(c)
-	return clientPtr(c)
-}
-
 //export Java_com_veyron_runtimes_google_ipc_Runtime_00024Client_nativeStartCall
-func Java_com_veyron_runtimes_google_ipc_Runtime_00024Client_nativeStartCall(env *C.JNIEnv, jClient C.jobject, goClientPtr C.jlong, name C.jstring, method C.jstring, jsonArgs C.jobjectArray, jPath C.jstring, timeoutMillis C.jlong) C.jlong {
-	c := getClient(env, goClientPtr)
-	if c == nil {
-		jThrow(env, fmt.Sprintf("Couldn't find Go client with pointer: %d", int(goClientPtr)))
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024Client_nativeStartCall(env *C.JNIEnv, jClient C.jobject, goClientPtr C.jlong, jContext C.jobject, name C.jstring, method C.jstring, jsonArgs C.jobjectArray, jPath C.jstring, timeoutMillis C.jlong) C.jlong {
+	c, ok := ptr(goClientPtr).(*client)
+	if !ok || c == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go client with pointer: %d", int(goClientPtr)))
 		return C.jlong(0)
 	}
-	call, err := c.StartCall(env, goString(env, name), goString(env, method), jsonArgs, jPath, timeoutMillis)
+	call, err := c.StartCall(env, jContext, goString(env, name), goString(env, method), jsonArgs, jPath, timeoutMillis)
 	if err != nil {
-		jThrow(env, fmt.Sprintf("Couldn't start Go call: %v", err))
+		jThrowV(env, err)
 		return C.jlong(0)
 	}
-	return clientCallPtr(call)
+	goRef(call)
+	return ptrValue(call)
 }
 
 //export Java_com_veyron_runtimes_google_ipc_Runtime_00024Client_nativeClose
 func Java_com_veyron_runtimes_google_ipc_Runtime_00024Client_nativeClose(env *C.JNIEnv, jClient C.jobject, goClientPtr C.jlong) {
-	c := getClient(env, goClientPtr)
-	if c != nil {
-		c.Close()
+	c, ok := ptr(goClientPtr).(*client)
+	if !ok || c == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go client with pointer: %d", int(goClientPtr)))
+		return
 	}
+	c.Close()
 }
 
 //export Java_com_veyron_runtimes_google_ipc_Runtime_00024Client_nativeFinalize
 func Java_com_veyron_runtimes_google_ipc_Runtime_00024Client_nativeFinalize(env *C.JNIEnv, jClient C.jobject, goClientPtr C.jlong) {
-	c := getClient(env, goClientPtr)
-	if c != nil {
-		// Unref.
-		refs.delete(c)
+	c, ok := ptr(goClientPtr).(*client)
+	if ok && c != nil {
+		goUnref(c)
 	}
 }
 
-//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Call_nativeFinish
-func Java_com_veyron_runtimes_google_ipc_Runtime_00024Call_nativeFinish(env *C.JNIEnv, jClient C.jobject, goCallPtr C.jlong) C.jobjectArray {
-	c := getCall(env, goCallPtr)
-	if c == nil {
-		jThrow(env, fmt.Sprintf("Couldn't find Go client with pointer: %d", int(goCallPtr)))
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Context_nativeFinalize
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024Context_nativeFinalize(env *C.JNIEnv, jClient C.jobject, goContextPtr C.jlong) {
+	c, ok := ptr(goContextPtr).(*ipc.Context)
+	if ok && c != nil {
+		goUnref(c)
+	}
+}
+
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Stream_nativeSend
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024Stream_nativeSend(env *C.JNIEnv, jStream C.jobject, goStreamPtr C.jlong, jItem C.jstring) {
+	s, ok := ptr(goStreamPtr).(*stream)
+	if !ok || s == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go stream with pointer: %d", int(goStreamPtr)))
+		return
+	}
+	s.Send(env, jItem)
+}
+
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Stream_nativeRecv
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024Stream_nativeRecv(env *C.JNIEnv, jStream C.jobject, goStreamPtr C.jlong) C.jstring {
+	s, ok := ptr(goStreamPtr).(*stream)
+	if !ok || s == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go stream with pointer: %d", int(goStreamPtr)))
+		return nil
+	}
+	ret, err := s.Recv(env)
+	if err != nil {
+		if err == io.EOF {
+			jThrow(env, jEOFExceptionClass, err.Error())
+			return nil
+		}
+		jThrowV(env, err)
+		return nil
+	}
+	return ret
+}
+
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024ClientCall_nativeFinish
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024ClientCall_nativeFinish(env *C.JNIEnv, jClientCall C.jobject, goClientCallPtr C.jlong) C.jobjectArray {
+	c, ok := ptr(goClientCallPtr).(*clientCall)
+	if !ok || c == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go client call with pointer: %d", int(goClientCallPtr)))
 		return nil
 	}
 	ret, err := c.Finish(env)
 	if err != nil {
-		// Could be an application error, so we throw it with jThrowV.
-		jThrowV(env, verror.Convert(err))
+		jThrowV(env, err)
 		return nil
 	}
-	// Unref.
-	refs.delete(c)
 	return ret
 }
 
-//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Call_nativeCancel
-func Java_com_veyron_runtimes_google_ipc_Runtime_00024Call_nativeCancel(env *C.JNIEnv, jClient C.jobject, goCallPtr C.jlong) {
-	c := getCall(env, goCallPtr)
-	if c != nil {
-		c.Cancel()
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024ClientCall_nativeCancel
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024ClientCall_nativeCancel(env *C.JNIEnv, jClientCall C.jobject, goClientCallPtr C.jlong) {
+	c, ok := ptr(goClientCallPtr).(*clientCall)
+	if !ok || c == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go client call with pointer: %d", int(goClientCallPtr)))
+		return
+	}
+	c.Cancel()
+}
+
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024ClientCall_nativeFinalize
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024ClientCall_nativeFinalize(env *C.JNIEnv, jClientCall C.jobject, goClientCallPtr C.jlong) {
+	c, ok := ptr(goClientCallPtr).(*clientCall)
+	if ok && c != nil {
+		goUnref(c)
 	}
 }
 
-//export Java_com_veyron_runtimes_google_ipc_Runtime_00024Call_nativeFinalize
-func Java_com_veyron_runtimes_google_ipc_Runtime_00024Call_nativeFinalize(env *C.JNIEnv, jClient C.jobject, goCallPtr C.jlong) {
-	c := getCall(env, goCallPtr)
-	if c != nil {
-		refs.delete(c)
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024ServerCall_nativeDeadline
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024ServerCall_nativeDeadline(env *C.JNIEnv, jServerCall C.jobject, goServerCallPtr C.jlong) C.jlong {
+	s, ok := ptr(goServerCallPtr).(*serverCall)
+	if !ok || s == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go server call with pointer: %d", int(goServerCallPtr)))
+		return C.jlong(0)
+	}
+	var d time.Time
+	if s == nil {
+		// Error, return current time as deadline.
+		d = time.Now()
+	} else {
+		d = s.Deadline()
+	}
+	return C.jlong(d.UnixNano() / 1000)
+}
+
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024ServerCall_nativeClosed
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024ServerCall_nativeClosed(env *C.JNIEnv, jServerCall C.jobject, goServerCallPtr C.jlong) C.jboolean {
+	s, ok := ptr(goServerCallPtr).(*serverCall)
+	if !ok || s == nil {
+		jThrowV(env, fmt.Errorf("Couldn't find Go server call with pointer: %d", int(goServerCallPtr)))
+		return C.JNI_FALSE
+	}
+	if s.IsClosed() {
+		return C.JNI_TRUE
+	}
+	return C.JNI_FALSE
+}
+
+//export Java_com_veyron_runtimes_google_ipc_Runtime_00024ServerCall_nativeFinalize
+func Java_com_veyron_runtimes_google_ipc_Runtime_00024ServerCall_nativeFinalize(env *C.JNIEnv, jServerCall C.jobject, goServerCallPtr C.jlong) {
+	s, ok := ptr(goServerCallPtr).(*serverCall)
+	if ok && s != nil {
+		goUnref(s)
 	}
 }
 
@@ -258,5 +343,4 @@
 	// flag is removed, the process will likely crash as android requires that all logs are written
 	// into a specific directory.
 	flag.Set("logtostderr", "true")
-	rt.Init()
 }
diff --git a/runtimes/google/ipc/jni/server_call.go b/runtimes/google/ipc/jni/server_call.go
new file mode 100644
index 0000000..ffab2ff
--- /dev/null
+++ b/runtimes/google/ipc/jni/server_call.go
@@ -0,0 +1,19 @@
+// +build android
+
+package main
+
+import (
+	"veyron2/ipc"
+)
+
+func newServerCall(call ipc.ServerCall, mArgs *methodArgs) *serverCall {
+	return &serverCall{
+		stream:     newStream(call, mArgs),
+		ServerCall: call,
+	}
+}
+
+type serverCall struct {
+	stream
+	ipc.ServerCall
+}
diff --git a/runtimes/google/ipc/jni/stream.go b/runtimes/google/ipc/jni/stream.go
new file mode 100644
index 0000000..dc4a2fa
--- /dev/null
+++ b/runtimes/google/ipc/jni/stream.go
@@ -0,0 +1,54 @@
+// +build android
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"veyron2/ipc"
+)
+
+// #include <stdlib.h>
+// #include "jni_wrapper.h"
+import "C"
+
+func newStream(s ipc.Stream, mArgs *methodArgs) stream {
+	return stream{
+		stream: s,
+		mArgs:  mArgs,
+	}
+}
+
+type stream struct {
+	stream ipc.Stream
+	mArgs  *methodArgs
+}
+
+func (s *stream) Send(env *C.JNIEnv, jItem C.jstring) error {
+	argStr := goString(env, jItem)
+	argptr := s.mArgs.StreamSendPtr()
+	if argptr == nil {
+		return fmt.Errorf("nil stream input argument, expected a non-nil type for argument %q", argStr)
+	}
+	if err := json.Unmarshal([]byte(argStr), argptr); err != nil {
+		return err
+	}
+	return s.stream.Send(derefOrDie(argptr))
+}
+
+func (s *stream) Recv(env *C.JNIEnv) (C.jstring, error) {
+	argptr := s.mArgs.StreamRecvPtr()
+	if argptr == nil {
+		return nil, fmt.Errorf("nil stream output argument")
+	}
+	if err := s.stream.Recv(argptr); err != nil {
+		return nil, err
+	}
+	// JSON encode the result.
+	result, err := json.Marshal(derefOrDie(argptr))
+	if err != nil {
+		return nil, err
+	}
+	return jString(env, string(result)), nil
+}
diff --git a/runtimes/google/ipc/jni/util.go b/runtimes/google/ipc/jni/util.go
index 0455c9f..50041ef 100644
--- a/runtimes/google/ipc/jni/util.go
+++ b/runtimes/google/ipc/jni/util.go
@@ -29,32 +29,54 @@
 const (
 	voidSign   = "V"
 	boolSign   = "Z"
+	longSign   = "J"
 	stringSign = "Ljava/lang/String;"
 	objectSign = "Ljava/lang/Object;"
 )
 
-// safeSet is a thread-safe set.
-type safeSet struct {
-	lock  sync.Mutex
-	items map[interface{}]bool
-}
+// refs stores references to instances of various Go types, namely instances
+// that are referenced only by the Java code.  The only purpose of this store
+// is to prevent Go runtime from garbage collecting those instances.
+var refs = newSafeSet()
 
-func newSafeSet() *safeSet {
-	return &safeSet{
-		items: make(map[interface{}]bool),
+// goRef creates a new reference to the value addressed by the provided pointer.
+// The value will remain referenced until it is explicitly unreferenced using
+// goUnref().
+func goRef(valptr interface{}) {
+	if !isPointer(valptr) {
+		panic("must pass pointer value to goRef")
 	}
+	refs.insert(valptr)
 }
 
-func (s *safeSet) insert(item interface{}) {
-	s.lock.Lock()
-	defer s.lock.Unlock()
-	s.items[item] = true
+// goUnref removes a previously added reference to the value addressed by the
+// provided pointer.  If the value hasn't been ref-ed (a bug?), this unref will
+// be a no-op.
+func goUnref(valptr interface{}) {
+	if !isPointer(valptr) {
+		panic("must pass pointer value to goUnref")
+	}
+	refs.delete(valptr)
 }
 
-func (s *safeSet) delete(item interface{}) {
-	s.lock.Lock()
-	defer s.lock.Unlock()
-	delete(s.items, item)
+// ptrValue returns the value of the pointer as a Java C.jlong type.
+func ptrValue(ptr interface{}) C.jlong {
+	v := reflect.ValueOf(ptr)
+	if v.Kind() != reflect.Ptr {
+		panic("must pass pointer value to ptrValue")
+	}
+	return C.jlong(v.Pointer())
+}
+
+// ptr returns the pointer represented by the provided (Java C.jlong) value.
+func ptr(ptrValue C.jlong) (ptr interface{}) {
+	ptr = unsafe.Pointer(uintptr(ptrValue))
+	return
+}
+
+// isPointer returns true iff the provided value is a pointer.
+func isPointer(val interface{}) bool {
+	return reflect.ValueOf(ptr).Kind() == reflect.Ptr
 }
 
 // goString returns a Go string given the Java string.
@@ -74,17 +96,18 @@
 	return C.NewStringUTF(env, cString)
 }
 
-// jThrow throws a new Java VeyronException with the given message.
-func jThrow(env *C.JNIEnv, msg string) {
+// jThrow throws a new Java exception of the provided type with the given message.
+func jThrow(env *C.JNIEnv, class C.jclass, msg string) {
 	s := C.CString(msg)
 	defer C.free(unsafe.Pointer(s))
-	C.ThrowNew(env, jVeyronExceptionClass, s)
+	C.ThrowNew(env, class, s)
 }
 
-// jThrowV throws a new Java VeyronException corresponding to the given verror.
-func jThrowV(env *C.JNIEnv, err verror.E) {
+// jThrowV throws a new Java VeyronException corresponding to the given error.
+func jThrowV(env *C.JNIEnv, err error) {
+	verr := verror.Convert(err)
 	id := jMethodID(env, jVeyronExceptionClass, "<init>", fmt.Sprintf("(%s%s)%s", stringSign, stringSign, voidSign))
-	obj := C.jthrowable(C.CallNewVeyronExceptionObject(env, jVeyronExceptionClass, id, jString(env, err.Error()), jString(env, string(err.ErrorID()))))
+	obj := C.jthrowable(C.CallNewVeyronExceptionObject(env, jVeyronExceptionClass, id, jString(env, verr.Error()), jString(env, string(verr.ErrorID()))))
 	C.Throw(env, obj)
 }
 
@@ -183,3 +206,27 @@
 	}
 	return v.Elem().Interface()
 }
+
+func newSafeSet() *safeSet {
+	return &safeSet{
+		items: make(map[interface{}]bool),
+	}
+}
+
+// safeSet is a thread-safe set.
+type safeSet struct {
+	lock  sync.Mutex
+	items map[interface{}]bool
+}
+
+func (s *safeSet) insert(item interface{}) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+	s.items[item] = true
+}
+
+func (s *safeSet) delete(item interface{}) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+	delete(s.items, item)
+}