mobile/app/android.go

806 строки
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/input.h>
#include <android/keycodes.h>
#include <android/looper.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/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, window *C.ANativeWindow) {
}
//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) {
C.AInputQueue_detachLooper(q)
inputQueue <- q
<-inputQueueDone
}
//export onInputQueueDestroyed
func onInputQueueDestroyed(activity *C.ANativeActivity, q *C.AInputQueue) {
inputQueue <- nil
<-inputQueueDone
}
//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)
inputQueueDone = make(chan struct{})
windowDestroyed = make(chan *C.ANativeWindow)
windowRedrawNeeded = make(chan *C.ANativeWindow)
windowRedrawDone = make(chan struct{})
windowConfigChange = make(chan windowConfig)
)
func init() {
theApp.registerGLViewportFilter()
}
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
if err := mobileinit.RunOnJVM(mainUI); err != nil {
log.Fatalf("app: %v", err)
}
}
var mainUserFn func(App)
func mainUI(vm, jniEnv, ctx uintptr) error {
workAvailable := theApp.worker.WorkAvailable()
donec := make(chan struct{})
go func() {
mainUserFn(theApp)
close(donec)
}()
var pixelsPerPt float32
var orientation size.Orientation
for {
select {
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())
}
}
theApp.sendLifecycle(lifecycle.StageFocused)
widthPx := int(C.ANativeWindow_getWidth(w))
heightPx := int(C.ANativeWindow_getHeight(w))
theApp.eventsIn <- size.Event{
WidthPx: widthPx,
HeightPx: heightPx,
WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt),
HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt),
PixelsPerPt: pixelsPerPt,
Orientation: orientation,
}
theApp.eventsIn <- paint.Event{External: true}
case <-windowDestroyed:
if C.surface != nil {
if errStr := C.destroyEGLSurface(); errStr != nil {
return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError())
}
}
C.surface = nil
theApp.sendLifecycle(lifecycle.StageAlive)
case <-workAvailable:
theApp.worker.DoWork()
case <-theApp.publish:
// TODO: compare a generation number to redrawGen for stale paints?
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:
}
theApp.publishResult <- PublishResult{}
}
}
}
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 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)
}
}()
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)
}
q = p
if q != nil {
C.AInputQueue_attachLooper(q, l, 0, nil, nil)
}
inputQueueDone <- struct{}{}
}
}
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)
}
}
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
}
theApp.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.
theApp.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
}