github.com/goki/mobile@v0.0.0-20230707090321-193544ec5700/app/android.c (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  #include <android/log.h>
     9  #include <dlfcn.h>
    10  #include <errno.h>
    11  #include <fcntl.h>
    12  #include <stdint.h>
    13  #include <stdbool.h>
    14  #include <string.h>
    15  #include "_cgo_export.h"
    16  
    17  #define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, "Go", __VA_ARGS__)
    18  #define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "Go", __VA_ARGS__)
    19  
    20  static jclass current_class;
    21  
    22  static jclass find_class(JNIEnv *env, const char *class_name)
    23  {
    24  	jclass clazz = (*env)->FindClass(env, class_name);
    25  	if (clazz == NULL)
    26  	{
    27  		(*env)->ExceptionClear(env);
    28  		LOG_FATAL("cannot find %s", class_name);
    29  		return NULL;
    30  	}
    31  	return clazz;
    32  }
    33  
    34  static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const char *sig)
    35  {
    36  	jmethodID m = (*env)->GetMethodID(env, clazz, name, sig);
    37  	if (m == 0)
    38  	{
    39  		(*env)->ExceptionClear(env);
    40  		LOG_FATAL("cannot find method %s %s", name, sig);
    41  		return 0;
    42  	}
    43  	return m;
    44  }
    45  
    46  static jmethodID find_static_method(JNIEnv *env, jclass clazz, const char *name, const char *sig)
    47  {
    48  	jmethodID m = (*env)->GetStaticMethodID(env, clazz, name, sig);
    49  	if (m == 0)
    50  	{
    51  		(*env)->ExceptionClear(env);
    52  		LOG_FATAL("cannot find method %s %s", name, sig);
    53  		return 0;
    54  	}
    55  	return m;
    56  }
    57  
    58  static jmethodID key_rune_method;
    59  static jmethodID show_keyboard_method;
    60  static jmethodID hide_keyboard_method;
    61  static jmethodID show_file_open_method;
    62  static jmethodID show_file_save_method;
    63  
    64  jint JNI_OnLoad(JavaVM *vm, void *reserved)
    65  {
    66  	JNIEnv *env;
    67  	if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK)
    68  	{
    69  		return -1;
    70  	}
    71  
    72  	return JNI_VERSION_1_6;
    73  }
    74  
    75  static int main_running = 0;
    76  
    77  // Entry point from our subclassed NativeActivity.
    78  //
    79  // By here, the Go runtime has been initialized (as we are running in
    80  // -buildmode=c-shared) but the first time it is called, Go's main.main
    81  // hasn't been called yet.
    82  //
    83  // The Activity may be created and destroyed multiple times throughout
    84  // the life of a single process. Each time, onCreate is called.
    85  void ANativeActivity_onCreate(ANativeActivity *activity, void *savedState, size_t savedStateSize)
    86  {
    87  	if (!main_running)
    88  	{
    89  		JNIEnv *env = activity->env;
    90  
    91  		// Note that activity->clazz is mis-named.
    92  		current_class = (*env)->GetObjectClass(env, activity->clazz);
    93  		current_class = (*env)->NewGlobalRef(env, current_class);
    94  		key_rune_method = find_static_method(env, current_class, "getRune", "(III)I");
    95  		show_keyboard_method = find_static_method(env, current_class, "showKeyboard", "(I)V");
    96  		hide_keyboard_method = find_static_method(env, current_class, "hideKeyboard", "()V");
    97  		show_file_open_method = find_static_method(env, current_class, "showFileOpen", "(Ljava/lang/String;)V");
    98  		show_file_save_method = find_static_method(env, current_class, "showFileSave", "(Ljava/lang/String;Ljava/lang/String;)V");
    99  
   100  		setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, activity->clazz));
   101  
   102  		// Set FILESDIR
   103  		if (setenv("FILESDIR", activity->internalDataPath, 1) != 0)
   104  		{
   105  			LOG_INFO("setenv(\"FILESDIR\", \"%s\", 1) failed: %d", activity->internalDataPath, errno);
   106  		}
   107  
   108  		// Set TMPDIR.
   109  		jmethodID gettmpdir = find_method(env, current_class, "getTmpdir", "()Ljava/lang/String;");
   110  		jstring jpath = (jstring)(*env)->CallObjectMethod(env, activity->clazz, gettmpdir, NULL);
   111  		const char *tmpdir = (*env)->GetStringUTFChars(env, jpath, NULL);
   112  		if (setenv("TMPDIR", tmpdir, 1) != 0)
   113  		{
   114  			LOG_INFO("setenv(\"TMPDIR\", \"%s\", 1) failed: %d", tmpdir, errno);
   115  		}
   116  		(*env)->ReleaseStringUTFChars(env, jpath, tmpdir);
   117  
   118  		// Call the Go main.main.
   119  		uintptr_t mainPC = (uintptr_t)dlsym(RTLD_DEFAULT, "main.main");
   120  		if (!mainPC)
   121  		{
   122  			LOG_FATAL("missing main.main");
   123  		}
   124  		callMain(mainPC);
   125  		main_running = 1;
   126  	}
   127  
   128  	// These functions match the methods on Activity, described at
   129  	// http://developer.android.com/reference/android/app/Activity.html
   130  	//
   131  	// Note that onNativeWindowResized is not called on resize. Avoid it.
   132  	// https://code.google.com/p/android/issues/detail?id=180645
   133  	activity->callbacks->onStart = onStart;
   134  	activity->callbacks->onResume = onResume;
   135  	activity->callbacks->onSaveInstanceState = onSaveInstanceState;
   136  	activity->callbacks->onPause = onPause;
   137  	activity->callbacks->onStop = onStop;
   138  	activity->callbacks->onDestroy = onDestroy;
   139  	activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
   140  	activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
   141  	activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded;
   142  	activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
   143  	activity->callbacks->onInputQueueCreated = onInputQueueCreated;
   144  	activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
   145  	activity->callbacks->onConfigurationChanged = onConfigurationChanged;
   146  	activity->callbacks->onLowMemory = onLowMemory;
   147  
   148  	onCreate(activity);
   149  }
   150  
   151  // TODO(crawshaw): Test configuration on more devices.
   152  static const EGLint RGB_888[] = {
   153  	EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
   154  	EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
   155  	EGL_BLUE_SIZE, 8,
   156  	EGL_GREEN_SIZE, 8,
   157  	EGL_RED_SIZE, 8,
   158  	EGL_DEPTH_SIZE, 16,
   159  	EGL_CONFIG_CAVEAT, EGL_NONE,
   160  	EGL_NONE};
   161  
   162  EGLDisplay display = NULL;
   163  EGLSurface surface = NULL;
   164  EGLContext context = NULL;
   165  
   166  static char *initEGLDisplay()
   167  {
   168  	display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
   169  	if (!eglInitialize(display, 0, 0))
   170  	{
   171  		return "EGL initialize failed";
   172  	}
   173  	return NULL;
   174  }
   175  
   176  char *createEGLSurface(ANativeWindow *window)
   177  {
   178  	char *err;
   179  	EGLint numConfigs, format;
   180  	EGLConfig config;
   181  
   182  	if (display == 0)
   183  	{
   184  		if ((err = initEGLDisplay()) != NULL)
   185  		{
   186  			return err;
   187  		}
   188  	}
   189  
   190  	if (!eglChooseConfig(display, RGB_888, &config, 1, &numConfigs))
   191  	{
   192  		return "EGL choose RGB_888 config failed";
   193  	}
   194  	if (numConfigs <= 0)
   195  	{
   196  		return "EGL no config found";
   197  	}
   198  
   199  	eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
   200  	if (ANativeWindow_setBuffersGeometry(window, 0, 0, format) != 0)
   201  	{
   202  		return "EGL set buffers geometry failed";
   203  	}
   204  
   205  	surface = eglCreateWindowSurface(display, config, window, NULL);
   206  	if (surface == EGL_NO_SURFACE)
   207  	{
   208  		return "EGL create surface failed";
   209  	}
   210  
   211  	if (context == NULL)
   212  	{
   213  		const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
   214  		context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
   215  	}
   216  
   217  	if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
   218  	{
   219  		return "eglMakeCurrent failed";
   220  	}
   221  	return NULL;
   222  }
   223  
   224  char *destroyEGLSurface()
   225  {
   226  	if (!eglDestroySurface(display, surface))
   227  	{
   228  		return "EGL destroy surface failed";
   229  	}
   230  	return NULL;
   231  }
   232  
   233  int32_t getKeyRune(JNIEnv *env, AInputEvent *e)
   234  {
   235  	return (int32_t)(*env)->CallStaticIntMethod(
   236  		env,
   237  		current_class,
   238  		key_rune_method,
   239  		AInputEvent_getDeviceId(e),
   240  		AKeyEvent_getKeyCode(e),
   241  		AKeyEvent_getMetaState(e));
   242  }
   243  
   244  void showKeyboard(JNIEnv *env, int keyboardType)
   245  {
   246  	(*env)->CallStaticVoidMethod(
   247  		env,
   248  		current_class,
   249  		show_keyboard_method,
   250  		keyboardType);
   251  }
   252  
   253  void hideKeyboard(JNIEnv *env)
   254  {
   255  	(*env)->CallStaticVoidMethod(
   256  		env,
   257  		current_class,
   258  		hide_keyboard_method);
   259  }
   260  
   261  void showFileOpen(JNIEnv *env, char *mimes)
   262  {
   263  	jstring mimesJString = (*env)->NewStringUTF(env, mimes);
   264  	(*env)->CallStaticVoidMethod(
   265  		env,
   266  		current_class,
   267  		show_file_open_method,
   268  		mimesJString);
   269  }
   270  
   271  void showFileSave(JNIEnv *env, char *mimes, char *filename)
   272  {
   273  	jstring mimesJString = (*env)->NewStringUTF(env, mimes);
   274  	jstring filenameJString = (*env)->NewStringUTF(env, filename);
   275  	(*env)->CallStaticVoidMethod(
   276  		env,
   277  		current_class,
   278  		show_file_save_method,
   279  		mimesJString,
   280  		filenameJString);
   281  }
   282  
   283  void Java_org_golang_app_GoNativeActivity_filePickerReturned(JNIEnv *env, jclass clazz, jstring str)
   284  {
   285  	const char *cstr = (*env)->GetStringUTFChars(env, str, JNI_FALSE);
   286  	filePickerReturned((char *)cstr);
   287  }
   288  
   289  void Java_org_golang_app_GoNativeActivity_insetsChanged(JNIEnv *env, jclass clazz, int top, int bottom, int left, int right)
   290  {
   291  	insetsChanged(top, bottom, left, right);
   292  }
   293  
   294  void Java_org_golang_app_GoNativeActivity_keyboardTyped(JNIEnv *env, jclass clazz, jstring str)
   295  {
   296  	const char *cstr = (*env)->GetStringUTFChars(env, str, JNI_FALSE);
   297  	keyboardTyped((char *)cstr);
   298  }
   299  
   300  void Java_org_golang_app_GoNativeActivity_keyboardDelete(JNIEnv *env, jclass clazz)
   301  {
   302  	keyboardDelete();
   303  }
   304  
   305  void Java_org_golang_app_GoNativeActivity_setDarkMode(JNIEnv *env, jclass clazz, jboolean dark)
   306  {
   307  	setDarkMode((bool)dark);
   308  }