app: re-use the input queue goroutine.

We now have one such goroutine for the life of the process, not one per
onInputQueueCreated call (e.g. whenever the app re-gains visibility).

Also use ALooper_wake instead of rolling our own os.Pipe.

Change-Id: I4fa045972289144e083033f2e243cac65f35ee46
Reviewed-on: https://go-review.googlesource.com/14670
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/app/android.go b/app/android.go
index 8ecbeef..81c3844 100644
--- a/app/android.go
+++ b/app/android.go
@@ -158,31 +158,15 @@
 
 //export onInputQueueCreated
 func onInputQueueCreated(activity *C.ANativeActivity, q *C.AInputQueue) {
-	onInputQueue(q)
+	C.AInputQueue_detachLooper(q)
+	inputQueue <- q
+	<-inputQueueDone
 }
 
 //export onInputQueueDestroyed
 func onInputQueueDestroyed(activity *C.ANativeActivity, q *C.AInputQueue) {
-	onInputQueue(nil)
-}
-
-func onInputQueue(q *C.AInputQueue) {
-	if inputQueueDonec != nil {
-		close(inputQueueDonec)
-		inputQueueDonec = nil
-	}
-	if q == nil {
-		return
-	}
-	// Disassociate the AInputQueue from this thread's looper.
-	C.AInputQueue_detachLooper(q)
-	inputQueueDonec = make(chan struct{})
-	f := runInputQueue(q, inputQueueDonec)
-	go func() {
-		if err := mobileinit.RunOnJVM(f); err != nil {
-			log.Fatalf("app: %v", err)
-		}
-	}()
+	inputQueue <- nil
+	<-inputQueueDone
 }
 
 //export onContentRectChanged
@@ -253,7 +237,8 @@
 }
 
 var (
-	inputQueueDonec    = chan struct{}(nil)
+	inputQueue         = make(chan *C.AInputQueue)
+	inputQueueDone     = make(chan struct{})
 	windowDestroyed    = make(chan *C.ANativeWindow)
 	windowRedrawNeeded = make(chan *C.ANativeWindow)
 	windowRedrawDone   = make(chan struct{})
@@ -266,6 +251,12 @@
 
 func main(f func(App)) {
 	mainUserFn = f
+	// TODO: merge the runInputQueue and mainUI functions?
+	go func() {
+		if err := mobileinit.RunOnJVM(runInputQueue); err != nil {
+			log.Fatalf("app: %v", err)
+		}
+	}()
 	// Preserve this OS thread for:
 	//	1. the attached JNI thread
 	//	2. the GL context
@@ -354,44 +345,51 @@
 	}
 }
 
-func runInputQueue(q *C.AInputQueue, donec <-chan struct{}) func(vm, jniEnv, ctx uintptr) error {
-	return func(vm, jniEnv, ctx uintptr) error {
-		env := (*C.JNIEnv)(unsafe.Pointer(jniEnv)) // not a Go heap pointer
+func runInputQueue(vm, jniEnv, ctx uintptr) error {
+	env := (*C.JNIEnv)(unsafe.Pointer(jniEnv)) // not a Go heap pointer
 
-		// Android loopers select on OS file descriptors, not Go channels, so
-		// we translate the donec channel to an os.Pipe and connect the read
-		// end to a looper.
-		r, w, err := os.Pipe()
-		if err != nil {
-			return fmt.Errorf("os.Pipe: %v", err)
+	// Android loopers select on OS file descriptors, not Go channels, so we
+	// translate the inputQueue channel to an ALooper_wake call.
+	l := C.ALooper_prepare(C.ALOOPER_PREPARE_ALLOW_NON_CALLBACKS)
+	pending := make(chan *C.AInputQueue, 1)
+	go func() {
+		for q := range inputQueue {
+			pending <- q
+			C.ALooper_wake(l)
 		}
-		go func() {
-			<-donec
-			w.Close()
-		}()
-		defer r.Close()
-		rfd := C.int(r.Fd())
-		l := C.ALooper_prepare(C.ALOOPER_PREPARE_ALLOW_NON_CALLBACKS)
-		C.ALooper_addFd(l, rfd, 0, 0, nil, nil)
+	}()
 
-		// Associate the AInputQueue with this thread's looper.
-		C.AInputQueue_attachLooper(q, l, 0, nil, nil)
-
-		var gotFD C.int
-		for C.ALooper_pollAll(-1, &gotFD, nil, nil) >= 0 {
-			if gotFD == rfd {
-				break
-			}
-			var e *C.AInputEvent
-			for C.AInputQueue_getEvent(q, &e) >= 0 {
-				if C.AInputQueue_preDispatchEvent(q, e) != 0 {
-					continue
+	var q *C.AInputQueue
+	for {
+		if C.ALooper_pollAll(-1, nil, nil, nil) == C.ALOOPER_POLL_WAKE {
+			select {
+			default:
+			case p := <-pending:
+				if q != nil {
+					processEvents(env, q)
+					C.AInputQueue_detachLooper(q)
 				}
-				processEvent(env, e)
-				C.AInputQueue_finishEvent(q, e, 0)
+				q = p
+				if q != nil {
+					C.AInputQueue_attachLooper(q, l, 0, nil, nil)
+				}
+				inputQueueDone <- struct{}{}
 			}
 		}
-		return nil
+		if q != nil {
+			processEvents(env, q)
+		}
+	}
+}
+
+func processEvents(env *C.JNIEnv, q *C.AInputQueue) {
+	var e *C.AInputEvent
+	for C.AInputQueue_getEvent(q, &e) >= 0 {
+		if C.AInputQueue_preDispatchEvent(q, e) != 0 {
+			continue
+		}
+		processEvent(env, e)
+		C.AInputQueue_finishEvent(q, e, 0)
 	}
 }