github.com/shranet/mobile@v0.0.0-20200814083559-5702cdcd481b/app/android.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build android
     6  
     7  /*
     8  Android Apps are built with -buildmode=c-shared. They are loaded by a
     9  running Java process.
    10  
    11  Before any entry point is reached, a global constructor initializes the
    12  Go runtime, calling all Go init functions. All cgo calls will block
    13  until this is complete. Next JNI_OnLoad is called. When that is
    14  complete, one of two entry points is called.
    15  
    16  All-Go apps built using NativeActivity enter at ANativeActivity_onCreate.
    17  
    18  Go libraries (for example, those built with gomobile bind) do not use
    19  the app package initialization.
    20  */
    21  
    22  package app
    23  
    24  /*
    25  #cgo LDFLAGS: -landroid -llog -lEGL -lGLESv2
    26  
    27  #include <android/configuration.h>
    28  #include <android/input.h>
    29  #include <android/keycodes.h>
    30  #include <android/looper.h>
    31  #include <android/native_activity.h>
    32  #include <android/native_window.h>
    33  #include <EGL/egl.h>
    34  #include <jni.h>
    35  #include <pthread.h>
    36  #include <stdlib.h>
    37  
    38  EGLDisplay display;
    39  EGLSurface surface;
    40  
    41  char* createEGLSurface(ANativeWindow* window);
    42  char* destroyEGLSurface();
    43  int32_t getKeyRune(JNIEnv* env, AInputEvent* e);
    44  */
    45  import "C"
    46  import (
    47  	"fmt"
    48  	"log"
    49  	"os"
    50  	"time"
    51  	"unsafe"
    52  
    53  	"github.com/shranet/mobile/app/internal/callfn"
    54  	"github.com/shranet/mobile/event/key"
    55  	"github.com/shranet/mobile/event/lifecycle"
    56  	"github.com/shranet/mobile/event/paint"
    57  	"github.com/shranet/mobile/event/size"
    58  	"github.com/shranet/mobile/event/touch"
    59  	"github.com/shranet/mobile/geom"
    60  	"github.com/shranet/mobile/internal/mobileinit"
    61  )
    62  
    63  // RunOnJVM runs fn on a new goroutine locked to an OS thread with a JNIEnv.
    64  //
    65  // RunOnJVM blocks until the call to fn is complete. Any Java
    66  // exception or failure to attach to the JVM is returned as an error.
    67  //
    68  // The function fn takes vm, the current JavaVM*,
    69  // env, the current JNIEnv*, and
    70  // ctx, a jobject representing the global android.context.Context.
    71  func RunOnJVM(fn func(vm, jniEnv, ctx uintptr) error) error {
    72  	return mobileinit.RunOnJVM(fn)
    73  }
    74  
    75  //export setCurrentContext
    76  func setCurrentContext(vm *C.JavaVM, ctx C.jobject) {
    77  	mobileinit.SetCurrentContext(unsafe.Pointer(vm), uintptr(ctx))
    78  }
    79  
    80  //export callMain
    81  func callMain(mainPC uintptr) {
    82  	for _, name := range []string{"TMPDIR", "PATH", "LD_LIBRARY_PATH"} {
    83  		n := C.CString(name)
    84  		os.Setenv(name, C.GoString(C.getenv(n)))
    85  		C.free(unsafe.Pointer(n))
    86  	}
    87  
    88  	// Set timezone.
    89  	//
    90  	// Note that Android zoneinfo is stored in /system/usr/share/zoneinfo,
    91  	// but it is in some kind of packed TZiff file that we do not support
    92  	// yet. As a stopgap, we build a fixed zone using the tm_zone name.
    93  	var curtime C.time_t
    94  	var curtm C.struct_tm
    95  	C.time(&curtime)
    96  	C.localtime_r(&curtime, &curtm)
    97  	tzOffset := int(curtm.tm_gmtoff)
    98  	tz := C.GoString(curtm.tm_zone)
    99  	time.Local = time.FixedZone(tz, tzOffset)
   100  
   101  	go callfn.CallFn(mainPC)
   102  }
   103  
   104  //export callFunc
   105  func callFunc(fn uintptr) {
   106  	go callfn.CallFn(fn)
   107  }
   108  
   109  //export onStart
   110  func onStart(activity *C.ANativeActivity) {
   111  }
   112  
   113  //export onResume
   114  func onResume(activity *C.ANativeActivity) {
   115  }
   116  
   117  //export onSaveInstanceState
   118  func onSaveInstanceState(activity *C.ANativeActivity, outSize *C.size_t) unsafe.Pointer {
   119  	return nil
   120  }
   121  
   122  //export onPause
   123  func onPause(activity *C.ANativeActivity) {
   124  }
   125  
   126  //export onStop
   127  func onStop(activity *C.ANativeActivity) {
   128  }
   129  
   130  //export onCreate
   131  func onCreate(activity *C.ANativeActivity) {
   132  	// Set the initial configuration.
   133  	//
   134  	// Note we use unbuffered channels to talk to the activity loop, and
   135  	// NativeActivity calls these callbacks sequentially, so configuration
   136  	// will be set before <-windowRedrawNeeded is processed.
   137  	windowConfigChange <- windowConfigRead(activity)
   138  }
   139  
   140  //export onDestroy
   141  func onDestroy(activity *C.ANativeActivity) {
   142  }
   143  
   144  //export onWindowFocusChanged
   145  func onWindowFocusChanged(activity *C.ANativeActivity, hasFocus C.int) {
   146  }
   147  
   148  //export onNativeWindowCreated
   149  func onNativeWindowCreated(activity *C.ANativeActivity, window *C.ANativeWindow) {
   150  }
   151  
   152  //export onNativeWindowRedrawNeeded
   153  func onNativeWindowRedrawNeeded(activity *C.ANativeActivity, window *C.ANativeWindow) {
   154  	// Called on orientation change and window resize.
   155  	// Send a request for redraw, and block this function
   156  	// until a complete draw and buffer swap is completed.
   157  	// This is required by the redraw documentation to
   158  	// avoid bad draws.
   159  	windowRedrawNeeded <- window
   160  	<-windowRedrawDone
   161  }
   162  
   163  //export onNativeWindowDestroyed
   164  func onNativeWindowDestroyed(activity *C.ANativeActivity, window *C.ANativeWindow) {
   165  	windowDestroyed <- window
   166  }
   167  
   168  //export onInputQueueCreated
   169  func onInputQueueCreated(activity *C.ANativeActivity, q *C.AInputQueue) {
   170  	inputQueue <- q
   171  	<-inputQueueDone
   172  }
   173  
   174  //export onInputQueueDestroyed
   175  func onInputQueueDestroyed(activity *C.ANativeActivity, q *C.AInputQueue) {
   176  	inputQueue <- nil
   177  	<-inputQueueDone
   178  }
   179  
   180  //export onContentRectChanged
   181  func onContentRectChanged(activity *C.ANativeActivity, rect *C.ARect) {
   182  }
   183  
   184  type windowConfig struct {
   185  	orientation size.Orientation
   186  	pixelsPerPt float32
   187  }
   188  
   189  func windowConfigRead(activity *C.ANativeActivity) windowConfig {
   190  	aconfig := C.AConfiguration_new()
   191  	C.AConfiguration_fromAssetManager(aconfig, activity.assetManager)
   192  	orient := C.AConfiguration_getOrientation(aconfig)
   193  	density := C.AConfiguration_getDensity(aconfig)
   194  	C.AConfiguration_delete(aconfig)
   195  
   196  	// Calculate the screen resolution. This value is approximate. For example,
   197  	// a physical resolution of 200 DPI may be quantized to one of the
   198  	// ACONFIGURATION_DENSITY_XXX values such as 160 or 240.
   199  	//
   200  	// A more accurate DPI could possibly be calculated from
   201  	// https://developer.android.com/reference/android/util/DisplayMetrics.html#xdpi
   202  	// but this does not appear to be accessible via the NDK. In any case, the
   203  	// hardware might not even provide a more accurate number, as the system
   204  	// does not apparently use the reported value. See golang.org/issue/13366
   205  	// for a discussion.
   206  	var dpi int
   207  	switch density {
   208  	case C.ACONFIGURATION_DENSITY_DEFAULT:
   209  		dpi = 160
   210  	case C.ACONFIGURATION_DENSITY_LOW,
   211  		C.ACONFIGURATION_DENSITY_MEDIUM,
   212  		213, // C.ACONFIGURATION_DENSITY_TV
   213  		C.ACONFIGURATION_DENSITY_HIGH,
   214  		320, // ACONFIGURATION_DENSITY_XHIGH
   215  		480, // ACONFIGURATION_DENSITY_XXHIGH
   216  		640: // ACONFIGURATION_DENSITY_XXXHIGH
   217  		dpi = int(density)
   218  	case C.ACONFIGURATION_DENSITY_NONE:
   219  		log.Print("android device reports no screen density")
   220  		dpi = 72
   221  	default:
   222  		log.Printf("android device reports unknown density: %d", density)
   223  		// All we can do is guess.
   224  		if density > 0 {
   225  			dpi = int(density)
   226  		} else {
   227  			dpi = 72
   228  		}
   229  	}
   230  
   231  	o := size.OrientationUnknown
   232  	switch orient {
   233  	case C.ACONFIGURATION_ORIENTATION_PORT:
   234  		o = size.OrientationPortrait
   235  	case C.ACONFIGURATION_ORIENTATION_LAND:
   236  		o = size.OrientationLandscape
   237  	}
   238  
   239  	return windowConfig{
   240  		orientation: o,
   241  		pixelsPerPt: float32(dpi) / 72,
   242  	}
   243  }
   244  
   245  //export onConfigurationChanged
   246  func onConfigurationChanged(activity *C.ANativeActivity) {
   247  	// A rotation event first triggers onConfigurationChanged, then
   248  	// calls onNativeWindowRedrawNeeded. We extract the orientation
   249  	// here and save it for the redraw event.
   250  	windowConfigChange <- windowConfigRead(activity)
   251  }
   252  
   253  //export onLowMemory
   254  func onLowMemory(activity *C.ANativeActivity) {
   255  }
   256  
   257  var (
   258  	inputQueue         = make(chan *C.AInputQueue)
   259  	inputQueueDone     = make(chan struct{})
   260  	windowDestroyed    = make(chan *C.ANativeWindow)
   261  	windowRedrawNeeded = make(chan *C.ANativeWindow)
   262  	windowRedrawDone   = make(chan struct{})
   263  	windowConfigChange = make(chan windowConfig)
   264  )
   265  
   266  func init() {
   267  	theApp.registerGLViewportFilter()
   268  }
   269  
   270  func main(f func(App)) {
   271  	mainUserFn = f
   272  	// TODO: merge the runInputQueue and mainUI functions?
   273  	go func() {
   274  		if err := mobileinit.RunOnJVM(runInputQueue); err != nil {
   275  			log.Fatalf("app: %v", err)
   276  		}
   277  	}()
   278  	// Preserve this OS thread for:
   279  	//	1. the attached JNI thread
   280  	//	2. the GL context
   281  	if err := mobileinit.RunOnJVM(mainUI); err != nil {
   282  		log.Fatalf("app: %v", err)
   283  	}
   284  }
   285  
   286  var mainUserFn func(App)
   287  
   288  func mainUI(vm, jniEnv, ctx uintptr) error {
   289  	workAvailable := theApp.worker.WorkAvailable()
   290  
   291  	donec := make(chan struct{})
   292  	go func() {
   293  		mainUserFn(theApp)
   294  		close(donec)
   295  	}()
   296  
   297  	var pixelsPerPt float32
   298  	var orientation size.Orientation
   299  
   300  	for {
   301  		select {
   302  		case <-donec:
   303  			return nil
   304  		case cfg := <-windowConfigChange:
   305  			pixelsPerPt = cfg.pixelsPerPt
   306  			orientation = cfg.orientation
   307  		case w := <-windowRedrawNeeded:
   308  			if C.surface == nil {
   309  				if errStr := C.createEGLSurface(w); errStr != nil {
   310  					return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError())
   311  				}
   312  			}
   313  			theApp.sendLifecycle(lifecycle.StageFocused)
   314  			widthPx := int(C.ANativeWindow_getWidth(w))
   315  			heightPx := int(C.ANativeWindow_getHeight(w))
   316  			theApp.eventsIn <- size.Event{
   317  				WidthPx:     widthPx,
   318  				HeightPx:    heightPx,
   319  				WidthPt:     geom.Pt(float32(widthPx) / pixelsPerPt),
   320  				HeightPt:    geom.Pt(float32(heightPx) / pixelsPerPt),
   321  				PixelsPerPt: pixelsPerPt,
   322  				Orientation: orientation,
   323  			}
   324  			theApp.eventsIn <- paint.Event{External: true}
   325  		case <-windowDestroyed:
   326  			if C.surface != nil {
   327  				if errStr := C.destroyEGLSurface(); errStr != nil {
   328  					return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError())
   329  				}
   330  			}
   331  			C.surface = nil
   332  			theApp.sendLifecycle(lifecycle.StageAlive)
   333  		case <-workAvailable:
   334  			theApp.worker.DoWork()
   335  		case <-theApp.publish:
   336  			// TODO: compare a generation number to redrawGen for stale paints?
   337  			if C.surface != nil {
   338  				// eglSwapBuffers blocks until vsync.
   339  				if C.eglSwapBuffers(C.display, C.surface) == C.EGL_FALSE {
   340  					log.Printf("app: failed to swap buffers (%s)", eglGetError())
   341  				}
   342  			}
   343  			select {
   344  			case windowRedrawDone <- struct{}{}:
   345  			default:
   346  			}
   347  			theApp.publishResult <- PublishResult{}
   348  		}
   349  	}
   350  }
   351  
   352  func runInputQueue(vm, jniEnv, ctx uintptr) error {
   353  	env := (*C.JNIEnv)(unsafe.Pointer(jniEnv)) // not a Go heap pointer
   354  
   355  	// Android loopers select on OS file descriptors, not Go channels, so we
   356  	// translate the inputQueue channel to an ALooper_wake call.
   357  	l := C.ALooper_prepare(C.ALOOPER_PREPARE_ALLOW_NON_CALLBACKS)
   358  	pending := make(chan *C.AInputQueue, 1)
   359  	go func() {
   360  		for q := range inputQueue {
   361  			pending <- q
   362  			C.ALooper_wake(l)
   363  		}
   364  	}()
   365  
   366  	var q *C.AInputQueue
   367  	for {
   368  		if C.ALooper_pollAll(-1, nil, nil, nil) == C.ALOOPER_POLL_WAKE {
   369  			select {
   370  			default:
   371  			case p := <-pending:
   372  				if q != nil {
   373  					processEvents(env, q)
   374  					C.AInputQueue_detachLooper(q)
   375  				}
   376  				q = p
   377  				if q != nil {
   378  					C.AInputQueue_attachLooper(q, l, 0, nil, nil)
   379  				}
   380  				inputQueueDone <- struct{}{}
   381  			}
   382  		}
   383  		if q != nil {
   384  			processEvents(env, q)
   385  		}
   386  	}
   387  }
   388  
   389  func processEvents(env *C.JNIEnv, q *C.AInputQueue) {
   390  	var e *C.AInputEvent
   391  	for C.AInputQueue_getEvent(q, &e) >= 0 {
   392  		if C.AInputQueue_preDispatchEvent(q, e) != 0 {
   393  			continue
   394  		}
   395  		processEvent(env, e)
   396  		C.AInputQueue_finishEvent(q, e, 0)
   397  	}
   398  }
   399  
   400  func processEvent(env *C.JNIEnv, e *C.AInputEvent) {
   401  	switch C.AInputEvent_getType(e) {
   402  	case C.AINPUT_EVENT_TYPE_KEY:
   403  		processKey(env, e)
   404  	case C.AINPUT_EVENT_TYPE_MOTION:
   405  		// At most one of the events in this batch is an up or down event; get its index and change.
   406  		upDownIndex := C.size_t(C.AMotionEvent_getAction(e)&C.AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> C.AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT
   407  		upDownType := touch.TypeMove
   408  		switch C.AMotionEvent_getAction(e) & C.AMOTION_EVENT_ACTION_MASK {
   409  		case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
   410  			upDownType = touch.TypeBegin
   411  		case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
   412  			upDownType = touch.TypeEnd
   413  		}
   414  
   415  		for i, n := C.size_t(0), C.AMotionEvent_getPointerCount(e); i < n; i++ {
   416  			t := touch.TypeMove
   417  			if i == upDownIndex {
   418  				t = upDownType
   419  			}
   420  			theApp.eventsIn <- touch.Event{
   421  				X:        float32(C.AMotionEvent_getX(e, i)),
   422  				Y:        float32(C.AMotionEvent_getY(e, i)),
   423  				Sequence: touch.Sequence(C.AMotionEvent_getPointerId(e, i)),
   424  				Type:     t,
   425  			}
   426  		}
   427  	default:
   428  		log.Printf("unknown input event, type=%d", C.AInputEvent_getType(e))
   429  	}
   430  }
   431  
   432  func processKey(env *C.JNIEnv, e *C.AInputEvent) {
   433  	deviceID := C.AInputEvent_getDeviceId(e)
   434  	if deviceID == 0 {
   435  		// Software keyboard input, leaving for scribe/IME.
   436  		return
   437  	}
   438  
   439  	k := key.Event{
   440  		Rune: rune(C.getKeyRune(env, e)),
   441  		Code: convAndroidKeyCode(int32(C.AKeyEvent_getKeyCode(e))),
   442  	}
   443  	switch C.AKeyEvent_getAction(e) {
   444  	case C.AKEY_EVENT_ACTION_DOWN:
   445  		k.Direction = key.DirPress
   446  	case C.AKEY_EVENT_ACTION_UP:
   447  		k.Direction = key.DirRelease
   448  	default:
   449  		k.Direction = key.DirNone
   450  	}
   451  	// TODO(crawshaw): set Modifiers.
   452  	theApp.eventsIn <- k
   453  }
   454  
   455  func eglGetError() string {
   456  	switch errNum := C.eglGetError(); errNum {
   457  	case C.EGL_SUCCESS:
   458  		return "EGL_SUCCESS"
   459  	case C.EGL_NOT_INITIALIZED:
   460  		return "EGL_NOT_INITIALIZED"
   461  	case C.EGL_BAD_ACCESS:
   462  		return "EGL_BAD_ACCESS"
   463  	case C.EGL_BAD_ALLOC:
   464  		return "EGL_BAD_ALLOC"
   465  	case C.EGL_BAD_ATTRIBUTE:
   466  		return "EGL_BAD_ATTRIBUTE"
   467  	case C.EGL_BAD_CONTEXT:
   468  		return "EGL_BAD_CONTEXT"
   469  	case C.EGL_BAD_CONFIG:
   470  		return "EGL_BAD_CONFIG"
   471  	case C.EGL_BAD_CURRENT_SURFACE:
   472  		return "EGL_BAD_CURRENT_SURFACE"
   473  	case C.EGL_BAD_DISPLAY:
   474  		return "EGL_BAD_DISPLAY"
   475  	case C.EGL_BAD_SURFACE:
   476  		return "EGL_BAD_SURFACE"
   477  	case C.EGL_BAD_MATCH:
   478  		return "EGL_BAD_MATCH"
   479  	case C.EGL_BAD_PARAMETER:
   480  		return "EGL_BAD_PARAMETER"
   481  	case C.EGL_BAD_NATIVE_PIXMAP:
   482  		return "EGL_BAD_NATIVE_PIXMAP"
   483  	case C.EGL_BAD_NATIVE_WINDOW:
   484  		return "EGL_BAD_NATIVE_WINDOW"
   485  	case C.EGL_CONTEXT_LOST:
   486  		return "EGL_CONTEXT_LOST"
   487  	default:
   488  		return fmt.Sprintf("Unknown EGL err: %d", errNum)
   489  	}
   490  }
   491  
   492  func convAndroidKeyCode(aKeyCode int32) key.Code {
   493  	// Many Android key codes do not map into USB HID codes.
   494  	// For those, key.CodeUnknown is returned. This switch has all
   495  	// cases, even the unknown ones, to serve as a documentation
   496  	// and search aid.
   497  	switch aKeyCode {
   498  	case C.AKEYCODE_UNKNOWN:
   499  	case C.AKEYCODE_SOFT_LEFT:
   500  	case C.AKEYCODE_SOFT_RIGHT:
   501  	case C.AKEYCODE_HOME:
   502  		return key.CodeHome
   503  	case C.AKEYCODE_BACK:
   504  	case C.AKEYCODE_CALL:
   505  	case C.AKEYCODE_ENDCALL:
   506  	case C.AKEYCODE_0:
   507  		return key.Code0
   508  	case C.AKEYCODE_1:
   509  		return key.Code1
   510  	case C.AKEYCODE_2:
   511  		return key.Code2
   512  	case C.AKEYCODE_3:
   513  		return key.Code3
   514  	case C.AKEYCODE_4:
   515  		return key.Code4
   516  	case C.AKEYCODE_5:
   517  		return key.Code5
   518  	case C.AKEYCODE_6:
   519  		return key.Code6
   520  	case C.AKEYCODE_7:
   521  		return key.Code7
   522  	case C.AKEYCODE_8:
   523  		return key.Code8
   524  	case C.AKEYCODE_9:
   525  		return key.Code9
   526  	case C.AKEYCODE_STAR:
   527  	case C.AKEYCODE_POUND:
   528  	case C.AKEYCODE_DPAD_UP:
   529  	case C.AKEYCODE_DPAD_DOWN:
   530  	case C.AKEYCODE_DPAD_LEFT:
   531  	case C.AKEYCODE_DPAD_RIGHT:
   532  	case C.AKEYCODE_DPAD_CENTER:
   533  	case C.AKEYCODE_VOLUME_UP:
   534  		return key.CodeVolumeUp
   535  	case C.AKEYCODE_VOLUME_DOWN:
   536  		return key.CodeVolumeDown
   537  	case C.AKEYCODE_POWER:
   538  	case C.AKEYCODE_CAMERA:
   539  	case C.AKEYCODE_CLEAR:
   540  	case C.AKEYCODE_A:
   541  		return key.CodeA
   542  	case C.AKEYCODE_B:
   543  		return key.CodeB
   544  	case C.AKEYCODE_C:
   545  		return key.CodeC
   546  	case C.AKEYCODE_D:
   547  		return key.CodeD
   548  	case C.AKEYCODE_E:
   549  		return key.CodeE
   550  	case C.AKEYCODE_F:
   551  		return key.CodeF
   552  	case C.AKEYCODE_G:
   553  		return key.CodeG
   554  	case C.AKEYCODE_H:
   555  		return key.CodeH
   556  	case C.AKEYCODE_I:
   557  		return key.CodeI
   558  	case C.AKEYCODE_J:
   559  		return key.CodeJ
   560  	case C.AKEYCODE_K:
   561  		return key.CodeK
   562  	case C.AKEYCODE_L:
   563  		return key.CodeL
   564  	case C.AKEYCODE_M:
   565  		return key.CodeM
   566  	case C.AKEYCODE_N:
   567  		return key.CodeN
   568  	case C.AKEYCODE_O:
   569  		return key.CodeO
   570  	case C.AKEYCODE_P:
   571  		return key.CodeP
   572  	case C.AKEYCODE_Q:
   573  		return key.CodeQ
   574  	case C.AKEYCODE_R:
   575  		return key.CodeR
   576  	case C.AKEYCODE_S:
   577  		return key.CodeS
   578  	case C.AKEYCODE_T:
   579  		return key.CodeT
   580  	case C.AKEYCODE_U:
   581  		return key.CodeU
   582  	case C.AKEYCODE_V:
   583  		return key.CodeV
   584  	case C.AKEYCODE_W:
   585  		return key.CodeW
   586  	case C.AKEYCODE_X:
   587  		return key.CodeX
   588  	case C.AKEYCODE_Y:
   589  		return key.CodeY
   590  	case C.AKEYCODE_Z:
   591  		return key.CodeZ
   592  	case C.AKEYCODE_COMMA:
   593  		return key.CodeComma
   594  	case C.AKEYCODE_PERIOD:
   595  		return key.CodeFullStop
   596  	case C.AKEYCODE_ALT_LEFT:
   597  		return key.CodeLeftAlt
   598  	case C.AKEYCODE_ALT_RIGHT:
   599  		return key.CodeRightAlt
   600  	case C.AKEYCODE_SHIFT_LEFT:
   601  		return key.CodeLeftShift
   602  	case C.AKEYCODE_SHIFT_RIGHT:
   603  		return key.CodeRightShift
   604  	case C.AKEYCODE_TAB:
   605  		return key.CodeTab
   606  	case C.AKEYCODE_SPACE:
   607  		return key.CodeSpacebar
   608  	case C.AKEYCODE_SYM:
   609  	case C.AKEYCODE_EXPLORER:
   610  	case C.AKEYCODE_ENVELOPE:
   611  	case C.AKEYCODE_ENTER:
   612  		return key.CodeReturnEnter
   613  	case C.AKEYCODE_DEL:
   614  		return key.CodeDeleteBackspace
   615  	case C.AKEYCODE_GRAVE:
   616  		return key.CodeGraveAccent
   617  	case C.AKEYCODE_MINUS:
   618  		return key.CodeHyphenMinus
   619  	case C.AKEYCODE_EQUALS:
   620  		return key.CodeEqualSign
   621  	case C.AKEYCODE_LEFT_BRACKET:
   622  		return key.CodeLeftSquareBracket
   623  	case C.AKEYCODE_RIGHT_BRACKET:
   624  		return key.CodeRightSquareBracket
   625  	case C.AKEYCODE_BACKSLASH:
   626  		return key.CodeBackslash
   627  	case C.AKEYCODE_SEMICOLON:
   628  		return key.CodeSemicolon
   629  	case C.AKEYCODE_APOSTROPHE:
   630  		return key.CodeApostrophe
   631  	case C.AKEYCODE_SLASH:
   632  		return key.CodeSlash
   633  	case C.AKEYCODE_AT:
   634  	case C.AKEYCODE_NUM:
   635  	case C.AKEYCODE_HEADSETHOOK:
   636  	case C.AKEYCODE_FOCUS:
   637  	case C.AKEYCODE_PLUS:
   638  	case C.AKEYCODE_MENU:
   639  	case C.AKEYCODE_NOTIFICATION:
   640  	case C.AKEYCODE_SEARCH:
   641  	case C.AKEYCODE_MEDIA_PLAY_PAUSE:
   642  	case C.AKEYCODE_MEDIA_STOP:
   643  	case C.AKEYCODE_MEDIA_NEXT:
   644  	case C.AKEYCODE_MEDIA_PREVIOUS:
   645  	case C.AKEYCODE_MEDIA_REWIND:
   646  	case C.AKEYCODE_MEDIA_FAST_FORWARD:
   647  	case C.AKEYCODE_MUTE:
   648  	case C.AKEYCODE_PAGE_UP:
   649  		return key.CodePageUp
   650  	case C.AKEYCODE_PAGE_DOWN:
   651  		return key.CodePageDown
   652  	case C.AKEYCODE_PICTSYMBOLS:
   653  	case C.AKEYCODE_SWITCH_CHARSET:
   654  	case C.AKEYCODE_BUTTON_A:
   655  	case C.AKEYCODE_BUTTON_B:
   656  	case C.AKEYCODE_BUTTON_C:
   657  	case C.AKEYCODE_BUTTON_X:
   658  	case C.AKEYCODE_BUTTON_Y:
   659  	case C.AKEYCODE_BUTTON_Z:
   660  	case C.AKEYCODE_BUTTON_L1:
   661  	case C.AKEYCODE_BUTTON_R1:
   662  	case C.AKEYCODE_BUTTON_L2:
   663  	case C.AKEYCODE_BUTTON_R2:
   664  	case C.AKEYCODE_BUTTON_THUMBL:
   665  	case C.AKEYCODE_BUTTON_THUMBR:
   666  	case C.AKEYCODE_BUTTON_START:
   667  	case C.AKEYCODE_BUTTON_SELECT:
   668  	case C.AKEYCODE_BUTTON_MODE:
   669  	case C.AKEYCODE_ESCAPE:
   670  		return key.CodeEscape
   671  	case C.AKEYCODE_FORWARD_DEL:
   672  		return key.CodeDeleteForward
   673  	case C.AKEYCODE_CTRL_LEFT:
   674  		return key.CodeLeftControl
   675  	case C.AKEYCODE_CTRL_RIGHT:
   676  		return key.CodeRightControl
   677  	case C.AKEYCODE_CAPS_LOCK:
   678  		return key.CodeCapsLock
   679  	case C.AKEYCODE_SCROLL_LOCK:
   680  	case C.AKEYCODE_META_LEFT:
   681  		return key.CodeLeftGUI
   682  	case C.AKEYCODE_META_RIGHT:
   683  		return key.CodeRightGUI
   684  	case C.AKEYCODE_FUNCTION:
   685  	case C.AKEYCODE_SYSRQ:
   686  	case C.AKEYCODE_BREAK:
   687  	case C.AKEYCODE_MOVE_HOME:
   688  	case C.AKEYCODE_MOVE_END:
   689  	case C.AKEYCODE_INSERT:
   690  		return key.CodeInsert
   691  	case C.AKEYCODE_FORWARD:
   692  	case C.AKEYCODE_MEDIA_PLAY:
   693  	case C.AKEYCODE_MEDIA_PAUSE:
   694  	case C.AKEYCODE_MEDIA_CLOSE:
   695  	case C.AKEYCODE_MEDIA_EJECT:
   696  	case C.AKEYCODE_MEDIA_RECORD:
   697  	case C.AKEYCODE_F1:
   698  		return key.CodeF1
   699  	case C.AKEYCODE_F2:
   700  		return key.CodeF2
   701  	case C.AKEYCODE_F3:
   702  		return key.CodeF3
   703  	case C.AKEYCODE_F4:
   704  		return key.CodeF4
   705  	case C.AKEYCODE_F5:
   706  		return key.CodeF5
   707  	case C.AKEYCODE_F6:
   708  		return key.CodeF6
   709  	case C.AKEYCODE_F7:
   710  		return key.CodeF7
   711  	case C.AKEYCODE_F8:
   712  		return key.CodeF8
   713  	case C.AKEYCODE_F9:
   714  		return key.CodeF9
   715  	case C.AKEYCODE_F10:
   716  		return key.CodeF10
   717  	case C.AKEYCODE_F11:
   718  		return key.CodeF11
   719  	case C.AKEYCODE_F12:
   720  		return key.CodeF12
   721  	case C.AKEYCODE_NUM_LOCK:
   722  		return key.CodeKeypadNumLock
   723  	case C.AKEYCODE_NUMPAD_0:
   724  		return key.CodeKeypad0
   725  	case C.AKEYCODE_NUMPAD_1:
   726  		return key.CodeKeypad1
   727  	case C.AKEYCODE_NUMPAD_2:
   728  		return key.CodeKeypad2
   729  	case C.AKEYCODE_NUMPAD_3:
   730  		return key.CodeKeypad3
   731  	case C.AKEYCODE_NUMPAD_4:
   732  		return key.CodeKeypad4
   733  	case C.AKEYCODE_NUMPAD_5:
   734  		return key.CodeKeypad5
   735  	case C.AKEYCODE_NUMPAD_6:
   736  		return key.CodeKeypad6
   737  	case C.AKEYCODE_NUMPAD_7:
   738  		return key.CodeKeypad7
   739  	case C.AKEYCODE_NUMPAD_8:
   740  		return key.CodeKeypad8
   741  	case C.AKEYCODE_NUMPAD_9:
   742  		return key.CodeKeypad9
   743  	case C.AKEYCODE_NUMPAD_DIVIDE:
   744  		return key.CodeKeypadSlash
   745  	case C.AKEYCODE_NUMPAD_MULTIPLY:
   746  		return key.CodeKeypadAsterisk
   747  	case C.AKEYCODE_NUMPAD_SUBTRACT:
   748  		return key.CodeKeypadHyphenMinus
   749  	case C.AKEYCODE_NUMPAD_ADD:
   750  		return key.CodeKeypadPlusSign
   751  	case C.AKEYCODE_NUMPAD_DOT:
   752  		return key.CodeKeypadFullStop
   753  	case C.AKEYCODE_NUMPAD_COMMA:
   754  	case C.AKEYCODE_NUMPAD_ENTER:
   755  		return key.CodeKeypadEnter
   756  	case C.AKEYCODE_NUMPAD_EQUALS:
   757  		return key.CodeKeypadEqualSign
   758  	case C.AKEYCODE_NUMPAD_LEFT_PAREN:
   759  	case C.AKEYCODE_NUMPAD_RIGHT_PAREN:
   760  	case C.AKEYCODE_VOLUME_MUTE:
   761  		return key.CodeMute
   762  	case C.AKEYCODE_INFO:
   763  	case C.AKEYCODE_CHANNEL_UP:
   764  	case C.AKEYCODE_CHANNEL_DOWN:
   765  	case C.AKEYCODE_ZOOM_IN:
   766  	case C.AKEYCODE_ZOOM_OUT:
   767  	case C.AKEYCODE_TV:
   768  	case C.AKEYCODE_WINDOW:
   769  	case C.AKEYCODE_GUIDE:
   770  	case C.AKEYCODE_DVR:
   771  	case C.AKEYCODE_BOOKMARK:
   772  	case C.AKEYCODE_CAPTIONS:
   773  	case C.AKEYCODE_SETTINGS:
   774  	case C.AKEYCODE_TV_POWER:
   775  	case C.AKEYCODE_TV_INPUT:
   776  	case C.AKEYCODE_STB_POWER:
   777  	case C.AKEYCODE_STB_INPUT:
   778  	case C.AKEYCODE_AVR_POWER:
   779  	case C.AKEYCODE_AVR_INPUT:
   780  	case C.AKEYCODE_PROG_RED:
   781  	case C.AKEYCODE_PROG_GREEN:
   782  	case C.AKEYCODE_PROG_YELLOW:
   783  	case C.AKEYCODE_PROG_BLUE:
   784  	case C.AKEYCODE_APP_SWITCH:
   785  	case C.AKEYCODE_BUTTON_1:
   786  	case C.AKEYCODE_BUTTON_2:
   787  	case C.AKEYCODE_BUTTON_3:
   788  	case C.AKEYCODE_BUTTON_4:
   789  	case C.AKEYCODE_BUTTON_5:
   790  	case C.AKEYCODE_BUTTON_6:
   791  	case C.AKEYCODE_BUTTON_7:
   792  	case C.AKEYCODE_BUTTON_8:
   793  	case C.AKEYCODE_BUTTON_9:
   794  	case C.AKEYCODE_BUTTON_10:
   795  	case C.AKEYCODE_BUTTON_11:
   796  	case C.AKEYCODE_BUTTON_12:
   797  	case C.AKEYCODE_BUTTON_13:
   798  	case C.AKEYCODE_BUTTON_14:
   799  	case C.AKEYCODE_BUTTON_15:
   800  	case C.AKEYCODE_BUTTON_16:
   801  	case C.AKEYCODE_LANGUAGE_SWITCH:
   802  	case C.AKEYCODE_MANNER_MODE:
   803  	case C.AKEYCODE_3D_MODE:
   804  	case C.AKEYCODE_CONTACTS:
   805  	case C.AKEYCODE_CALENDAR:
   806  	case C.AKEYCODE_MUSIC:
   807  	case C.AKEYCODE_CALCULATOR:
   808  	}
   809  	/* Defined in an NDK API version beyond what we use today:
   810  	C.AKEYCODE_ASSIST
   811  	C.AKEYCODE_BRIGHTNESS_DOWN
   812  	C.AKEYCODE_BRIGHTNESS_UP
   813  	C.AKEYCODE_EISU
   814  	C.AKEYCODE_HENKAN
   815  	C.AKEYCODE_KANA
   816  	C.AKEYCODE_KATAKANA_HIRAGANA
   817  	C.AKEYCODE_MEDIA_AUDIO_TRACK
   818  	C.AKEYCODE_MUHENKAN
   819  	C.AKEYCODE_RO
   820  	C.AKEYCODE_YEN
   821  	C.AKEYCODE_ZENKAKU_HANKAKU
   822  	*/
   823  	return key.CodeUnknown
   824  }