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