2
0
mirror of synced 2025-02-24 07:18:15 +00:00
mobile/app/android.go
David Crawshaw eadadb8667 internal/mobileinit: standardize JNIEnv* access
Replace the direct access to JavaVM* and the global android Context
instance with a function responsible for running attached correctly
to the JVM. This saves having to replicate the logic for attaching an
OS thread to the JVM. While here, check for any unhandled Java
exceptions.

This supersedes cl/11812.

Change-Id: Ic9291fe64083d2bd983c4f8e309941b9c47d60c2
Reviewed-on: https://go-review.googlesource.com/14162
Reviewed-by: Nigel Tao <nigeltao@golang.org>
2015-09-14 17:30:52 +00:00

785 lines
20 KiB
Go

// Copyright 2014 The Go 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
/*
Android Apps are built with -buildmode=c-shared. They are loaded by a
running Java process.
Before any entry point is reached, a global constructor initializes the
Go runtime, calling all Go init functions. All cgo calls will block
until this is complete. Next JNI_OnLoad is called. When that is
complete, one of two entry points is called.
All-Go apps built using NativeActivity enter at ANativeActivity_onCreate.
Go libraries (for example, those built with gomobile bind) do not use
the app package initialization.
*/
package app
/*
#cgo LDFLAGS: -landroid -llog -lEGL -lGLESv2
#include <android/configuration.h>
#include <android/keycodes.h>
#include <android/native_activity.h>
#include <android/native_window.h>
#include <EGL/egl.h>
#include <jni.h>
#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);
EGLDisplay display;
EGLSurface surface;
char* initEGLDisplay();
char* createEGLSurface(ANativeWindow* window);
char* destroyEGLSurface();
int32_t getKeyRune(JNIEnv* env, AInputEvent* e);
*/
import "C"
import (
"fmt"
"log"
"os"
"time"
"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"
"golang.org/x/mobile/event/touch"
"golang.org/x/mobile/geom"
"golang.org/x/mobile/gl"
"golang.org/x/mobile/internal/mobileinit"
)
//export setCurrentContext
func setCurrentContext(vm *C.JavaVM, ctx C.jobject) {
mobileinit.SetCurrentContext(unsafe.Pointer(vm), unsafe.Pointer(ctx))
}
//export callMain
func callMain(mainPC uintptr) {
for _, name := range []string{"TMPDIR", "PATH", "LD_LIBRARY_PATH"} {
n := C.CString(name)
os.Setenv(name, C.GoString(C.getenv(n)))
C.free(unsafe.Pointer(n))
}
// Set timezone.
//
// Note that Android zoneinfo is stored in /system/usr/share/zoneinfo,
// but it is in some kind of packed TZiff file that we do not support
// yet. As a stopgap, we build a fixed zone using the tm_zone name.
var curtime C.time_t
var curtm C.struct_tm
C.time(&curtime)
C.localtime_r(&curtime, &curtm)
tzOffset := int(curtm.tm_gmtoff)
tz := C.GoString(curtm.tm_zone)
time.Local = time.FixedZone(tz, tzOffset)
go callfn.CallFn(mainPC)
}
//export onStart
func onStart(activity *C.ANativeActivity) {
}
//export onResume
func onResume(activity *C.ANativeActivity) {
}
//export onSaveInstanceState
func onSaveInstanceState(activity *C.ANativeActivity, outSize *C.size_t) unsafe.Pointer {
return nil
}
//export onPause
func onPause(activity *C.ANativeActivity) {
}
//export onStop
func onStop(activity *C.ANativeActivity) {
}
//export onCreate
func onCreate(activity *C.ANativeActivity) {
// Set the initial configuration.
//
// Note we use unbuffered channels to talk to the activity loop, and
// NativeActivity calls these callbacks sequentially, so configuration
// will be set before <-windowRedrawNeeded is processed.
windowConfigChange <- windowConfigRead(activity)
}
//export onDestroy
func onDestroy(activity *C.ANativeActivity) {
}
//export onWindowFocusChanged
func onWindowFocusChanged(activity *C.ANativeActivity, hasFocus int) {
}
//export onNativeWindowCreated
func onNativeWindowCreated(activity *C.ANativeActivity, w *C.ANativeWindow) {
windowCreated <- w
}
//export onNativeWindowRedrawNeeded
func onNativeWindowRedrawNeeded(activity *C.ANativeActivity, window *C.ANativeWindow) {
// Called on orientation change and window resize.
// Send a request for redraw, and block this function
// until a complete draw and buffer swap is completed.
// This is required by the redraw documentation to
// avoid bad draws.
windowRedrawNeeded <- window
<-windowRedrawDone
}
//export onNativeWindowDestroyed
func onNativeWindowDestroyed(activity *C.ANativeActivity, window *C.ANativeWindow) {
windowDestroyed <- window
}
//export onInputQueueCreated
func onInputQueueCreated(activity *C.ANativeActivity, q *C.AInputQueue) {
inputQueue <- q
}
//export onInputQueueDestroyed
func onInputQueueDestroyed(activity *C.ANativeActivity, q *C.AInputQueue) {
inputQueue <- nil
}
//export onContentRectChanged
func onContentRectChanged(activity *C.ANativeActivity, rect *C.ARect) {
}
type windowConfig struct {
orientation size.Orientation
pixelsPerPt float32
}
func windowConfigRead(activity *C.ANativeActivity) windowConfig {
aconfig := C.AConfiguration_new()
C.AConfiguration_fromAssetManager(aconfig, activity.assetManager)
orient := C.AConfiguration_getOrientation(aconfig)
density := C.AConfiguration_getDensity(aconfig)
C.AConfiguration_delete(aconfig)
var dpi int
switch density {
case C.ACONFIGURATION_DENSITY_DEFAULT:
dpi = 160
case C.ACONFIGURATION_DENSITY_LOW,
C.ACONFIGURATION_DENSITY_MEDIUM,
213, // C.ACONFIGURATION_DENSITY_TV
C.ACONFIGURATION_DENSITY_HIGH,
320, // ACONFIGURATION_DENSITY_XHIGH
480, // ACONFIGURATION_DENSITY_XXHIGH
640: // ACONFIGURATION_DENSITY_XXXHIGH
dpi = int(density)
case C.ACONFIGURATION_DENSITY_NONE:
log.Print("android device reports no screen density")
dpi = 72
default:
log.Printf("android device reports unknown density: %d", density)
// All we can do is guess.
if density > 0 {
dpi = int(density)
} else {
dpi = 72
}
}
o := size.OrientationUnknown
switch orient {
case C.ACONFIGURATION_ORIENTATION_PORT:
o = size.OrientationPortrait
case C.ACONFIGURATION_ORIENTATION_LAND:
o = size.OrientationLandscape
}
return windowConfig{
orientation: o,
pixelsPerPt: float32(dpi) / 72,
}
}
//export onConfigurationChanged
func onConfigurationChanged(activity *C.ANativeActivity) {
// A rotation event first triggers onConfigurationChanged, then
// calls onNativeWindowRedrawNeeded. We extract the orientation
// here and save it for the redraw event.
windowConfigChange <- windowConfigRead(activity)
}
//export onLowMemory
func onLowMemory(activity *C.ANativeActivity) {
}
var (
inputQueue = make(chan *C.AInputQueue)
windowDestroyed = make(chan *C.ANativeWindow)
windowCreated = make(chan *C.ANativeWindow)
windowRedrawNeeded = make(chan *C.ANativeWindow)
windowRedrawDone = make(chan struct{})
windowConfigChange = make(chan windowConfig)
)
func init() {
registerGLViewportFilter()
}
func main(f func(App)) {
mainUserFn = f
// Preserve this OS thread for:
// 1. the attached JNI thread
// 2. the GL context
if err := mobileinit.RunOnJVM(mainUI); err != nil {
log.Fatalf("app: %v", err)
}
}
var mainUserFn func(App)
func mainUI(vm, jniEnv, ctx uintptr) error {
env := (*C.JNIEnv)(unsafe.Pointer(jniEnv)) // not a Go heap pointer
donec := make(chan struct{})
go func() {
mainUserFn(app{})
close(donec)
}()
var q *C.AInputQueue
var pixelsPerPt float32
var orientation size.Orientation
// Android can send a windowRedrawNeeded event any time, including
// in the middle of a paint cycle. The redraw event may have changed
// the size of the screen, so any partial painting is now invalidated.
// We must also not return to Android (via sending on windowRedrawDone)
// until a complete paint with the new configuration is complete.
//
// When a windowRedrawNeeded request comes in, we increment redrawGen
// (Gen is short for generation number), and do not make a paint cycle
// visible on <-endPaint unless Generation agrees. If possible,
// windowRedrawDone is signalled, allowing onNativeWindowRedrawNeeded
// to return.
var redrawGen uint32
for {
if q != nil {
processEvents(env, q)
}
select {
case <-windowCreated:
case q = <-inputQueue:
case <-donec:
return nil
case cfg := <-windowConfigChange:
pixelsPerPt = cfg.pixelsPerPt
orientation = cfg.orientation
case w := <-windowRedrawNeeded:
if C.surface == nil {
if errStr := C.createEGLSurface(w); errStr != nil {
return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError())
}
}
sendLifecycle(lifecycle.StageFocused)
widthPx := int(C.ANativeWindow_getWidth(w))
heightPx := int(C.ANativeWindow_getHeight(w))
eventsIn <- size.Event{
WidthPx: widthPx,
HeightPx: heightPx,
WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt),
HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt),
PixelsPerPt: pixelsPerPt,
Orientation: orientation,
}
redrawGen++
eventsIn <- paint.Event{redrawGen}
case <-windowDestroyed:
if C.surface != nil {
if errStr := C.destroyEGLSurface(); errStr != nil {
return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError())
}
}
C.surface = nil
sendLifecycle(lifecycle.StageAlive)
case <-gl.WorkAvailable:
gl.DoWork()
case p := <-endPaint:
if p.Generation != redrawGen {
continue
}
if C.surface != nil {
// eglSwapBuffers blocks until vsync.
if C.eglSwapBuffers(C.display, C.surface) == C.EGL_FALSE {
log.Printf("app: failed to swap buffers (%s)", eglGetError())
}
}
select {
case windowRedrawDone <- struct{}{}:
default:
}
if C.surface != nil {
redrawGen++
eventsIn <- paint.Event{redrawGen}
}
}
}
}
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(env, event)
C.AInputQueue_finishEvent(queue, event, 0)
}
}
func processEvent(env *C.JNIEnv, e *C.AInputEvent) {
switch C.AInputEvent_getType(e) {
case C.AINPUT_EVENT_TYPE_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
upDownType := touch.TypeMove
switch C.AMotionEvent_getAction(e) & C.AMOTION_EVENT_ACTION_MASK {
case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
upDownType = touch.TypeBegin
case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
upDownType = touch.TypeEnd
}
for i, n := C.size_t(0), C.AMotionEvent_getPointerCount(e); i < n; i++ {
t := touch.TypeMove
if i == upDownIndex {
t = upDownType
}
eventsIn <- touch.Event{
X: float32(C.AMotionEvent_getX(e, i)),
Y: float32(C.AMotionEvent_getY(e, i)),
Sequence: touch.Sequence(C.AMotionEvent_getPointerId(e, i)),
Type: t,
}
}
default:
log.Printf("unknown input event, type=%d", C.AInputEvent_getType(e))
}
}
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)),
Code: convAndroidKeyCode(int32(C.AKeyEvent_getKeyCode(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.
eventsIn <- k
}
func eglGetError() string {
switch errNum := C.eglGetError(); errNum {
case C.EGL_SUCCESS:
return "EGL_SUCCESS"
case C.EGL_NOT_INITIALIZED:
return "EGL_NOT_INITIALIZED"
case C.EGL_BAD_ACCESS:
return "EGL_BAD_ACCESS"
case C.EGL_BAD_ALLOC:
return "EGL_BAD_ALLOC"
case C.EGL_BAD_ATTRIBUTE:
return "EGL_BAD_ATTRIBUTE"
case C.EGL_BAD_CONTEXT:
return "EGL_BAD_CONTEXT"
case C.EGL_BAD_CONFIG:
return "EGL_BAD_CONFIG"
case C.EGL_BAD_CURRENT_SURFACE:
return "EGL_BAD_CURRENT_SURFACE"
case C.EGL_BAD_DISPLAY:
return "EGL_BAD_DISPLAY"
case C.EGL_BAD_SURFACE:
return "EGL_BAD_SURFACE"
case C.EGL_BAD_MATCH:
return "EGL_BAD_MATCH"
case C.EGL_BAD_PARAMETER:
return "EGL_BAD_PARAMETER"
case C.EGL_BAD_NATIVE_PIXMAP:
return "EGL_BAD_NATIVE_PIXMAP"
case C.EGL_BAD_NATIVE_WINDOW:
return "EGL_BAD_NATIVE_WINDOW"
case C.EGL_CONTEXT_LOST:
return "EGL_CONTEXT_LOST"
default:
return fmt.Sprintf("Unknown EGL err: %d", errNum)
}
}
func convAndroidKeyCode(aKeyCode int32) key.Code {
// Many Android key codes do not map into USB HID codes.
// For those, key.CodeUnknown is returned. This switch has all
// cases, even the unknown ones, to serve as a documentation
// and search aid.
switch aKeyCode {
case C.AKEYCODE_UNKNOWN:
case C.AKEYCODE_SOFT_LEFT:
case C.AKEYCODE_SOFT_RIGHT:
case C.AKEYCODE_HOME:
return key.CodeHome
case C.AKEYCODE_BACK:
case C.AKEYCODE_CALL:
case C.AKEYCODE_ENDCALL:
case C.AKEYCODE_0:
return key.Code0
case C.AKEYCODE_1:
return key.Code1
case C.AKEYCODE_2:
return key.Code2
case C.AKEYCODE_3:
return key.Code3
case C.AKEYCODE_4:
return key.Code4
case C.AKEYCODE_5:
return key.Code5
case C.AKEYCODE_6:
return key.Code6
case C.AKEYCODE_7:
return key.Code7
case C.AKEYCODE_8:
return key.Code8
case C.AKEYCODE_9:
return key.Code9
case C.AKEYCODE_STAR:
case C.AKEYCODE_POUND:
case C.AKEYCODE_DPAD_UP:
case C.AKEYCODE_DPAD_DOWN:
case C.AKEYCODE_DPAD_LEFT:
case C.AKEYCODE_DPAD_RIGHT:
case C.AKEYCODE_DPAD_CENTER:
case C.AKEYCODE_VOLUME_UP:
return key.CodeVolumeUp
case C.AKEYCODE_VOLUME_DOWN:
return key.CodeVolumeDown
case C.AKEYCODE_POWER:
case C.AKEYCODE_CAMERA:
case C.AKEYCODE_CLEAR:
case C.AKEYCODE_A:
return key.CodeA
case C.AKEYCODE_B:
return key.CodeB
case C.AKEYCODE_C:
return key.CodeC
case C.AKEYCODE_D:
return key.CodeD
case C.AKEYCODE_E:
return key.CodeE
case C.AKEYCODE_F:
return key.CodeF
case C.AKEYCODE_G:
return key.CodeG
case C.AKEYCODE_H:
return key.CodeH
case C.AKEYCODE_I:
return key.CodeI
case C.AKEYCODE_J:
return key.CodeJ
case C.AKEYCODE_K:
return key.CodeK
case C.AKEYCODE_L:
return key.CodeL
case C.AKEYCODE_M:
return key.CodeM
case C.AKEYCODE_N:
return key.CodeN
case C.AKEYCODE_O:
return key.CodeO
case C.AKEYCODE_P:
return key.CodeP
case C.AKEYCODE_Q:
return key.CodeQ
case C.AKEYCODE_R:
return key.CodeR
case C.AKEYCODE_S:
return key.CodeS
case C.AKEYCODE_T:
return key.CodeT
case C.AKEYCODE_U:
return key.CodeU
case C.AKEYCODE_V:
return key.CodeV
case C.AKEYCODE_W:
return key.CodeW
case C.AKEYCODE_X:
return key.CodeX
case C.AKEYCODE_Y:
return key.CodeY
case C.AKEYCODE_Z:
return key.CodeZ
case C.AKEYCODE_COMMA:
return key.CodeComma
case C.AKEYCODE_PERIOD:
return key.CodeFullStop
case C.AKEYCODE_ALT_LEFT:
return key.CodeLeftAlt
case C.AKEYCODE_ALT_RIGHT:
return key.CodeRightAlt
case C.AKEYCODE_SHIFT_LEFT:
return key.CodeLeftShift
case C.AKEYCODE_SHIFT_RIGHT:
return key.CodeRightShift
case C.AKEYCODE_TAB:
return key.CodeTab
case C.AKEYCODE_SPACE:
return key.CodeSpacebar
case C.AKEYCODE_SYM:
case C.AKEYCODE_EXPLORER:
case C.AKEYCODE_ENVELOPE:
case C.AKEYCODE_ENTER:
return key.CodeReturnEnter
case C.AKEYCODE_DEL:
return key.CodeDeleteBackspace
case C.AKEYCODE_GRAVE:
return key.CodeGraveAccent
case C.AKEYCODE_MINUS:
return key.CodeHyphenMinus
case C.AKEYCODE_EQUALS:
return key.CodeEqualSign
case C.AKEYCODE_LEFT_BRACKET:
return key.CodeLeftSquareBracket
case C.AKEYCODE_RIGHT_BRACKET:
return key.CodeRightSquareBracket
case C.AKEYCODE_BACKSLASH:
return key.CodeBackslash
case C.AKEYCODE_SEMICOLON:
return key.CodeSemicolon
case C.AKEYCODE_APOSTROPHE:
return key.CodeApostrophe
case C.AKEYCODE_SLASH:
return key.CodeSlash
case C.AKEYCODE_AT:
case C.AKEYCODE_NUM:
case C.AKEYCODE_HEADSETHOOK:
case C.AKEYCODE_FOCUS:
case C.AKEYCODE_PLUS:
case C.AKEYCODE_MENU:
case C.AKEYCODE_NOTIFICATION:
case C.AKEYCODE_SEARCH:
case C.AKEYCODE_MEDIA_PLAY_PAUSE:
case C.AKEYCODE_MEDIA_STOP:
case C.AKEYCODE_MEDIA_NEXT:
case C.AKEYCODE_MEDIA_PREVIOUS:
case C.AKEYCODE_MEDIA_REWIND:
case C.AKEYCODE_MEDIA_FAST_FORWARD:
case C.AKEYCODE_MUTE:
case C.AKEYCODE_PAGE_UP:
return key.CodePageUp
case C.AKEYCODE_PAGE_DOWN:
return key.CodePageDown
case C.AKEYCODE_PICTSYMBOLS:
case C.AKEYCODE_SWITCH_CHARSET:
case C.AKEYCODE_BUTTON_A:
case C.AKEYCODE_BUTTON_B:
case C.AKEYCODE_BUTTON_C:
case C.AKEYCODE_BUTTON_X:
case C.AKEYCODE_BUTTON_Y:
case C.AKEYCODE_BUTTON_Z:
case C.AKEYCODE_BUTTON_L1:
case C.AKEYCODE_BUTTON_R1:
case C.AKEYCODE_BUTTON_L2:
case C.AKEYCODE_BUTTON_R2:
case C.AKEYCODE_BUTTON_THUMBL:
case C.AKEYCODE_BUTTON_THUMBR:
case C.AKEYCODE_BUTTON_START:
case C.AKEYCODE_BUTTON_SELECT:
case C.AKEYCODE_BUTTON_MODE:
case C.AKEYCODE_ESCAPE:
return key.CodeEscape
case C.AKEYCODE_FORWARD_DEL:
return key.CodeDeleteForward
case C.AKEYCODE_CTRL_LEFT:
return key.CodeLeftControl
case C.AKEYCODE_CTRL_RIGHT:
return key.CodeRightControl
case C.AKEYCODE_CAPS_LOCK:
return key.CodeCapsLock
case C.AKEYCODE_SCROLL_LOCK:
case C.AKEYCODE_META_LEFT:
return key.CodeLeftGUI
case C.AKEYCODE_META_RIGHT:
return key.CodeRightGUI
case C.AKEYCODE_FUNCTION:
case C.AKEYCODE_SYSRQ:
case C.AKEYCODE_BREAK:
case C.AKEYCODE_MOVE_HOME:
case C.AKEYCODE_MOVE_END:
case C.AKEYCODE_INSERT:
return key.CodeInsert
case C.AKEYCODE_FORWARD:
case C.AKEYCODE_MEDIA_PLAY:
case C.AKEYCODE_MEDIA_PAUSE:
case C.AKEYCODE_MEDIA_CLOSE:
case C.AKEYCODE_MEDIA_EJECT:
case C.AKEYCODE_MEDIA_RECORD:
case C.AKEYCODE_F1:
return key.CodeF1
case C.AKEYCODE_F2:
return key.CodeF2
case C.AKEYCODE_F3:
return key.CodeF3
case C.AKEYCODE_F4:
return key.CodeF4
case C.AKEYCODE_F5:
return key.CodeF5
case C.AKEYCODE_F6:
return key.CodeF6
case C.AKEYCODE_F7:
return key.CodeF7
case C.AKEYCODE_F8:
return key.CodeF8
case C.AKEYCODE_F9:
return key.CodeF9
case C.AKEYCODE_F10:
return key.CodeF10
case C.AKEYCODE_F11:
return key.CodeF11
case C.AKEYCODE_F12:
return key.CodeF12
case C.AKEYCODE_NUM_LOCK:
return key.CodeKeypadNumLock
case C.AKEYCODE_NUMPAD_0:
return key.CodeKeypad0
case C.AKEYCODE_NUMPAD_1:
return key.CodeKeypad1
case C.AKEYCODE_NUMPAD_2:
return key.CodeKeypad2
case C.AKEYCODE_NUMPAD_3:
return key.CodeKeypad3
case C.AKEYCODE_NUMPAD_4:
return key.CodeKeypad4
case C.AKEYCODE_NUMPAD_5:
return key.CodeKeypad5
case C.AKEYCODE_NUMPAD_6:
return key.CodeKeypad6
case C.AKEYCODE_NUMPAD_7:
return key.CodeKeypad7
case C.AKEYCODE_NUMPAD_8:
return key.CodeKeypad8
case C.AKEYCODE_NUMPAD_9:
return key.CodeKeypad9
case C.AKEYCODE_NUMPAD_DIVIDE:
return key.CodeKeypadSlash
case C.AKEYCODE_NUMPAD_MULTIPLY:
return key.CodeKeypadAsterisk
case C.AKEYCODE_NUMPAD_SUBTRACT:
return key.CodeKeypadHyphenMinus
case C.AKEYCODE_NUMPAD_ADD:
return key.CodeKeypadPlusSign
case C.AKEYCODE_NUMPAD_DOT:
return key.CodeKeypadFullStop
case C.AKEYCODE_NUMPAD_COMMA:
case C.AKEYCODE_NUMPAD_ENTER:
return key.CodeKeypadEnter
case C.AKEYCODE_NUMPAD_EQUALS:
return key.CodeKeypadEqualSign
case C.AKEYCODE_NUMPAD_LEFT_PAREN:
case C.AKEYCODE_NUMPAD_RIGHT_PAREN:
case C.AKEYCODE_VOLUME_MUTE:
return key.CodeMute
case C.AKEYCODE_INFO:
case C.AKEYCODE_CHANNEL_UP:
case C.AKEYCODE_CHANNEL_DOWN:
case C.AKEYCODE_ZOOM_IN:
case C.AKEYCODE_ZOOM_OUT:
case C.AKEYCODE_TV:
case C.AKEYCODE_WINDOW:
case C.AKEYCODE_GUIDE:
case C.AKEYCODE_DVR:
case C.AKEYCODE_BOOKMARK:
case C.AKEYCODE_CAPTIONS:
case C.AKEYCODE_SETTINGS:
case C.AKEYCODE_TV_POWER:
case C.AKEYCODE_TV_INPUT:
case C.AKEYCODE_STB_POWER:
case C.AKEYCODE_STB_INPUT:
case C.AKEYCODE_AVR_POWER:
case C.AKEYCODE_AVR_INPUT:
case C.AKEYCODE_PROG_RED:
case C.AKEYCODE_PROG_GREEN:
case C.AKEYCODE_PROG_YELLOW:
case C.AKEYCODE_PROG_BLUE:
case C.AKEYCODE_APP_SWITCH:
case C.AKEYCODE_BUTTON_1:
case C.AKEYCODE_BUTTON_2:
case C.AKEYCODE_BUTTON_3:
case C.AKEYCODE_BUTTON_4:
case C.AKEYCODE_BUTTON_5:
case C.AKEYCODE_BUTTON_6:
case C.AKEYCODE_BUTTON_7:
case C.AKEYCODE_BUTTON_8:
case C.AKEYCODE_BUTTON_9:
case C.AKEYCODE_BUTTON_10:
case C.AKEYCODE_BUTTON_11:
case C.AKEYCODE_BUTTON_12:
case C.AKEYCODE_BUTTON_13:
case C.AKEYCODE_BUTTON_14:
case C.AKEYCODE_BUTTON_15:
case C.AKEYCODE_BUTTON_16:
case C.AKEYCODE_LANGUAGE_SWITCH:
case C.AKEYCODE_MANNER_MODE:
case C.AKEYCODE_3D_MODE:
case C.AKEYCODE_CONTACTS:
case C.AKEYCODE_CALENDAR:
case C.AKEYCODE_MUSIC:
case C.AKEYCODE_CALCULATOR:
}
/* Defined in an NDK API version beyond what we use today:
C.AKEYCODE_ASSIST
C.AKEYCODE_BRIGHTNESS_DOWN
C.AKEYCODE_BRIGHTNESS_UP
C.AKEYCODE_EISU
C.AKEYCODE_HENKAN
C.AKEYCODE_KANA
C.AKEYCODE_KATAKANA_HIRAGANA
C.AKEYCODE_MEDIA_AUDIO_TRACK
C.AKEYCODE_MUHENKAN
C.AKEYCODE_RO
C.AKEYCODE_YEN
C.AKEYCODE_ZENKAKU_HANKAKU
*/
return key.CodeUnknown
}