github.com/corfe83/mobile@v0.0.0-20220928034243-9edc37f43fac/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  // +build android
     6  
     7  #include <android/log.h>
     8  #include <dlfcn.h>
     9  #include <errno.h>
    10  #include <fcntl.h>
    11  #include <stdint.h>
    12  #include <string.h>
    13  #include "_cgo_export.h"
    14  
    15  #define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, "Go", __VA_ARGS__)
    16  #define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "Go", __VA_ARGS__)
    17  
    18  static jclass current_class;
    19  
    20  static jclass find_class(JNIEnv *env, const char *class_name) {
    21  	jclass clazz = (*env)->FindClass(env, class_name);
    22  	if (clazz == NULL) {
    23  		(*env)->ExceptionDescribe(env);
    24  		(*env)->ExceptionClear(env);
    25  		LOG_FATAL("cannot find %s", class_name);
    26  		return NULL;
    27  	}
    28  	return clazz;
    29  }
    30  
    31  static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
    32  	jmethodID m = (*env)->GetMethodID(env, clazz, name, sig);
    33  	if (m == 0) {
    34  		(*env)->ExceptionDescribe(env);
    35  		(*env)->ExceptionClear(env);
    36  		LOG_FATAL("cannot find method %s %s", name, sig);
    37  		return 0;
    38  	}
    39  	return m;
    40  }
    41  
    42  static jmethodID find_static_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
    43  	jmethodID m = (*env)->GetStaticMethodID(env, clazz, name, sig);
    44  	if (m == 0) {
    45  		(*env)->ExceptionDescribe(env);
    46  		(*env)->ExceptionClear(env);
    47  		LOG_FATAL("cannot find method %s %s", name, sig);
    48  		return 0;
    49  	}
    50  	return m;
    51  }
    52  
    53  static jmethodID key_rune_method;
    54  
    55  JavaVM* vmForClipboard;
    56  jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    57  	JNIEnv* env;
    58  	if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
    59  		return -1;
    60  	}
    61  
    62  	vmForClipboard = vm;
    63  
    64  	return JNI_VERSION_1_6;
    65  }
    66  
    67  static int main_running = 0;
    68  
    69  // Entry point from our subclassed NativeActivity.
    70  //
    71  // By here, the Go runtime has been initialized (as we are running in
    72  // -buildmode=c-shared) but the first time it is called, Go's main.main
    73  // hasn't been called yet.
    74  //
    75  // The Activity may be created and destroyed multiple times throughout
    76  // the life of a single process. Each time, onCreate is called.
    77  void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_t savedStateSize) {
    78  	if (!main_running) {
    79  		JNIEnv* env = activity->env;
    80  
    81  		// Note that activity->clazz is mis-named.
    82  		current_class = (*env)->GetObjectClass(env, activity->clazz);
    83  		current_class = (*env)->NewGlobalRef(env, current_class);
    84  		key_rune_method = find_static_method(env, current_class, "getRune", "(III)I");
    85  
    86  		setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, activity->clazz));
    87  
    88  		// Set TMPDIR.
    89  		jmethodID gettmpdir = find_method(env, current_class, "getTmpdir", "()Ljava/lang/String;");
    90  		jstring jpath = (jstring)(*env)->CallObjectMethod(env, activity->clazz, gettmpdir, NULL);
    91  		const char* tmpdir = (*env)->GetStringUTFChars(env, jpath, NULL);
    92  		if (setenv("TMPDIR", tmpdir, 1) != 0) {
    93  			LOG_INFO("setenv(\"TMPDIR\", \"%s\", 1) failed: %d", tmpdir, errno);
    94  		}
    95  		(*env)->ReleaseStringUTFChars(env, jpath, tmpdir);
    96  
    97  		// Call the Go main.main.
    98  		uintptr_t mainPC = (uintptr_t)dlsym(RTLD_DEFAULT, "main.main");
    99  		if (!mainPC) {
   100  			LOG_FATAL("missing main.main");
   101  		}
   102  		callMain(mainPC);
   103  		main_running = 1;
   104  	}
   105  
   106  	// These functions match the methods on Activity, described at
   107  	// http://developer.android.com/reference/android/app/Activity.html
   108  	//
   109  	// Note that onNativeWindowResized is not called on resize. Avoid it.
   110  	// https://code.google.com/p/android/issues/detail?id=180645
   111  	activity->callbacks->onStart = onStart;
   112  	activity->callbacks->onResume = onResume;
   113  	activity->callbacks->onSaveInstanceState = onSaveInstanceState;
   114  	activity->callbacks->onPause = onPause;
   115  	activity->callbacks->onStop = onStop;
   116  	activity->callbacks->onDestroy = onDestroy;
   117  	activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
   118  	activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
   119  	activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded;
   120  	activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
   121  	activity->callbacks->onInputQueueCreated = onInputQueueCreated;
   122  	activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
   123  	activity->callbacks->onConfigurationChanged = onConfigurationChanged;
   124  	activity->callbacks->onLowMemory = onLowMemory;
   125  
   126  	onCreate(activity);
   127  }
   128  
   129  // TODO(crawshaw): Test configuration on more devices.
   130  static const EGLint RGB_888[] = {
   131  	EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
   132  	EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
   133  	EGL_BLUE_SIZE, 8,
   134  	EGL_GREEN_SIZE, 8,
   135  	EGL_RED_SIZE, 8,
   136  	EGL_DEPTH_SIZE, 16,
   137  	EGL_CONFIG_CAVEAT, EGL_NONE,
   138  	EGL_NONE
   139  };
   140  
   141  EGLDisplay display = NULL;
   142  EGLSurface surface = NULL;
   143  
   144  static char* initEGLDisplay() {
   145  	display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
   146  	if (!eglInitialize(display, 0, 0)) {
   147  		return "EGL initialize failed";
   148  	}
   149  	return NULL;
   150  }
   151  
   152  char* createEGLSurface(ANativeWindow* window) {
   153  	char* err;
   154  	EGLint numConfigs, format;
   155  	EGLConfig config;
   156  	EGLContext context;
   157  
   158  	if (display == 0) {
   159  		if ((err = initEGLDisplay()) != NULL) {
   160  			return err;
   161  		}
   162  	}
   163  
   164  	if (!eglChooseConfig(display, RGB_888, &config, 1, &numConfigs)) {
   165  		return "EGL choose RGB_888 config failed";
   166  	}
   167  	if (numConfigs <= 0) {
   168  		return "EGL no config found";
   169  	}
   170  
   171  	eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
   172  	if (ANativeWindow_setBuffersGeometry(window, 0, 0, format) != 0) {
   173  		return "EGL set buffers geometry failed";
   174  	}
   175  
   176  	surface = eglCreateWindowSurface(display, config, window, NULL);
   177  	if (surface == EGL_NO_SURFACE) {
   178  		return "EGL create surface failed";
   179  	}
   180  
   181  	const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
   182  	context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
   183  
   184  	if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
   185  		return "eglMakeCurrent failed";
   186  	}
   187  	return NULL;
   188  }
   189  
   190  char* destroyEGLSurface() {
   191  	if (!eglDestroySurface(display, surface)) {
   192  		return "EGL destroy surface failed";
   193  	}
   194  	return NULL;
   195  }
   196  
   197  int32_t getKeyRune(JNIEnv* env, AInputEvent* e) {
   198  	return (int32_t)(*env)->CallStaticIntMethod(
   199  		env,
   200  		current_class,
   201  		key_rune_method,
   202  		AInputEvent_getDeviceId(e),
   203  		AKeyEvent_getKeyCode(e),
   204  		AKeyEvent_getMetaState(e)
   205  	);
   206  }
   207  
   208  jobject clipboardManager = NULL;
   209  jobject applicationContext = NULL;
   210  
   211  jclass contextClass = NULL;
   212  
   213  jclass clipDataClass = NULL;
   214  jmethodID clipDataConstructor = NULL;
   215  jclass clipDataItemClass = NULL;
   216  jmethodID clipDataItemConstructor = NULL;
   217  jclass clipDescriptionClass = NULL;
   218  jmethodID clipDescriptionConstructor = NULL;
   219  
   220  jmethodID getPrimaryClipFunc = NULL;
   221  jmethodID getItemAtFunc = NULL;
   222  jmethodID getTextFunc = NULL;
   223  jmethodID charSequencetoString = NULL;
   224  jmethodID setPrimaryClipFunc = NULL;
   225  unsigned char clipboardFailed = 0;
   226  
   227  char clipboardLastError[512] = {0};
   228  
   229  // 1 means success, 0 means failure
   230  JNIEnv* JVMEnsureAttached() {
   231  	JNIEnv* env;
   232  	if (clipboardFailed != 0) {
   233  		return NULL;
   234  	}
   235  
   236  	if ((*vmForClipboard)->GetEnv(vmForClipboard, (void**)&env, JNI_VERSION_1_6) == JNI_OK) {
   237  		return env;
   238  	}
   239  
   240  	if ((*vmForClipboard)->AttachCurrentThread(vmForClipboard, &env, NULL) == JNI_OK) {
   241  		return env;
   242  	}
   243  
   244  	return NULL;
   245  }
   246  
   247  void copyExceptionMessage(JNIEnv* env, const char *prefix, char *cStringOutput, size_t maxSize) {
   248  	jthrowable e = (*env)->ExceptionOccurred(env);
   249  	if (e == NULL) {
   250  		strncpy(cStringOutput, prefix, maxSize);
   251  		return;
   252  	}
   253  	(*env)->ExceptionClear(env); // clears the exception; e remains valid
   254  
   255  	jclass clazz = (*env)->GetObjectClass(env, e);
   256  	jmethodID getMessage = (*env)->GetMethodID(env, clazz,
   257  											"getMessage",
   258  											"()Ljava/lang/String;");
   259  	jstring jStringOutput = (jstring)(*env)->CallObjectMethod(env, e, getMessage);
   260  
   261  	const char *javaOwnedOutput = (*env)->GetStringUTFChars(env, jStringOutput, NULL);
   262  	strncpy(cStringOutput, prefix, maxSize);
   263  	int prefixSize = strnlen(prefix, maxSize);
   264  	if (prefixSize < maxSize) {
   265  		strncpy(cStringOutput+prefixSize, javaOwnedOutput, maxSize-prefixSize);
   266  	}
   267  	(*env)->ReleaseStringUTFChars(env, jStringOutput, javaOwnedOutput);
   268  }
   269  
   270  const char * getLastClipboardError() {
   271  	return clipboardLastError;
   272  }
   273  
   274  const char * getClipboardString() {
   275  	if (clipboardFailed != 0) {
   276  		return "";
   277  	}
   278  
   279  	JNIEnv* env = JVMEnsureAttached();
   280  	jobject clipData = (*env)->CallObjectMethod(env, clipboardManager, getPrimaryClipFunc);
   281  	if (clipData == NULL) {
   282  		copyExceptionMessage(env, "Error getting clipboard data:", clipboardLastError, sizeof(clipboardLastError));
   283  		return "";
   284  	}
   285  
   286  	jobject clipFirstItem = (*env)->CallObjectMethod(env, clipData, getItemAtFunc, 0);
   287  	if (clipFirstItem == NULL) {
   288  		copyExceptionMessage(env, "Error getting first item of clipboard:", clipboardLastError, sizeof(clipboardLastError));
   289  		return "";
   290  	}
   291  
   292  	jobject charSequence = (*env)->CallObjectMethod(env, clipFirstItem, getTextFunc);
   293  	if (charSequence == NULL) {
   294  		copyExceptionMessage(env, "Looks like no text is copied right now:", clipboardLastError, sizeof(clipboardLastError));
   295  		return "";
   296  	}
   297  
   298  	jstring result = (jstring)(*env)->CallObjectMethod(env, charSequence, charSequencetoString);
   299  	if (result == NULL) {
   300  		copyExceptionMessage(env, "CharSequence could not be converted to string:", clipboardLastError, sizeof(clipboardLastError));
   301  		return "";
   302  	}
   303  
   304  	return (*env)->GetStringUTFChars(env, result, 0);
   305  }
   306  
   307  void setClipboardString(const char * input) {
   308  	if (clipboardFailed != 0) {
   309  		return;
   310  	}
   311  
   312  	JNIEnv* env = JVMEnsureAttached();
   313  
   314  	// Single string in array of text/plain MIME type
   315  	jstring textToSet = (*env)->NewStringUTF(env, "Text Data");
   316  	jclass stringClass = (*env)->FindClass(env, "java/lang/String");
   317  	jstring mimeTypeString = (*env)->NewStringUTF(env, "text/plain");
   318  	jobjectArray mimeTypeStringArray = (jobjectArray)(*env)->NewObjectArray(env, 1, stringClass, mimeTypeString);
   319  	if (mimeTypeStringArray == NULL) {
   320  		copyExceptionMessage(env, "Failed to create mime type string array:", clipboardLastError, sizeof(clipboardLastError));
   321  		return;
   322  	}
   323  
   324  	jobject clipDescription = (*env)->NewObject(env, clipDescriptionClass, clipDescriptionConstructor, textToSet, mimeTypeStringArray);
   325  	if (clipDescription == NULL) {
   326  		copyExceptionMessage(env, "Failed to create clip description:", clipboardLastError, sizeof(clipboardLastError));
   327  		return;
   328  	}
   329  
   330  	jstring inputString = (*env)->NewStringUTF(env, input);
   331  	jobject clipDataItem = (*env)->NewObject(env, clipDataItemClass, clipDataItemConstructor, inputString);
   332  	if (clipDataItem == NULL) {
   333  		copyExceptionMessage(env, "Failed to create clip data item:", clipboardLastError, sizeof(clipboardLastError));
   334  		return;
   335  	}
   336  
   337  	jobject clipData = (*env)->NewObject(env, clipDataClass, clipDataConstructor, clipDescription, clipDataItem);
   338  	if (clipData == NULL) {
   339  		copyExceptionMessage(env, "Failed to create clip data:", clipboardLastError, sizeof(clipboardLastError));
   340  		return;
   341  	}
   342  
   343  	(*env)->CallVoidMethod(env, clipboardManager, setPrimaryClipFunc, clipData);
   344  }
   345  
   346  
   347  // Called from OnStart (CANNOT be called from OnCreate)
   348  void setupClipboardManager(ANativeActivity *activity) {
   349  	JNIEnv* env = activity->env;
   350  
   351  	// If we already failed, or already have it, no need to do anything here
   352  	if (clipboardFailed == 0 && clipboardManager != NULL) {
   353  		return;
   354  	}
   355  	if (clipboardFailed) {
   356  		return;
   357  	}
   358  
   359  	jobject context = activity->clazz;
   360  
   361  	contextClass = (*env)->GetObjectClass(env, context);
   362  	if (contextClass == NULL) {
   363  		clipboardFailed = 1;
   364  		copyExceptionMessage(env, "failed to get context class:", clipboardLastError, sizeof(clipboardLastError));
   365  		return;
   366  	}
   367  
   368  	// Find context
   369  	jmethodID getApplicationContextFunc = NULL;
   370  	while (getApplicationContextFunc == NULL) {
   371  		getApplicationContextFunc = find_method(env, contextClass, "getApplicationContext", "()Landroid/content/Context;");
   372  		if (getApplicationContextFunc != NULL) {
   373  			break;
   374  		}
   375  
   376  		contextClass = (*env)->GetSuperclass(env, contextClass);
   377  		if (contextClass == NULL) {
   378  			clipboardFailed = 1;
   379  			copyExceptionMessage(env, "failed to get superclass:", clipboardLastError, sizeof(clipboardLastError));
   380  			return;
   381  		}
   382  	}
   383  
   384  	applicationContext = (*env)->CallObjectMethod(env, context, getApplicationContextFunc);
   385  	if (applicationContext == NULL) {
   386  		clipboardFailed = 1;
   387  		copyExceptionMessage(env, "failed to call getApplicationContext():", clipboardLastError, sizeof(clipboardLastError));
   388  		return;
   389  	}
   390  	applicationContext = (jclass)(*env)->NewGlobalRef(env, applicationContext);
   391  
   392  	contextClass = (*env)->GetObjectClass(env, applicationContext);
   393  	if (contextClass == NULL) {
   394  		clipboardFailed = 1;
   395  		copyExceptionMessage(env, "failed to get applicationcontext class:", clipboardLastError, sizeof(clipboardLastError));
   396  		return;
   397  	}
   398  	contextClass = (jclass)(*env)->NewGlobalRef(env, contextClass);
   399  
   400  	jclass generalContextClass = (*env)->FindClass(env, "android/content/Context");
   401  	if (context == NULL) {
   402  		clipboardFailed = 1;
   403  		copyExceptionMessage(env, "failed to find context class:", clipboardLastError, sizeof(clipboardLastError));
   404  		return;
   405  	}
   406  
   407  	jfieldID clipboardServiceField = (*env)->GetStaticFieldID(env, generalContextClass, "CLIPBOARD_SERVICE", "Ljava/lang/String;");
   408  	if (clipboardServiceField == 0) {
   409  		clipboardFailed = 1;
   410  		copyExceptionMessage(env, "failed to find clipboardServiceField:", clipboardLastError, sizeof(clipboardLastError));
   411  		return;
   412  	}
   413  
   414  	jstring clipboardServiceName = (jstring)(*env)->GetStaticObjectField(env, generalContextClass, clipboardServiceField);
   415  	if (clipboardServiceName == NULL) {
   416  		clipboardFailed = 1;
   417  		copyExceptionMessage(env, "failed to read clipboardServiceField:", clipboardLastError, sizeof(clipboardLastError));
   418  		return;
   419  	}
   420  
   421  	jmethodID getSystemServiceFunc = find_method(env, contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   422  	if (getSystemServiceFunc == NULL) {
   423  		clipboardFailed = 1;
   424  		copyExceptionMessage(env, "failed to find getSystemService method:", clipboardLastError, sizeof(clipboardLastError));
   425  		return;
   426  	}
   427  
   428  	jobject localClipboardManager = (*env)->CallObjectMethod(env, applicationContext, getSystemServiceFunc, clipboardServiceName);
   429  	if (localClipboardManager == NULL) {
   430  		clipboardFailed = 1;
   431  		copyExceptionMessage(env, "failed to get clipboard service:", clipboardLastError, sizeof(clipboardLastError));
   432  		return;
   433  	}
   434  
   435  	jclass clipboardManagerClass = (*env)->FindClass(env, "android/content/ClipboardManager");
   436  	if (clipboardManagerClass == NULL) {
   437  		clipboardFailed = 1;
   438  		copyExceptionMessage(env, "failed to get class of clipboardmanager:", clipboardLastError, sizeof(clipboardLastError));
   439  		return;
   440  	}
   441  
   442  	setPrimaryClipFunc = find_method(env, clipboardManagerClass, "setPrimaryClip", "(Landroid/content/ClipData;)V");
   443  	if (setPrimaryClipFunc == NULL) {
   444  		clipboardFailed = 1;
   445  		copyExceptionMessage(env, "failed to find setPrimaryClip method:", clipboardLastError, sizeof(clipboardLastError));
   446  		return;
   447  	}
   448  
   449  	getPrimaryClipFunc = find_method(env, clipboardManagerClass, "getPrimaryClip", "()Landroid/content/ClipData;");
   450  	if (getPrimaryClipFunc == NULL) {
   451  		clipboardFailed = 1;
   452  		copyExceptionMessage(env, "failed to find getPrimaryClip method:", clipboardLastError, sizeof(clipboardLastError));
   453  		return;
   454  	}
   455  
   456  	clipDataClass = (*env)->FindClass(env, "android/content/ClipData");
   457  	if (clipDataClass == NULL) {
   458  		clipboardFailed = 1;
   459  		copyExceptionMessage(env, "failed to find ClipData class:", clipboardLastError, sizeof(clipboardLastError));
   460  		return;
   461  	}
   462  	clipDataClass = (jclass)(*env)->NewGlobalRef(env, clipDataClass);
   463  
   464  	getItemAtFunc = find_method(env, clipDataClass, "getItemAt", "(I)Landroid/content/ClipData$Item;");
   465  	if (getItemAtFunc == NULL) {
   466  		clipboardFailed = 1;
   467  		copyExceptionMessage(env, "failed to find getItemAt method:", clipboardLastError, sizeof(clipboardLastError));
   468  		return;
   469  	}
   470  
   471  	clipDataItemClass = (*env)->FindClass(env, "android/content/ClipData$Item");
   472  	if (clipDataItemClass == NULL) {
   473  		clipboardFailed = 1;
   474  		copyExceptionMessage(env, "failed to find ClipData.Item class:", clipboardLastError, sizeof(clipboardLastError));
   475  		return;
   476  	}
   477  	clipDataItemClass = (jclass)(*env)->NewGlobalRef(env, clipDataItemClass);
   478  
   479  	getTextFunc = find_method(env, clipDataItemClass, "getText", "()Ljava/lang/CharSequence;");
   480  	if (getTextFunc == NULL) {
   481  		clipboardFailed = 1;
   482  		copyExceptionMessage(env, "failed to find getText method:", clipboardLastError, sizeof(clipboardLastError));
   483  		return;
   484  	}
   485  
   486  	jclass charSequenceClass = (*env)->FindClass(env, "java/lang/CharSequence");
   487  	if (charSequenceClass == NULL) {
   488  		clipboardFailed = 1;
   489  		copyExceptionMessage(env, "failed to find CharSequence class:", clipboardLastError, sizeof(clipboardLastError));
   490  		return;
   491  	}
   492  
   493  	charSequencetoString = find_method(env, charSequenceClass, "toString", "()Ljava/lang/String;");
   494  	if (charSequencetoString == NULL) {
   495  		clipboardFailed = 1;
   496  		copyExceptionMessage(env, "failed to find toString method:", clipboardLastError, sizeof(clipboardLastError));
   497  		return;
   498  	}
   499  
   500  	// Get constructors
   501  	clipDataItemConstructor = find_method(env, clipDataItemClass, "<init>", "(Ljava/lang/CharSequence;)V");
   502  	if (clipDataItemConstructor == NULL) {
   503  		clipboardFailed = 1;
   504  		copyExceptionMessage(env, "failed to find ClipDataItem constructor:", clipboardLastError, sizeof(clipboardLastError));
   505  		return;
   506  	}
   507  
   508  	clipDataConstructor = find_method(env, clipDataClass, "<init>", "(Landroid/content/ClipDescription;Landroid/content/ClipData$Item;)V");
   509  	if (clipDataConstructor == NULL) {
   510  		clipboardFailed = 1;
   511  		copyExceptionMessage(env, "failed to find ClipData constructor:", clipboardLastError, sizeof(clipboardLastError));
   512  		return;
   513  	}
   514  
   515  	clipDescriptionClass = (*env)->FindClass(env, "android/content/ClipDescription");
   516  	if (clipDescriptionClass == NULL) {
   517  		clipboardFailed = 1;
   518  		copyExceptionMessage(env, "failed to find ClipDescription class:", clipboardLastError, sizeof(clipboardLastError));
   519  		return;
   520  	}
   521  	clipDescriptionClass = (jclass)(*env)->NewGlobalRef(env, clipDescriptionClass);
   522  
   523  	clipDescriptionConstructor = find_method(env, clipDescriptionClass, "<init>", "(Ljava/lang/CharSequence;[Ljava/lang/String;)V");
   524  	if (clipDescriptionConstructor == NULL) {
   525  		clipboardFailed = 1;
   526  		copyExceptionMessage(env, "failed to find ClipDescription constructor:", clipboardLastError, sizeof(clipboardLastError));
   527  		return;
   528  	}
   529  
   530  	clipboardManager = (*env)->NewGlobalRef(env, localClipboardManager);
   531  	clipboardLastError[0] = '\0';
   532  
   533  	return;
   534  }
   535  
   536  jclass intentClass = NULL;
   537  jmethodID intentConstructor = NULL;
   538  
   539  jmethodID addFlags = NULL;
   540  
   541  jclass uriClass = NULL;
   542  jmethodID uriParseFunc = NULL;
   543  
   544  jmethodID startActivityFunc = NULL;
   545  
   546  jstring actionViewString = NULL;
   547  jint newTaskFlag = 0;
   548  
   549  unsigned char browserFailed = 0;
   550  unsigned char browserInitCompleted = 0;
   551  char browserLastError[512] = {0};
   552  void setupBrowser(ANativeActivity *activity) {
   553  	JNIEnv* env = activity->env;
   554  
   555  	// If we already failed, or already have it, no need to do anything here
   556  	if (browserFailed == 0 && browserInitCompleted != 0) {
   557  		return;
   558  	}
   559  	if (browserFailed) {
   560  		return;
   561  	}
   562  
   563  	intentClass = (*env)->FindClass(env, "android/content/Intent");
   564  	if (intentClass == NULL) {
   565  		browserFailed = 1;
   566  		copyExceptionMessage(env, "failed to find Intent class:", browserLastError, sizeof(browserLastError));
   567  		return;
   568  	}
   569  	intentClass = (jclass)(*env)->NewGlobalRef(env, intentClass);
   570  
   571  	intentConstructor = find_method(env, intentClass, "<init>", "(Ljava/lang/String;Landroid/net/Uri;)V");
   572  	if (intentConstructor == NULL) {
   573  		browserFailed = 1;
   574  		copyExceptionMessage(env, "failed to find Intent constructor:", browserLastError, sizeof(browserLastError));
   575  		return;
   576  	}
   577  
   578  	addFlags = find_method(env, intentClass, "addFlags", "(I)Landroid/content/Intent;");
   579  	if (addFlags == NULL) {
   580  		browserFailed = 1;
   581  		copyExceptionMessage(env, "failed to find addFlags method:", browserLastError, sizeof(browserLastError));
   582  	}
   583  
   584  	uriClass = (*env)->FindClass(env, "android/net/Uri");
   585  	if (uriClass == NULL) {
   586  		browserFailed = 1;
   587  		copyExceptionMessage(env, "failed to find Uri class:", browserLastError, sizeof(browserLastError));
   588  		return;
   589  	}
   590  	uriClass = (jclass)(*env)->NewGlobalRef(env, uriClass);
   591  
   592  	uriParseFunc = find_static_method(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
   593  	if (uriParseFunc == NULL) {
   594  		browserFailed = 1;
   595  		copyExceptionMessage(env, "failed to find static method Uri.parse:", browserLastError, sizeof(browserLastError));
   596  		return;
   597  	}
   598  
   599  	jfieldID actionViewField = (*env)->GetStaticFieldID(env, intentClass, "ACTION_VIEW", "Ljava/lang/String;");
   600  	if (actionViewField == 0) {
   601  		browserFailed = 1;
   602  		copyExceptionMessage(env, "failed to find Intent.ACTION_VIEW:", browserLastError, sizeof(browserLastError));
   603  		return;
   604  	}
   605  
   606  	actionViewString = (jstring)(*env)->GetStaticObjectField(env, intentClass, actionViewField);
   607  	if (actionViewString == NULL) {
   608  		browserFailed = 1;
   609  		copyExceptionMessage(env, "failed to read Intent.ACTION_VIEW:", browserLastError, sizeof(browserLastError));
   610  		return;
   611  	}
   612  	actionViewString = (jstring)(*env)->NewGlobalRef(env, actionViewString);
   613  
   614  	jfieldID newTaskField = (*env)->GetStaticFieldID(env, intentClass, "FLAG_ACTIVITY_NEW_TASK", "I");
   615  	if (newTaskField == 0) {
   616  		browserFailed = 1;
   617  		copyExceptionMessage(env, "failed to find Intent.FLAG_ACTIVITY_NEW_TASK:", browserLastError, sizeof(browserLastError));
   618  		return;
   619  	}
   620  
   621  	newTaskFlag = (jint)(*env)->GetStaticIntField(env, intentClass, newTaskField);
   622  	if (newTaskFlag == 0) {
   623  		browserFailed = 1;
   624  		copyExceptionMessage(env, "failed to read Intent.FLAG_ACTIVITY_NEW_TASK:", browserLastError, sizeof(browserLastError));
   625  		return;
   626  	}
   627  
   628  	startActivityFunc = find_method(env, contextClass, "startActivity", "(Landroid/content/Intent;)V");
   629  	if (intentConstructor == NULL) {
   630  		browserFailed = 1;
   631  		copyExceptionMessage(env, "failed to find startActivity function:", browserLastError, sizeof(browserLastError));
   632  		return;
   633  	}
   634  
   635  	browserInitCompleted = 1;
   636  }
   637  
   638  void openUrl(const char * url) {
   639  	if (browserFailed != 0) {
   640  		return;
   641  	}
   642  
   643  	JNIEnv* env = JVMEnsureAttached();
   644  
   645  	jstring urlstring = (*env)->NewStringUTF(env, url);
   646  	if (urlstring == NULL) {
   647  		copyExceptionMessage(env, "Failed to create jstring for url:", browserLastError, sizeof(browserLastError));
   648  		return;
   649  	}
   650  
   651  	jobject uri = (jstring)(*env)->CallStaticObjectMethod(env, uriClass, uriParseFunc, urlstring);
   652  	if (uri == NULL) {
   653  		copyExceptionMessage(env, "Uri.parse call failed:", browserLastError, sizeof(browserLastError));
   654  		return;
   655  	}
   656  
   657  	jobject intent = (*env)->NewObject(env, intentClass, intentConstructor, actionViewString, uri);
   658  	if (intent == NULL) {
   659  		copyExceptionMessage(env, "Failed to create intent:", browserLastError, sizeof(browserLastError));
   660  		return;
   661  	}
   662  
   663  	(*env)->CallObjectMethod(env, intent, addFlags, newTaskFlag);
   664  	if ((*env)->ExceptionOccurred(env) != NULL) {
   665  		copyExceptionMessage(env, "Failed to add flags:", browserLastError, sizeof(browserLastError));
   666  		return;
   667  	}
   668  
   669  	(*env)->CallVoidMethod(env, applicationContext, startActivityFunc, intent);
   670  	if ((*env)->ExceptionOccurred(env) != NULL) {
   671  		copyExceptionMessage(env, "Failed to start activity:", browserLastError, sizeof(browserLastError));
   672  		return;
   673  	}
   674  }
   675  
   676  const char * getLastBrowserError() {
   677  	return browserLastError;
   678  }
   679  
   680  unsigned char systemUiVisibilityFailed = 0;
   681  unsigned char systemUiVisibilityInitCompleted = 0;
   682  char systemUiVisibilityLastError[512] = {0};
   683  
   684  jmethodID getWindowMethod;
   685  jmethodID getDecorViewMethod;
   686  jmethodID setSystemUiVisibilityMethod;
   687  jmethodID getSystemUiVisibilityMethod;
   688  
   689  void setupSystemUiVisibility(ANativeActivity *activity) {
   690  	JNIEnv* env = activity->env;
   691  
   692  	// If we already failed, or already have it, no need to do anything here
   693  	if (systemUiVisibilityFailed == 0 && systemUiVisibilityInitCompleted != 0) {
   694  		return;
   695  	}
   696  	if (systemUiVisibilityFailed) {
   697  		return;
   698  	}
   699  
   700  	jclass activityClass = (*env)->FindClass(env, "android/app/Activity");
   701  	if (activityClass == NULL) {
   702  		systemUiVisibilityFailed = 1;
   703  		copyExceptionMessage(env, "failed to find Activity class:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   704  		return;
   705  	}
   706  
   707  	getWindowMethod = find_method(env, activityClass, "getWindow", "()Landroid/view/Window;");
   708  	if (getWindowMethod == NULL) {
   709  		systemUiVisibilityFailed = 1;
   710  		copyExceptionMessage(env, "failed to find getWindow method:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   711  		return;
   712  	}
   713  
   714  	jclass windowClass = (*env)->FindClass(env, "android/view/Window");
   715  	if (windowClass == NULL) {
   716  		systemUiVisibilityFailed = 1;
   717  		copyExceptionMessage(env, "failed to find Window class:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   718  		return;
   719  	}
   720  
   721  	getDecorViewMethod = find_method(env, windowClass, "getDecorView", "()Landroid/view/View;");
   722  	if (getDecorViewMethod == NULL) {
   723  		systemUiVisibilityFailed = 1;
   724  		copyExceptionMessage(env, "failed to find getDecorView method:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   725  		return;
   726  	}
   727  
   728  	jclass viewClass = (*env)->FindClass(env, "android/view/View");
   729  	if (viewClass == NULL) {
   730  		systemUiVisibilityFailed = 1;
   731  		copyExceptionMessage(env, "failed to find View class:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   732  		return;
   733  	}
   734  
   735  	setSystemUiVisibilityMethod = find_method(env, viewClass, "setSystemUiVisibility", "(I)V");
   736  	if (setSystemUiVisibilityMethod == NULL) {
   737  		systemUiVisibilityFailed = 1;
   738  		copyExceptionMessage(env, "failed to find setSystemUiVisibility method:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   739  		return;
   740  	}
   741  
   742  	getSystemUiVisibilityMethod = find_method(env, viewClass, "getSystemUiVisibility", "()I");
   743  	if (getSystemUiVisibilityMethod == NULL) {
   744  		systemUiVisibilityFailed = 1;
   745  		copyExceptionMessage(env, "failed to find getSystemUiVisibilityMethod method:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   746  		return;
   747  	}
   748  }
   749  
   750  const int SYSTEM_UI_FLAG_IMMERSIVE = 0x00000800;
   751  const int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002;
   752  const int SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004;
   753  
   754  void hideNavBar(ANativeActivity *activity) {
   755  	if (systemUiVisibilityFailed != 0) {
   756  		return;
   757  	}
   758  
   759  	JNIEnv* env = activity->env;
   760  
   761  	jobject window = (*env)->CallObjectMethod(env, activity->clazz, getWindowMethod);
   762  	if (window == NULL || (*env)->ExceptionOccurred(env) != NULL) {
   763  		copyExceptionMessage(env, "Failed to call getWindow:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   764  		return;
   765  	}
   766  
   767  	jobject view = (*env)->CallObjectMethod(env, window, getDecorViewMethod);
   768  	if (view == NULL || (*env)->ExceptionOccurred(env) != NULL) {
   769  		copyExceptionMessage(env, "Failed to call getDecorView:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   770  		return;
   771  	}
   772  
   773  	jint systemUiVisibilityFlags = (*env)->CallIntMethod(env, view, getSystemUiVisibilityMethod);
   774  	if ((*env)->ExceptionOccurred(env) != NULL) {
   775  		copyExceptionMessage(env, "Failed to call getDecorView:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   776  		return;
   777  	}
   778  
   779  	(*env)->CallVoidMethod(env, view, setSystemUiVisibilityMethod, systemUiVisibilityFlags | SYSTEM_UI_FLAG_IMMERSIVE | SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_FULLSCREEN);
   780  	if ((*env)->ExceptionOccurred(env) != NULL) {
   781  		copyExceptionMessage(env, "Failed to call setSystemUiVisibilityMethod:", systemUiVisibilityLastError, sizeof(systemUiVisibilityLastError));
   782  		return;
   783  	}
   784  }
   785  
   786  const char * getLastSystemUiVisibilityError() {
   787  	return systemUiVisibilityLastError;
   788  }