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 }