app: plumbing for physical android key events

This is only the first half of physical key event mapping. The
modifiers and hardware key codes will be in a followup CL. I'm
splitting this out because it covers two other parts of the problem:
first is maintaining a JNIEnv pointer for the main routine, the
second is access to the Android unicode key map. The NDK does not have
a method to give us the key mapping, so we get to it via
GoNativeActivity.

Tested with a USB keyboard. I'll attempt an abd-based unit test later,
but I suspect it will be difficult to set a device ID.

Change-Id: Ie93700d1f2a5d382a9b17cdd668cb4acaa6e4bcc
Reviewed-on: https://go-review.googlesource.com/13649
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
David Crawshaw 2015-08-17 11:17:51 -04:00
parent 0cfb79fc77
commit 377063dfa4
4 changed files with 118 additions and 50 deletions

View File

@ -7,6 +7,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyCharacterMap;
public class GoNativeActivity extends NativeActivity {
private static GoNativeActivity goNativeActivity;
@ -20,6 +21,21 @@ public class GoNativeActivity extends NativeActivity {
return getCacheDir().getAbsolutePath();
}
int getRune(int deviceId, int keyCode, int metaState) {
try {
int rune = KeyCharacterMap.load(deviceId).get(keyCode, metaState);
if (rune == 0) {
return -1;
}
return rune;
} catch (KeyCharacterMap.UnavailableException e) {
return -1;
} catch (Exception e) {
Log.e("Go", "exception reading KeyCharacterMap", e);
return -1;
}
}
private void load() {
// Interestingly, NativeActivity uses a different method
// to find native code to execute, avoiding

View File

@ -35,6 +35,8 @@ static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const
return m;
}
jmethodID key_rune_method;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
@ -44,6 +46,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// Load classes here, which uses the correct ClassLoader.
current_ctx_clazz = find_class(env, "org/golang/app/GoNativeActivity");
current_ctx_clazz = (jclass)(*env)->NewGlobalRef(env, current_ctx_clazz);
key_rune_method = find_method(env, current_ctx_clazz, "getRune", "(III)I");
return JNI_VERSION_1_6;
}
@ -63,8 +66,8 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_
JNIEnv* env = activity->env;
// Note that activity->clazz is mis-named.
JavaVM* current_vm = activity->vm;
jobject current_ctx = activity->clazz;
current_vm = activity->vm;
current_ctx = activity->clazz;
setCurrentContext(current_vm, (*env)->NewGlobalRef(env, current_ctx));
@ -177,12 +180,12 @@ char* destroyEGLSurface() {
return NULL;
}
char* attachJNI(void *vm) {
JavaVM* current_vm = (JavaVM*)(vm);
JNIEnv* env;
char* attachJNI(JNIEnv** envp) {
if (current_vm == NULL) {
return "current_vm not set";
}
int attached = 0;
switch ((*current_vm)->GetEnv(current_vm, (void**)&env, JNI_VERSION_1_6)) {
switch ((*current_vm)->GetEnv(current_vm, (void**)envp, JNI_VERSION_1_6)) {
case JNI_OK:
return NULL;
case JNI_EDETACHED:
@ -190,7 +193,7 @@ char* attachJNI(void *vm) {
// DetachCurrentThread, however attachJNI is called for
// the duration of the main function, which exits when the
// process exits. We let Unix take care of thread cleanup.
if ((*current_vm)->AttachCurrentThread(current_vm, &env, 0) != 0) {
if ((*current_vm)->AttachCurrentThread(current_vm, envp, 0) != 0) {
return "cannot attach JVM";
}
return NULL;
@ -200,3 +203,14 @@ char* attachJNI(void *vm) {
return "unknown GetEnv error";
}
}
int32_t getKeyRune(JNIEnv* env, AInputEvent* e) {
return (int32_t)(*env)->CallIntMethod(
env,
current_ctx,
key_rune_method,
AInputEvent_getDeviceId(e),
AKeyEvent_getKeyCode(e),
AKeyEvent_getMetaState(e)
);
}

View File

@ -32,6 +32,8 @@ package app
#include <pthread.h>
#include <stdlib.h>
JavaVM* current_vm;
jobject current_ctx;
jclass current_ctx_clazz;
jclass app_find_class(JNIEnv* env, const char* name);
@ -42,7 +44,8 @@ EGLSurface surface;
char* initEGLDisplay();
char* createEGLSurface(ANativeWindow* window);
char* destroyEGLSurface();
char* attachJNI(void* vm);
char* attachJNI(JNIEnv**);
int32_t getKeyRune(JNIEnv* env, AInputEvent* e);
*/
import "C"
import (
@ -54,6 +57,7 @@ import (
"unsafe"
"golang.org/x/mobile/app/internal/callfn"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
@ -250,7 +254,8 @@ func main(f func(App)) {
// Calls into NativeActivity functions must be made from
// a thread attached to the JNI.
if errStr := C.attachJNI(mobileinit.Context{}.JavaVM()); errStr != nil {
var env *C.JNIEnv
if errStr := C.attachJNI(&env); errStr != nil {
log.Fatalf("app: %s", C.GoString(errStr))
}
@ -279,7 +284,7 @@ func main(f func(App)) {
for {
if q != nil {
processEvents(q)
processEvents(env, q)
}
select {
case <-windowCreated:
@ -342,21 +347,21 @@ func main(f func(App)) {
}
}
func processEvents(queue *C.AInputQueue) {
func processEvents(env *C.JNIEnv, queue *C.AInputQueue) {
var event *C.AInputEvent
for C.AInputQueue_getEvent(queue, &event) >= 0 {
if C.AInputQueue_preDispatchEvent(queue, event) != 0 {
continue
}
processEvent(event)
processEvent(env, event)
C.AInputQueue_finishEvent(queue, event, 0)
}
}
func processEvent(e *C.AInputEvent) {
func processEvent(env *C.JNIEnv, e *C.AInputEvent) {
switch C.AInputEvent_getType(e) {
case C.AINPUT_EVENT_TYPE_KEY:
log.Printf("TODO input event: key")
processKey(env, e)
case C.AINPUT_EVENT_TYPE_MOTION:
// At most one of the events in this batch is an up or down event; get its index and change.
upDownIndex := C.size_t(C.AMotionEvent_getAction(e)&C.AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> C.AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT
@ -385,6 +390,32 @@ func processEvent(e *C.AInputEvent) {
}
}
func processKey(env *C.JNIEnv, e *C.AInputEvent) {
deviceID := C.AInputEvent_getDeviceId(e)
if deviceID == 0 {
// Software keyboard input, leaving for scribe/IME.
return
}
k := key.Event{
Rune: rune(C.getKeyRune(env, e)),
}
switch C.AKeyEvent_getAction(e) {
case C.AKEY_STATE_DOWN:
k.Direction = key.DirPress
case C.AKEY_STATE_UP:
k.Direction = key.DirRelease
default:
k.Direction = key.DirNone
}
// TODO(crawshaw): set Modifiers.
// TODO(crawshaw): map Android key codes to USB HID key codes.
//keyCode := C.AKeyEvent_getKeyCode(e)
eventsIn <- k
}
func eglGetError() string {
switch errNum := C.eglGetError(); errNum {
case C.EGL_SUCCESS:

View File

@ -6,39 +6,46 @@
package main
var dexStr = `ZGV4CjAzNQBgMPcbh5xnMiJMhJmv7LSwWLsd8n8QqVIUBwAAcAAAAHhWNBIAAAAAAAAAAH` +
`QGAAApAAAAcAAAAA8AAAAUAQAADAAAAFABAAACAAAA4AEAABAAAADwAQAAAQAAAHACAACE` +
`BAAAkAIAAJ4DAACmAwAAqgMAAMEDAADEAwAAyQMAAM8DAADSAwAA1gMAANsDAAD5AwAAGg` +
`QAADQEAABXBAAAfAQAAJEEAAClBAAAtQQAAMwEAADgBAAA9AQAAAsFAAAuBQAAMQUAADUF` +
`AABLBQAATgUAAF8FAABwBQAAfQUAAIsFAACWBQAAqQUAALQFAAC/BQAA0QUAANcFAADkBQ` +
`AA+AUAACEGAAArBgAAAwAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAAAA8AAAAQAAAAEQAA` +
`ABIAAAATAAAAFAAAABUAAAAWAAAABAAAAAAAAAB0AwAABQAAAAAAAAB8AwAABgAAAAIAAA` +
`AAAAAABgAAAAMAAAAAAAAACAAAAAQAAACIAwAABgAAAAUAAAAAAAAABgAAAAgAAAAAAAAA` +
`BgAAAAoAAAAAAAAABwAAAAoAAACQAwAAFgAAAA4AAAAAAAAAFwAAAA4AAACYAwAAFwAAAA` +
`4AAACQAwAABAAGACcAAAANAA0AIgAAAAEACQAAAAAAAQAKACgAAAADAAIAHQAAAAUABAAb` +
`AAAABgAIACAAAAAHAAAAGQAAAAcAAQAZAAAACAAHABoAAAALAAsAJAAAAA0ACQAAAAAADQ` +
`AGABwAAAANAAMAHgAAAA0ABQAfAAAADQAHACEAAAANAAkAIwAAAA0ACgAoAAAADQAAAAEA` +
`AAABAAAAAAAAAAIAAAAAAAAAWQYAAAAAAAABAAEAAQAAADUGAAAGAAAAcBAAAAAAaQABAA` +
`4ABAABAAMAAQA8BgAAMwAAAG4QDAADAAwAbhALAAMADAFuEAIAAQAMARMCgABuMAMAEAIM` +
`AFQBAAA5AQoAGgABABoBJgBxIAUAEAAOAFQAAAAaARgAbiAEABAADABxEAgAAAAo9A0AGg` +
`EBABoCJQBxMAYAIQAo6wAAAAAAACkAAQABAQkqAgABAAEAAABMBgAACQAAAG4QCgABAAwA` +
`bhAHAAAADAARAAAAAgACAAIAAABRBgAABwAAAHAQDgAAAG8gAQAQAA4AAAACAAAACgAKAA` +
`MAAAAKAAoADAAAAAIAAAACAAAAAQAAAAoAAAABAAAABgAGPGluaXQ+AAJHbwAVR29OYXRp` +
`dmVBY3Rpdml0eS5qYXZhAAFJAANJTEwABElMTEwAAUwAAkxMAANMTEkAHExhbmRyb2lkL2` +
`FwcC9OYXRpdmVBY3Rpdml0eTsAH0xhbmRyb2lkL2NvbnRlbnQvQ29tcG9uZW50TmFtZTsA` +
`GExhbmRyb2lkL2NvbnRlbnQvSW50ZW50OwAhTGFuZHJvaWQvY29udGVudC9wbS9BY3Rpdm` +
`l0eUluZm87ACNMYW5kcm9pZC9jb250ZW50L3BtL1BhY2thZ2VNYW5hZ2VyOwATTGFuZHJv` +
`aWQvb3MvQnVuZGxlOwASTGFuZHJvaWQvdXRpbC9Mb2c7AA5MamF2YS9pby9GaWxlOwAVTG` +
`phdmEvbGFuZy9FeGNlcHRpb247ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcv` +
`U3lzdGVtOwAVTGphdmEvbGFuZy9UaHJvd2FibGU7ACFMb3JnL2dvbGFuZy9hcHAvR29OYX` +
`RpdmVBY3Rpdml0eTsAAVYAAlZMABRhbmRyb2lkLmFwcC5saWJfbmFtZQABZQAPZ2V0QWJz` +
`b2x1dGVQYXRoAA9nZXRBY3Rpdml0eUluZm8AC2dldENhY2hlRGlyAAxnZXRDb21wb25lbn` +
`QACWdldEludGVudAARZ2V0UGFja2FnZU1hbmFnZXIACWdldFN0cmluZwAJZ2V0VG1wZGly` +
`ABBnb05hdGl2ZUFjdGl2aXR5AARsb2FkAAtsb2FkTGlicmFyeQASbG9hZExpYnJhcnkgZm` +
`FpbGVkACdsb2FkTGlicmFyeTogbm8gbWFuaWZlc3QgbWV0YWRhdGEgZm91bmQACG1ldGFE` +
`YXRhAAhvbkNyZWF0ZQAPAAcOPC0AIQAHDgESEEt/Ansdh0seABQABw4AMAEABw48PAABAA` +
`ICAQoJgYAEkAUFAqwFDQCwBgIB1AYAAAANAAAAAAAAAAEAAAAAAAAAAQAAACkAAABwAAAA` +
`AgAAAA8AAAAUAQAAAwAAAAwAAABQAQAABAAAAAIAAADgAQAABQAAABAAAADwAQAABgAAAA` +
`EAAABwAgAAASAAAAQAAACQAgAAARAAAAUAAAB0AwAAAiAAACkAAACeAwAAAyAAAAQAAAA1` +
`BgAAACAAAAEAAABZBgAAABAAAAEAAAB0BgAA` +
var dexStr = `ZGV4CjAzNQBIdedFANrzNlK7iW3fcjAaCV9Jtg0Md7iQCAAAcAAAAHhWNBIAAAAAAAAAAP` +
`AHAAAxAAAAcAAAABEAAAA0AQAADwAAAHgBAAACAAAALAIAABMAAAA8AgAAAQAAANQCAACc` +
`BQAA9AIAAHIEAAB6BAAAfgQAAJUEAACYBAAAnQQAAKMEAACoBAAArgQAALEEAAC1BAAAuQ` +
`QAAL4EAADcBAAA/QQAABcFAAA6BQAAXwUAAHQFAACIBQAAvQUAAN0FAADtBQAABAYAABgG` +
`AAAsBgAAQwYAAGYGAABpBgAAbQYAAIMGAACGBgAAqQYAAK4GAAC/BgAA0AYAAN0GAADrBg` +
`AA9gYAAAkHAAASBwAAHQcAACgHAAA6BwAAQAcAAE0HAABhBwAAigcAAJQHAAADAAAADAAA` +
`AA0AAAAOAAAADwAAABAAAAARAAAAEgAAABMAAAAUAAAAFQAAABYAAAAXAAAAGAAAABkAAA` +
`AaAAAAGwAAAAQAAAAAAAAALAQAAAUAAAAAAAAANAQAAAYAAAAAAAAAQAQAAAcAAAAAAAAA` +
`SAQAAAgAAAACAAAAAAAAAAgAAAADAAAAAAAAAAsAAAAEAAAAVAQAAAgAAAAFAAAAAAAAAA` +
`kAAAAJAAAAXAQAAAgAAAAKAAAAAAAAAAgAAAAMAAAAAAAAAAoAAAAMAAAAZAQAABsAAAAQ` +
`AAAAAAAAABwAAAAQAAAAbAQAABwAAAAQAAAAZAQAAAQABgAvAAAADwAPACoAAAABAAwAAA` +
`AAAAEADQAwAAAAAwAEACQAAAAFAAYAIgAAAAYACwAoAAAABwACAB4AAAAHAAMAHgAAAAkA` +
`AAAgAAAACQAIACsAAAAKAAoAIQAAAA0ADgAsAAAADwAMAAAAAAAPAAkAIwAAAA8ABQAlAA` +
`AADwAHACYAAAAPAAEAJwAAAA8ACgApAAAADwAMACsAAAAPAA0AMAAAAA8AAAABAAAAAQAA` +
`AAAAAAACAAAAAAAAANIHAAAAAAAAAQABAAEAAACeBwAABgAAAHAQAAAAAGkAAQAOAAQAAQ` +
`ADAAEApQcAADMAAABuEA4AAwAMAG4QDQADAAwBbhACAAEADAETAoAAbjADABACDABUAQAA` +
`OQEKABoAAQAaAS4AcSAFABAADgBUAAAAGgEdAG4gBAAQAAwAcRAKAAAAKPQNABoBAQAaAi` +
`0AcTAGACEAKOsAAAAAAAApAAEAAQELKggABAADAAEAtQcAABkAAAAS8HEQCAAFAAwBbjAH` +
`AGEHCgE5AQMADwABECj+DQEaAgEAGgMfAHEwBgAyASj1DQEo8wAAAQAAAAcAAQABAggXCw` +
`4AAAIAAQABAAAAxQcAAAkAAABuEAwAAQAMAG4QCQAAAAwAEQAAAAIAAgACAAAAygcAAAcA` +
`AABwEBEAAABvIAEAEAAOAAAAAgAAAAAAAAADAAAAAAAAAAAAAAACAAAADAAMAAMAAAAMAA` +
`wADgAAAAIAAAACAAAAAQAAAAAAAAABAAAADAAAAAEAAAAGAAY8aW5pdD4AAkdvABVHb05h` +
`dGl2ZUFjdGl2aXR5LmphdmEAAUkAA0lJSQAESUlJSQADSUxMAARJTExMAAFMAAJMSQACTE` +
`wAA0xMSQAcTGFuZHJvaWQvYXBwL05hdGl2ZUFjdGl2aXR5OwAfTGFuZHJvaWQvY29udGVu` +
`dC9Db21wb25lbnROYW1lOwAYTGFuZHJvaWQvY29udGVudC9JbnRlbnQ7ACFMYW5kcm9pZC` +
`9jb250ZW50L3BtL0FjdGl2aXR5SW5mbzsAI0xhbmRyb2lkL2NvbnRlbnQvcG0vUGFja2Fn` +
`ZU1hbmFnZXI7ABNMYW5kcm9pZC9vcy9CdW5kbGU7ABJMYW5kcm9pZC91dGlsL0xvZzsAM0` +
`xhbmRyb2lkL3ZpZXcvS2V5Q2hhcmFjdGVyTWFwJFVuYXZhaWxhYmxlRXhjZXB0aW9uOwAe` +
`TGFuZHJvaWQvdmlldy9LZXlDaGFyYWN0ZXJNYXA7AA5MamF2YS9pby9GaWxlOwAVTGphdm` +
`EvbGFuZy9FeGNlcHRpb247ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lz` +
`dGVtOwAVTGphdmEvbGFuZy9UaHJvd2FibGU7ACFMb3JnL2dvbGFuZy9hcHAvR29OYXRpdm` +
`VBY3Rpdml0eTsAAVYAAlZMABRhbmRyb2lkLmFwcC5saWJfbmFtZQABZQAhZXhjZXB0aW9u` +
`IHJlYWRpbmcgS2V5Q2hhcmFjdGVyTWFwAANnZXQAD2dldEFic29sdXRlUGF0aAAPZ2V0QW` +
`N0aXZpdHlJbmZvAAtnZXRDYWNoZURpcgAMZ2V0Q29tcG9uZW50AAlnZXRJbnRlbnQAEWdl` +
`dFBhY2thZ2VNYW5hZ2VyAAdnZXRSdW5lAAlnZXRTdHJpbmcACWdldFRtcGRpcgAQZ29OYX` +
`RpdmVBY3Rpdml0eQAEbG9hZAALbG9hZExpYnJhcnkAEmxvYWRMaWJyYXJ5IGZhaWxlZAAn` +
`bG9hZExpYnJhcnk6IG5vIG1hbmlmZXN0IG1ldGFkYXRhIGZvdW5kAAhtZXRhRGF0YQAIb2` +
`5DcmVhdGUAEAAHDjwtADEABw4BEhBLfwJ7HYdLHgAaAwAAAAcdhzQCeywgHoMAFQAHDgBA` +
`AQAHDjw8AAEAAgMBCguBgAT0BQYCkAYPAJQHAQDoBwIBjAgAAA0AAAAAAAAAAQAAAAAAAA` +
`ABAAAAMQAAAHAAAAACAAAAEQAAADQBAAADAAAADwAAAHgBAAAEAAAAAgAAACwCAAAFAAAA` +
`EwAAADwCAAAGAAAAAQAAANQCAAABIAAABQAAAPQCAAABEAAACAAAACwEAAACIAAAMQAAAH` +
`IEAAADIAAABQAAAJ4HAAAAIAAAAQAAANIHAAAAEAAAAQAAAPAHAAA=` +
``