Files
Xenia-Canary/src/xenia/base/system_android.cc
Gliniak 9dfb0d0b68 [Base] Moved IsUseNexusForGameBarEnabled from emulator_window to system.h
This will remove direct usage of system specific code from emulator_window
2024-12-26 23:42:31 +01:00

301 lines
11 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include <jni.h>
#include <cstring>
#include <string>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/main_android.h"
#include "xenia/base/system.h"
namespace xe {
// To store method and field IDs persistently, global references to the classes
// are required to prevent the classes from being unloaded and reloaded,
// potentially changing the 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 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;
jfieldID intent_action_view_id;
intent_ids_obtained &= (intent_action_view_id = jni_env->GetStaticFieldID(
android_system_intent_class_, "ACTION_VIEW",
"Ljava/lang/String;")) != nullptr;
jfieldID intent_flag_activity_new_task_id;
intent_ids_obtained &=
(intent_flag_activity_new_task_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_, intent_action_view_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_, intent_flag_activity_new_task_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_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) {
if (!android_system_initialized_) {
return;
}
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 ShowSimpleMessageBox(SimpleMessageBoxType type, std::string_view message) {
// TODO(Triang3l): Likely not needed much at all. ShowSimpleMessageBox is a
// concept pretty unfriendly to platforms like Android because it's blocking,
// and because it can be called from threads other than the UI thread. In the
// normal execution flow, dialogs should preferably be asynchronous, and used
// only in the UI thread. However, non-blocking messages may be good for error
// reporting - investigate the usage of Toasts with respect to threads, and
// aborting the process immediately after showing a Toast. For a Toast, the
// Java VM for the calling thread is needed.
}
bool SetProcessPriorityClass(const uint32_t priority_class) { return true; }
bool IsUseNexusForGameBarEnabled() { return false; }
} // namespace xe