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 }