[Base] Android JNIEnv attachment and LaunchWebBrowser
This commit is contained in:
parent
54b057a46b
commit
3f817fb241
|
@ -24,7 +24,7 @@ extern "C" int main(int argc, char** argv) {
|
||||||
|
|
||||||
// Initialize Android globals, including logging. Needs parsed cvars.
|
// Initialize Android globals, including logging. Needs parsed cvars.
|
||||||
// TODO(Triang3l): Obtain the actual API level.
|
// TODO(Triang3l): Obtain the actual API level.
|
||||||
xe::InitializeAndroidAppFromMainThread(__ANDROID_API__);
|
xe::InitializeAndroidAppFromMainThread(__ANDROID_API__, nullptr, nullptr);
|
||||||
|
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
for (int n = 0; n < argc; n++) {
|
for (int n = 0; n < argc; n++) {
|
||||||
|
|
|
@ -9,11 +9,15 @@
|
||||||
|
|
||||||
#include "xenia/base/main_android.h"
|
#include "xenia/base/main_android.h"
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <pthread.h>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
|
#include "xenia/base/system.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -22,7 +26,25 @@ static size_t android_initializations_ = 0;
|
||||||
|
|
||||||
static int32_t android_api_level_ = __ANDROID_API__;
|
static int32_t android_api_level_ = __ANDROID_API__;
|
||||||
|
|
||||||
void InitializeAndroidAppFromMainThread(int32_t api_level) {
|
static JNIEnv* android_main_thread_jni_env_ = nullptr;
|
||||||
|
static JavaVM* android_java_vm_ = nullptr;
|
||||||
|
static pthread_key_t android_thread_jni_env_key_;
|
||||||
|
static jobject android_application_context_ = nullptr;
|
||||||
|
|
||||||
|
static void AndroidThreadJNIEnvDestructor(void* jni_env_pointer) {
|
||||||
|
// The JNIEnv pointer for the main thread is taken externally, the lifetime of
|
||||||
|
// the attachment is not managed by the key.
|
||||||
|
JNIEnv* jni_env = static_cast<JNIEnv*>(jni_env_pointer);
|
||||||
|
if (jni_env && jni_env != android_main_thread_jni_env_) {
|
||||||
|
android_java_vm_->DetachCurrentThread();
|
||||||
|
}
|
||||||
|
// Multiple iterations of destructor invocations can be done - clear.
|
||||||
|
pthread_setspecific(android_thread_jni_env_key_, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializeAndroidAppFromMainThread(int32_t api_level,
|
||||||
|
JNIEnv* main_thread_jni_env,
|
||||||
|
jobject application_context) {
|
||||||
if (android_initializations_++) {
|
if (android_initializations_++) {
|
||||||
// Already initialized for another component in the process.
|
// Already initialized for another component in the process.
|
||||||
return;
|
return;
|
||||||
|
@ -32,6 +54,45 @@ void InitializeAndroidAppFromMainThread(int32_t api_level) {
|
||||||
// subsystem initialization itself.
|
// subsystem initialization itself.
|
||||||
android_api_level_ = api_level;
|
android_api_level_ = api_level;
|
||||||
|
|
||||||
|
android_main_thread_jni_env_ = main_thread_jni_env;
|
||||||
|
if (main_thread_jni_env) {
|
||||||
|
// In a Java VM, not just in a process that runs an executable - set up
|
||||||
|
// the attachment of threads to the Java VM.
|
||||||
|
if (main_thread_jni_env->GetJavaVM(&android_java_vm_) < 0) {
|
||||||
|
// Logging has not been initialized yet.
|
||||||
|
__android_log_write(
|
||||||
|
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
|
||||||
|
"Failed to get the Java VM from the JNI environment of the main "
|
||||||
|
"thread");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
if (pthread_key_create(&android_thread_jni_env_key_,
|
||||||
|
AndroidThreadJNIEnvDestructor)) {
|
||||||
|
__android_log_write(
|
||||||
|
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
|
||||||
|
"Failed to create the thread-specific JNI environment key");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
if (pthread_setspecific(android_thread_jni_env_key_, main_thread_jni_env)) {
|
||||||
|
__android_log_write(
|
||||||
|
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
|
||||||
|
"Failed to set the thread-specific JNI environment pointer for the "
|
||||||
|
"main thread");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
if (application_context) {
|
||||||
|
android_application_context_ =
|
||||||
|
main_thread_jni_env->NewGlobalRef(application_context);
|
||||||
|
if (!android_application_context_) {
|
||||||
|
__android_log_write(
|
||||||
|
ANDROID_LOG_ERROR, "InitializeAndroidAppFromMainThread",
|
||||||
|
"Failed to create a global reference to the application context "
|
||||||
|
"object");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Logging uses threading.
|
// Logging uses threading.
|
||||||
xe::threading::AndroidInitialize();
|
xe::threading::AndroidInitialize();
|
||||||
|
|
||||||
|
@ -40,6 +101,15 @@ void InitializeAndroidAppFromMainThread(int32_t api_level) {
|
||||||
xe::InitializeLogging("xenia");
|
xe::InitializeLogging("xenia");
|
||||||
|
|
||||||
xe::memory::AndroidInitialize();
|
xe::memory::AndroidInitialize();
|
||||||
|
|
||||||
|
if (android_application_context_) {
|
||||||
|
if (!xe::InitializeAndroidSystemForApplicationContext()) {
|
||||||
|
__android_log_write(ANDROID_LOG_ERROR,
|
||||||
|
"InitializeAndroidAppFromMainThread",
|
||||||
|
"Failed to initialize system UI interaction");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShutdownAndroidAppFromMainThread() {
|
void ShutdownAndroidAppFromMainThread() {
|
||||||
|
@ -52,15 +122,36 @@ void ShutdownAndroidAppFromMainThread() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xe::ShutdownAndroidSystem();
|
||||||
|
|
||||||
xe::memory::AndroidShutdown();
|
xe::memory::AndroidShutdown();
|
||||||
|
|
||||||
xe::ShutdownLogging();
|
xe::ShutdownLogging();
|
||||||
|
|
||||||
xe::threading::AndroidShutdown();
|
xe::threading::AndroidShutdown();
|
||||||
|
|
||||||
|
if (android_application_context_) {
|
||||||
|
android_main_thread_jni_env_->DeleteGlobalRef(android_application_context_);
|
||||||
|
android_application_context_ = nullptr;
|
||||||
|
}
|
||||||
|
if (android_java_vm_) {
|
||||||
|
android_java_vm_ = nullptr;
|
||||||
|
pthread_key_delete(android_thread_jni_env_key_);
|
||||||
|
}
|
||||||
|
android_main_thread_jni_env_ = nullptr;
|
||||||
|
|
||||||
android_api_level_ = __ANDROID_API__;
|
android_api_level_ = __ANDROID_API__;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t GetAndroidApiLevel() { return android_api_level_; }
|
int32_t GetAndroidApiLevel() { return android_api_level_; }
|
||||||
|
|
||||||
|
JNIEnv* GetAndroidThreadJNIEnv() {
|
||||||
|
if (!android_java_vm_) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return static_cast<JNIEnv*>(pthread_getspecific(android_thread_jni_env_key_));
|
||||||
|
}
|
||||||
|
|
||||||
|
jobject GetAndroidApplicationContext() { return android_application_context_; }
|
||||||
|
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -10,6 +10,7 @@
|
||||||
#ifndef XENIA_BASE_MAIN_ANDROID_H_
|
#ifndef XENIA_BASE_MAIN_ANDROID_H_
|
||||||
#define XENIA_BASE_MAIN_ANDROID_H_
|
#define XENIA_BASE_MAIN_ANDROID_H_
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
|
@ -27,14 +28,22 @@ namespace xe {
|
||||||
// counting internally.
|
// counting internally.
|
||||||
//
|
//
|
||||||
// In standalone console apps built with $(BUILD_EXECUTABLE), these functions
|
// In standalone console apps built with $(BUILD_EXECUTABLE), these functions
|
||||||
// must be called in `main`.
|
// must be called in `main`, with a null main thread JNI environment.
|
||||||
void InitializeAndroidAppFromMainThread(int32_t api_level);
|
void InitializeAndroidAppFromMainThread(int32_t api_level,
|
||||||
|
JNIEnv* main_thread_jni_env,
|
||||||
|
jobject application_context);
|
||||||
void ShutdownAndroidAppFromMainThread();
|
void ShutdownAndroidAppFromMainThread();
|
||||||
|
|
||||||
// May be the minimum supported level if the initialization was done without a
|
// May be the minimum supported level if the initialization was done without a
|
||||||
// configuration.
|
// configuration.
|
||||||
int32_t GetAndroidApiLevel();
|
int32_t GetAndroidApiLevel();
|
||||||
|
|
||||||
|
// May return null if not in a Java VM process, or in case of a failure to
|
||||||
|
// attach on a non-main thread.
|
||||||
|
JNIEnv* GetAndroidThreadJNIEnv();
|
||||||
|
// Returns the global reference if in an application context, or null otherwise.
|
||||||
|
jobject GetAndroidApplicationContext();
|
||||||
|
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
#endif // XENIA_BASE_MAIN_ANDROID_H_
|
#endif // XENIA_BASE_MAIN_ANDROID_H_
|
||||||
|
|
|
@ -13,10 +13,17 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
|
||||||
|
#if XE_PLATFORM_ANDROID
|
||||||
|
bool InitializeAndroidSystemForApplicationContext();
|
||||||
|
void ShutdownAndroidSystem();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The URL must include the protocol.
|
||||||
void LaunchWebBrowser(const std::string_view url);
|
void LaunchWebBrowser(const std::string_view url);
|
||||||
void LaunchFileExplorer(const std::filesystem::path& path);
|
void LaunchFileExplorer(const std::filesystem::path& path);
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,285 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/main_android.h"
|
||||||
#include "xenia/base/system.h"
|
#include "xenia/base/system.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
|
||||||
|
// To store jmethodIDs persistently, global references to the classes are
|
||||||
|
// required to prevent the classes from being unloaded and reloaded, potentially
|
||||||
|
// changing the method IDs.
|
||||||
|
|
||||||
|
static jclass android_system_application_context_class_ = nullptr;
|
||||||
|
static jmethodID android_system_application_context_start_activity_ = nullptr;
|
||||||
|
|
||||||
|
static jclass android_system_uri_class_ = nullptr;
|
||||||
|
static jmethodID android_system_uri_parse_ = nullptr;
|
||||||
|
|
||||||
|
static jclass android_system_intent_class_ = nullptr;
|
||||||
|
static jfieldID android_system_intent_action_view_field_id_ = nullptr;
|
||||||
|
static jfieldID android_system_intent_flag_activity_new_task_field_id_ =
|
||||||
|
nullptr;
|
||||||
|
static jmethodID android_system_intent_init_action_uri_ = nullptr;
|
||||||
|
static jmethodID android_system_intent_add_flags_ = nullptr;
|
||||||
|
static jobject android_system_intent_action_view_ = nullptr;
|
||||||
|
static jint android_system_intent_flag_activity_new_task_;
|
||||||
|
|
||||||
|
static bool android_system_initialized_ = false;
|
||||||
|
|
||||||
|
bool InitializeAndroidSystemForApplicationContext() {
|
||||||
|
assert_false(android_system_initialized_);
|
||||||
|
|
||||||
|
JNIEnv* jni_env = GetAndroidThreadJNIEnv();
|
||||||
|
if (!jni_env) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
jobject application_context = xe::GetAndroidApplicationContext();
|
||||||
|
if (!application_context) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application context.
|
||||||
|
{
|
||||||
|
{
|
||||||
|
jclass application_context_class_local_ref =
|
||||||
|
jni_env->GetObjectClass(application_context);
|
||||||
|
if (!application_context_class_local_ref) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to get the "
|
||||||
|
"class of the application context");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
android_system_application_context_class_ =
|
||||||
|
reinterpret_cast<jclass>(jni_env->NewGlobalRef(
|
||||||
|
reinterpret_cast<jobject>(application_context_class_local_ref)));
|
||||||
|
jni_env->DeleteLocalRef(application_context_class_local_ref);
|
||||||
|
}
|
||||||
|
if (!android_system_application_context_class_) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to create a "
|
||||||
|
"global reference to the class of the application context");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool application_context_ids_obtained = true;
|
||||||
|
application_context_ids_obtained &=
|
||||||
|
(android_system_application_context_start_activity_ =
|
||||||
|
jni_env->GetMethodID(android_system_application_context_class_,
|
||||||
|
"startActivity",
|
||||||
|
"(Landroid/content/Intent;)V")) != nullptr;
|
||||||
|
if (!application_context_ids_obtained) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to get the "
|
||||||
|
"application context class IDs");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// URI.
|
||||||
|
{
|
||||||
|
{
|
||||||
|
jclass uri_class_local_ref = jni_env->FindClass("android/net/Uri");
|
||||||
|
if (!uri_class_local_ref) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to find the "
|
||||||
|
"URI class");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
android_system_uri_class_ =
|
||||||
|
reinterpret_cast<jclass>(jni_env->NewGlobalRef(
|
||||||
|
reinterpret_cast<jobject>(uri_class_local_ref)));
|
||||||
|
jni_env->DeleteLocalRef(uri_class_local_ref);
|
||||||
|
}
|
||||||
|
if (!android_system_uri_class_) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to create a "
|
||||||
|
"global reference to the URI class");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool uri_ids_obtained = true;
|
||||||
|
uri_ids_obtained &=
|
||||||
|
(android_system_uri_parse_ = jni_env->GetStaticMethodID(
|
||||||
|
android_system_uri_class_, "parse",
|
||||||
|
"(Ljava/lang/String;)Landroid/net/Uri;")) != nullptr;
|
||||||
|
if (!uri_ids_obtained) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to get the URI "
|
||||||
|
"class IDs");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intent.
|
||||||
|
{
|
||||||
|
{
|
||||||
|
jclass intent_class_local_ref =
|
||||||
|
jni_env->FindClass("android/content/Intent");
|
||||||
|
if (!intent_class_local_ref) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to find the "
|
||||||
|
"intent class");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
android_system_intent_class_ =
|
||||||
|
reinterpret_cast<jclass>(jni_env->NewGlobalRef(
|
||||||
|
reinterpret_cast<jobject>(intent_class_local_ref)));
|
||||||
|
jni_env->DeleteLocalRef(intent_class_local_ref);
|
||||||
|
}
|
||||||
|
if (!android_system_intent_class_) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to create a "
|
||||||
|
"global reference to the intent class");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool intent_ids_obtained = true;
|
||||||
|
intent_ids_obtained &= (android_system_intent_action_view_field_id_ =
|
||||||
|
jni_env->GetStaticFieldID(
|
||||||
|
android_system_intent_class_, "ACTION_VIEW",
|
||||||
|
"Ljava/lang/String;")) != nullptr;
|
||||||
|
intent_ids_obtained &=
|
||||||
|
(android_system_intent_flag_activity_new_task_field_id_ =
|
||||||
|
jni_env->GetStaticFieldID(android_system_intent_class_,
|
||||||
|
"FLAG_ACTIVITY_NEW_TASK", "I")) !=
|
||||||
|
nullptr;
|
||||||
|
intent_ids_obtained &=
|
||||||
|
(android_system_intent_init_action_uri_ = jni_env->GetMethodID(
|
||||||
|
android_system_intent_class_, "<init>",
|
||||||
|
"(Ljava/lang/String;Landroid/net/Uri;)V")) != nullptr;
|
||||||
|
intent_ids_obtained &=
|
||||||
|
(android_system_intent_add_flags_ =
|
||||||
|
jni_env->GetMethodID(android_system_intent_class_, "addFlags",
|
||||||
|
"(I)Landroid/content/Intent;")) != nullptr;
|
||||||
|
if (!intent_ids_obtained) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to get the "
|
||||||
|
"intent class IDs");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
jobject intent_action_view_local_ref = jni_env->GetStaticObjectField(
|
||||||
|
android_system_intent_class_,
|
||||||
|
android_system_intent_action_view_field_id_);
|
||||||
|
if (!intent_action_view_local_ref) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to get the "
|
||||||
|
"intent view action string");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
android_system_intent_action_view_ =
|
||||||
|
jni_env->NewGlobalRef(intent_action_view_local_ref);
|
||||||
|
jni_env->DeleteLocalRef(intent_action_view_local_ref);
|
||||||
|
if (!android_system_intent_action_view_) {
|
||||||
|
XELOGE(
|
||||||
|
"InitializeAndroidSystemForApplicationContext: Failed to create a "
|
||||||
|
"global reference to the intent view action string");
|
||||||
|
ShutdownAndroidSystem();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
android_system_intent_flag_activity_new_task_ = jni_env->GetStaticIntField(
|
||||||
|
android_system_intent_class_,
|
||||||
|
android_system_intent_flag_activity_new_task_field_id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
android_system_initialized_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShutdownAndroidSystem() {
|
||||||
|
// May be called from InitializeAndroidSystemForApplicationContext as well.
|
||||||
|
android_system_initialized_ = false;
|
||||||
|
android_system_intent_add_flags_ = nullptr;
|
||||||
|
android_system_intent_init_action_uri_ = nullptr;
|
||||||
|
android_system_intent_flag_activity_new_task_field_id_ = nullptr;
|
||||||
|
android_system_intent_action_view_field_id_ = nullptr;
|
||||||
|
android_system_uri_parse_ = nullptr;
|
||||||
|
android_system_application_context_start_activity_ = nullptr;
|
||||||
|
JNIEnv* jni_env = GetAndroidThreadJNIEnv();
|
||||||
|
if (jni_env) {
|
||||||
|
if (android_system_intent_action_view_) {
|
||||||
|
jni_env->DeleteGlobalRef(android_system_intent_action_view_);
|
||||||
|
}
|
||||||
|
if (android_system_intent_class_) {
|
||||||
|
jni_env->DeleteGlobalRef(android_system_intent_class_);
|
||||||
|
}
|
||||||
|
if (android_system_uri_class_) {
|
||||||
|
jni_env->DeleteGlobalRef(android_system_uri_class_);
|
||||||
|
}
|
||||||
|
if (android_system_application_context_class_) {
|
||||||
|
jni_env->DeleteGlobalRef(android_system_application_context_class_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
android_system_intent_action_view_ = nullptr;
|
||||||
|
android_system_intent_class_ = nullptr;
|
||||||
|
android_system_uri_class_ = nullptr;
|
||||||
|
android_system_application_context_class_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void LaunchWebBrowser(const std::string_view url) {
|
void LaunchWebBrowser(const std::string_view url) {
|
||||||
// TODO(Triang3l): Intent.ACTION_VIEW (need a Java VM for the thread -
|
if (!android_system_initialized_) {
|
||||||
// possibly restrict this to the UI thread).
|
return;
|
||||||
assert_always();
|
}
|
||||||
|
JNIEnv* jni_env = GetAndroidThreadJNIEnv();
|
||||||
|
if (!jni_env) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jobject application_context = GetAndroidApplicationContext();
|
||||||
|
if (!application_context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring uri_string = jni_env->NewStringUTF(std::string(url).c_str());
|
||||||
|
if (!uri_string) {
|
||||||
|
XELOGE("LaunchWebBrowser: Failed to create the URI string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jobject uri = jni_env->CallStaticObjectMethod(
|
||||||
|
android_system_uri_class_, android_system_uri_parse_, uri_string);
|
||||||
|
jni_env->DeleteLocalRef(uri_string);
|
||||||
|
if (!uri) {
|
||||||
|
XELOGE("LaunchWebBrowser: Failed to parse the URI");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jobject intent = jni_env->NewObject(android_system_intent_class_,
|
||||||
|
android_system_intent_init_action_uri_,
|
||||||
|
android_system_intent_action_view_, uri);
|
||||||
|
jni_env->DeleteLocalRef(uri);
|
||||||
|
if (!intent) {
|
||||||
|
XELOGE("LaunchWebBrowser: Failed to create the intent");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Start a new task - the user may want to be able to switch between the
|
||||||
|
// emulator and the newly opened web browser, without having to quit the web
|
||||||
|
// browser to return to the emulator. Also, since the application context, not
|
||||||
|
// the activity, is used, the new task flag is required.
|
||||||
|
{
|
||||||
|
jobject intent_add_flags_result_local_ref = jni_env->CallObjectMethod(
|
||||||
|
intent, android_system_intent_add_flags_,
|
||||||
|
android_system_intent_flag_activity_new_task_);
|
||||||
|
if (intent_add_flags_result_local_ref) {
|
||||||
|
jni_env->DeleteLocalRef(intent_add_flags_result_local_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jni_env->CallVoidMethod(application_context,
|
||||||
|
android_system_application_context_start_activity_,
|
||||||
|
intent);
|
||||||
|
jni_env->DeleteLocalRef(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchFileExplorer(const std::filesystem::path& path) { assert_always(); }
|
void LaunchFileExplorer(const std::filesystem::path& path) { assert_always(); }
|
||||||
|
|
|
@ -141,26 +141,14 @@ bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env,
|
||||||
}
|
}
|
||||||
AConfiguration_fromAssetManager(configuration_, asset_manager_);
|
AConfiguration_fromAssetManager(configuration_, asset_manager_);
|
||||||
|
|
||||||
// Initialize Xenia globals that may depend on the API level, as well as
|
// Get the activity class, needed for the application context here, and for
|
||||||
// logging.
|
// other activity interaction later.
|
||||||
xe::InitializeAndroidAppFromMainThread(
|
|
||||||
AConfiguration_getSdkVersion(configuration_));
|
|
||||||
android_base_initialized_ = true;
|
|
||||||
|
|
||||||
// Initialize interfacing with the WindowedAppActivity.
|
|
||||||
activity_ = ui_thread_jni_env_->NewGlobalRef(activity);
|
|
||||||
if (!activity_) {
|
|
||||||
XELOGE(
|
|
||||||
"AndroidWindowedAppContext: Failed to create a global reference to the "
|
|
||||||
"activity");
|
|
||||||
Shutdown();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
jclass activity_class_local_ref =
|
jclass activity_class_local_ref =
|
||||||
ui_thread_jni_env_->GetObjectClass(activity);
|
ui_thread_jni_env_->GetObjectClass(activity);
|
||||||
if (!activity_class_local_ref) {
|
if (!activity_class_local_ref) {
|
||||||
XELOGE("AndroidWindowedAppContext: Failed to get the activity class");
|
__android_log_write(ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
|
||||||
|
"Failed to get the activity class");
|
||||||
Shutdown();
|
Shutdown();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -170,9 +158,46 @@ bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env,
|
||||||
reinterpret_cast<jobject>(activity_class_local_ref));
|
reinterpret_cast<jobject>(activity_class_local_ref));
|
||||||
}
|
}
|
||||||
if (!activity_class_) {
|
if (!activity_class_) {
|
||||||
|
__android_log_write(
|
||||||
|
ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
|
||||||
|
"Failed to create a global reference to the activity class");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the application context.
|
||||||
|
jmethodID activity_get_application_context = ui_thread_jni_env_->GetMethodID(
|
||||||
|
activity_class_, "getApplicationContext", "()Landroid/content/Context;");
|
||||||
|
if (!activity_get_application_context) {
|
||||||
|
__android_log_write(
|
||||||
|
ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
|
||||||
|
"Failed to get the getApplicationContext method of the activity");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
jobject application_context_init_ref = ui_thread_jni_env_->CallObjectMethod(
|
||||||
|
activity, activity_get_application_context);
|
||||||
|
if (!application_context_init_ref) {
|
||||||
|
__android_log_write(
|
||||||
|
ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
|
||||||
|
"Failed to get the application context from the activity");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Xenia globals that may depend on the base globals and logging.
|
||||||
|
xe::InitializeAndroidAppFromMainThread(
|
||||||
|
AConfiguration_getSdkVersion(configuration_), ui_thread_jni_env_,
|
||||||
|
application_context_init_ref);
|
||||||
|
android_base_initialized_ = true;
|
||||||
|
ui_thread_jni_env_->DeleteLocalRef(application_context_init_ref);
|
||||||
|
|
||||||
|
// Initialize interfacing with the WindowedAppActivity.
|
||||||
|
activity_ = ui_thread_jni_env_->NewGlobalRef(activity);
|
||||||
|
if (!activity_) {
|
||||||
XELOGE(
|
XELOGE(
|
||||||
"AndroidWindowedAppContext: Failed to create a global reference to the "
|
"AndroidWindowedAppContext: Failed to create a global reference to the "
|
||||||
"activity class");
|
"activity");
|
||||||
Shutdown();
|
Shutdown();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -249,11 +274,6 @@ void AndroidWindowedAppContext::Shutdown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
activity_method_finish_ = nullptr;
|
activity_method_finish_ = nullptr;
|
||||||
if (activity_class_) {
|
|
||||||
ui_thread_jni_env_->DeleteGlobalRef(
|
|
||||||
reinterpret_cast<jobject>(activity_class_));
|
|
||||||
activity_class_ = nullptr;
|
|
||||||
}
|
|
||||||
if (activity_) {
|
if (activity_) {
|
||||||
ui_thread_jni_env_->DeleteGlobalRef(activity_);
|
ui_thread_jni_env_->DeleteGlobalRef(activity_);
|
||||||
activity_ = nullptr;
|
activity_ = nullptr;
|
||||||
|
@ -264,6 +284,12 @@ void AndroidWindowedAppContext::Shutdown() {
|
||||||
android_base_initialized_ = false;
|
android_base_initialized_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (activity_class_) {
|
||||||
|
ui_thread_jni_env_->DeleteGlobalRef(
|
||||||
|
reinterpret_cast<jobject>(activity_class_));
|
||||||
|
activity_class_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (configuration_) {
|
if (configuration_) {
|
||||||
AConfiguration_delete(configuration_);
|
AConfiguration_delete(configuration_);
|
||||||
configuration_ = nullptr;
|
configuration_ = nullptr;
|
||||||
|
|
|
@ -95,10 +95,11 @@ class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
|
|
||||||
AConfiguration* configuration_ = nullptr;
|
AConfiguration* configuration_ = nullptr;
|
||||||
|
|
||||||
|
jclass activity_class_ = nullptr;
|
||||||
|
|
||||||
bool android_base_initialized_ = false;
|
bool android_base_initialized_ = false;
|
||||||
|
|
||||||
jobject activity_ = nullptr;
|
jobject activity_ = nullptr;
|
||||||
jclass activity_class_ = nullptr;
|
|
||||||
jmethodID activity_method_finish_ = nullptr;
|
jmethodID activity_method_finish_ = nullptr;
|
||||||
|
|
||||||
// May be read by non-UI threads in NotifyUILoopOfPendingFunctions.
|
// May be read by non-UI threads in NotifyUILoopOfPendingFunctions.
|
||||||
|
|
Loading…
Reference in New Issue