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