[UI] android.app.NativeActivity > WindowedAppActivity + code style
This commit is contained in:
parent
347c9f01fd
commit
26a2d814da
|
@ -6,3 +6,8 @@ SortIncludes: true
|
||||||
|
|
||||||
# Regroup causes unnecessary noise due to clang-format bug.
|
# Regroup causes unnecessary noise due to clang-format bug.
|
||||||
IncludeBlocks: Preserve
|
IncludeBlocks: Preserve
|
||||||
|
|
||||||
|
---
|
||||||
|
Language: Java
|
||||||
|
DisableFormat: true
|
||||||
|
SortIncludes: false
|
||||||
|
|
|
@ -2,12 +2,23 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="jp.xenia.emulator">
|
package="jp.xenia.emulator">
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.vulkan.level" android:version="0" android:required="true" />
|
<uses-feature
|
||||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x400000" android:required="true" />
|
android:name="android.hardware.vulkan.level"
|
||||||
<!-- Granted automatically - guest sockets -->
|
android:required="true"
|
||||||
|
android:version="0" />
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.vulkan.version"
|
||||||
|
android:required="true"
|
||||||
|
android:version="0x400000" />
|
||||||
|
|
||||||
|
<!-- Granted automatically - guest sockets. -->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<!-- Needs to be requested - loading games from outside the app data directory -->
|
|
||||||
<!-- WRITE_EXTERNAL_STORAGE is not required to write to the external app data directory since API 19 -->
|
<!--
|
||||||
|
Needs to be requested - loading games from outside the app data directory.
|
||||||
|
WRITE_EXTERNAL_STORAGE is not required to write to the external app data directory since API 19.
|
||||||
|
-->
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
@ -17,12 +28,14 @@
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@android:style/Theme.Material.Light">
|
android:theme="@android:style/Theme.Material.Light">
|
||||||
<activity android:name="jp.xenia.emulator.DemoActivity">
|
|
||||||
|
<activity android:name="jp.xenia.emulator.WindowDemoActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -1,12 +0,0 @@
|
||||||
package jp.xenia.emulator;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
public class DemoActivity extends Activity {
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_demo);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package jp.xenia.emulator;
|
||||||
|
|
||||||
|
public class WindowDemoActivity extends WindowedAppActivity {
|
||||||
|
@Override
|
||||||
|
protected String getWindowedAppIdentifier() {
|
||||||
|
return "xenia_ui_window_vulkan_demo";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package jp.xenia.emulator;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public abstract class WindowedAppActivity extends Activity {
|
||||||
|
private static final String TAG = "WindowedAppActivity";
|
||||||
|
|
||||||
|
static {
|
||||||
|
// TODO(Triang3l): Move all demos to libxenia.so.
|
||||||
|
System.loadLibrary("xenia-ui-window-vulkan-demo");
|
||||||
|
}
|
||||||
|
|
||||||
|
private long mAppContext;
|
||||||
|
|
||||||
|
private native long initializeWindowedAppOnCreateNative(
|
||||||
|
String windowedAppIdentifier, AssetManager assetManager);
|
||||||
|
|
||||||
|
private native void onDestroyNative(long appContext);
|
||||||
|
|
||||||
|
protected abstract String getWindowedAppIdentifier();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
mAppContext = initializeWindowedAppOnCreateNative(getWindowedAppIdentifier(), getAssets());
|
||||||
|
if (mAppContext == 0) {
|
||||||
|
Log.e(TAG, "Error initializing the windowed app");
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
if (mAppContext != 0) {
|
||||||
|
onDestroyNative(mAppContext);
|
||||||
|
}
|
||||||
|
mAppContext = 0;
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,6 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context="jp.xenia.emulator.DemoActivity">
|
tools:context="jp.xenia.emulator.WindowDemoActivity">
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/ui/windowed_app.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
#if XE_UI_WINDOWED_APPS_IN_LIBRARY
|
||||||
|
// A zero-initialized pointer to remove dependence on the initialization order
|
||||||
|
// of the map relatively to the app creator proxies.
|
||||||
|
std::unordered_map<std::string, WindowedApp::Creator>* WindowedApp::creators_;
|
||||||
|
#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
|
@ -13,15 +13,17 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/ui/windowed_app_context.h"
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
#if XE_PLATFORM_ANDROID
|
#if XE_PLATFORM_ANDROID
|
||||||
#include <android/native_activity.h>
|
// Multiple apps in a single library instead of separate executables.
|
||||||
|
#define XE_UI_WINDOWED_APPS_IN_LIBRARY 1
|
||||||
#include "xenia/ui/windowed_app_context_android.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -36,6 +38,9 @@ class WindowedApp {
|
||||||
// initialization of platform-specific parts, should preferably be as simple
|
// initialization of platform-specific parts, should preferably be as simple
|
||||||
// as possible).
|
// as possible).
|
||||||
|
|
||||||
|
using Creator = std::unique_ptr<xe::ui::WindowedApp> (*)(
|
||||||
|
xe::ui::WindowedAppContext& app_context);
|
||||||
|
|
||||||
WindowedApp(const WindowedApp& app) = delete;
|
WindowedApp(const WindowedApp& app) = delete;
|
||||||
WindowedApp& operator=(const WindowedApp& app) = delete;
|
WindowedApp& operator=(const WindowedApp& app) = delete;
|
||||||
virtual ~WindowedApp() = default;
|
virtual ~WindowedApp() = default;
|
||||||
|
@ -101,27 +106,67 @@ class WindowedApp {
|
||||||
std::string name_;
|
std::string name_;
|
||||||
std::string positional_options_usage_;
|
std::string positional_options_usage_;
|
||||||
std::vector<std::string> positional_options_;
|
std::vector<std::string> positional_options_;
|
||||||
|
|
||||||
|
#if XE_UI_WINDOWED_APPS_IN_LIBRARY
|
||||||
|
public:
|
||||||
|
class CreatorRegistration {
|
||||||
|
public:
|
||||||
|
CreatorRegistration(const std::string_view identifier, Creator creator) {
|
||||||
|
if (!creators_) {
|
||||||
|
// Will be deleted by the last creator registration's destructor, no
|
||||||
|
// need for a library destructor.
|
||||||
|
creators_ = new std::unordered_map<std::string, WindowedApp::Creator>;
|
||||||
|
}
|
||||||
|
iterator_inserted_ = creators_->emplace(identifier, creator);
|
||||||
|
assert_true(iterator_inserted_.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
~CreatorRegistration() {
|
||||||
|
if (iterator_inserted_.second) {
|
||||||
|
creators_->erase(iterator_inserted_.first);
|
||||||
|
if (creators_->empty()) {
|
||||||
|
delete creators_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::pair<std::unordered_map<std::string, Creator>::iterator, bool>
|
||||||
|
iterator_inserted_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Creator GetCreator(const std::string& identifier) {
|
||||||
|
if (!creators_) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto it = creators_->find(identifier);
|
||||||
|
return it != creators_->end() ? it->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::unordered_map<std::string, Creator>* creators_;
|
||||||
|
#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY
|
||||||
};
|
};
|
||||||
|
|
||||||
#if XE_PLATFORM_ANDROID
|
#if XE_UI_WINDOWED_APPS_IN_LIBRARY
|
||||||
// Multiple apps in a single library. ANativeActivity_onCreate chosen via
|
// Multiple apps in a single library.
|
||||||
// android.app.func_name of the NativeActivity of each app.
|
#define XE_DEFINE_WINDOWED_APP(identifier, creator) \
|
||||||
#define XE_DEFINE_WINDOWED_APP(export_name, creator) \
|
namespace xe { \
|
||||||
__attribute__((visibility("default"))) extern "C" void export_name( \
|
namespace ui { \
|
||||||
ANativeActivity* activity, void* saved_state, size_t saved_state_size) { \
|
namespace windowed_app_creator_registrations { \
|
||||||
xe::ui::AndroidWindowedAppContext::StartAppOnActivityCreate( \
|
xe::ui::WindowedApp::CreatorRegistration identifier(#identifier, creator); \
|
||||||
activity, saved_state, saved_state_size, creator); \
|
} \
|
||||||
|
} \
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Separate executables for each app.
|
// Separate executables for each app.
|
||||||
std::unique_ptr<WindowedApp> (*GetWindowedAppCreator())(
|
std::unique_ptr<WindowedApp> (*GetWindowedAppCreator())(
|
||||||
WindowedAppContext& app_context);
|
WindowedAppContext& app_context);
|
||||||
#define XE_DEFINE_WINDOWED_APP(export_name, creator) \
|
#define XE_DEFINE_WINDOWED_APP(identifier, creator) \
|
||||||
std::unique_ptr<xe::ui::WindowedApp> (*xe::ui::GetWindowedAppCreator())( \
|
xe::ui::WindowedApp::Creator xe::ui::GetWindowedAppCreator() { \
|
||||||
xe::ui::WindowedAppContext & app_context) { \
|
return creator; \
|
||||||
return creator; \
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -9,10 +9,12 @@
|
||||||
|
|
||||||
#include "xenia/ui/windowed_app_context_android.h"
|
#include "xenia/ui/windowed_app_context_android.h"
|
||||||
|
|
||||||
|
#include <android/asset_manager_jni.h>
|
||||||
#include <android/configuration.h>
|
#include <android/configuration.h>
|
||||||
|
#include <android/log.h>
|
||||||
#include <android/looper.h>
|
#include <android/looper.h>
|
||||||
#include <android/native_activity.h>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <jni.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
@ -25,30 +27,6 @@
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
void AndroidWindowedAppContext::StartAppOnActivityCreate(
|
|
||||||
ANativeActivity* activity, [[maybe_unused]] void* saved_state,
|
|
||||||
[[maybe_unused]] size_t saved_state_size,
|
|
||||||
std::unique_ptr<WindowedApp> (*app_creator)(
|
|
||||||
WindowedAppContext& app_context)) {
|
|
||||||
// TODO(Triang3l): Pass the launch options from the Intent or the saved
|
|
||||||
// instance state.
|
|
||||||
AndroidWindowedAppContext* app_context = new AndroidWindowedAppContext;
|
|
||||||
if (!app_context->Initialize(activity)) {
|
|
||||||
delete app_context;
|
|
||||||
ANativeActivity_finish(activity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// The pointer is now held by the Activity as its ANativeActivity::instance,
|
|
||||||
// until the destruction.
|
|
||||||
if (!app_context->InitializeApp(app_creator)) {
|
|
||||||
// InitializeApp might have sent commands to the UI thread looper callback
|
|
||||||
// pipe, perform deferred destruction.
|
|
||||||
app_context->RequestDestruction();
|
|
||||||
ANativeActivity_finish(activity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() {
|
void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() {
|
||||||
// Don't check ui_thread_looper_callback_registered_, as it's owned
|
// Don't check ui_thread_looper_callback_registered_, as it's owned
|
||||||
// exclusively by the UI thread, while this may be called by any, and in case
|
// exclusively by the UI thread, while this may be called by any, and in case
|
||||||
|
@ -69,22 +47,145 @@ void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() {
|
||||||
|
|
||||||
void AndroidWindowedAppContext::PlatformQuitFromUIThread() {
|
void AndroidWindowedAppContext::PlatformQuitFromUIThread() {
|
||||||
// All the shutdown will be done in onDestroy of the activity.
|
// All the shutdown will be done in onDestroy of the activity.
|
||||||
ANativeActivity_finish(activity_);
|
if (activity_ && activity_method_finish_) {
|
||||||
|
ui_thread_jni_env_->CallVoidMethod(activity_, activity_method_finish_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidWindowedAppContext*
|
||||||
|
AndroidWindowedAppContext::JniActivityInitializeWindowedAppOnCreate(
|
||||||
|
JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier,
|
||||||
|
jobject asset_manager) {
|
||||||
|
WindowedApp::Creator app_creator;
|
||||||
|
{
|
||||||
|
const char* windowed_app_identifier_c_str =
|
||||||
|
jni_env->GetStringUTFChars(windowed_app_identifier, nullptr);
|
||||||
|
if (!windowed_app_identifier_c_str) {
|
||||||
|
__android_log_write(
|
||||||
|
ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
|
||||||
|
"Failed to get the UTF-8 string for the windowed app identifier");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
app_creator = WindowedApp::GetCreator(windowed_app_identifier_c_str);
|
||||||
|
if (!app_creator) {
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
|
||||||
|
"Failed to get the creator for the windowed app %s",
|
||||||
|
windowed_app_identifier_c_str);
|
||||||
|
jni_env->ReleaseStringUTFChars(windowed_app_identifier,
|
||||||
|
windowed_app_identifier_c_str);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
jni_env->ReleaseStringUTFChars(windowed_app_identifier,
|
||||||
|
windowed_app_identifier_c_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidWindowedAppContext* app_context = new AndroidWindowedAppContext;
|
||||||
|
if (!app_context->Initialize(jni_env, activity, asset_manager)) {
|
||||||
|
delete app_context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!app_context->InitializeApp(app_creator)) {
|
||||||
|
// InitializeApp might have sent commands to the UI thread looper callback
|
||||||
|
// pipe, perform deferred destruction.
|
||||||
|
app_context->RequestDestruction();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return app_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWindowedAppContext::JniActivityOnDestroy() {
|
||||||
|
if (app_) {
|
||||||
|
app_->InvokeOnDestroy();
|
||||||
|
app_.reset();
|
||||||
|
}
|
||||||
|
RequestDestruction();
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidWindowedAppContext::~AndroidWindowedAppContext() { Shutdown(); }
|
AndroidWindowedAppContext::~AndroidWindowedAppContext() { Shutdown(); }
|
||||||
|
|
||||||
bool AndroidWindowedAppContext::Initialize(ANativeActivity* activity) {
|
bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env,
|
||||||
int32_t api_level;
|
jobject activity,
|
||||||
{
|
jobject asset_manager) {
|
||||||
AConfiguration* configuration = AConfiguration_new();
|
// Xenia logging is not initialized yet - use __android_log_write or
|
||||||
AConfiguration_fromAssetManager(configuration, activity->assetManager);
|
// __android_log_print until InitializeAndroidAppFromMainThread is done.
|
||||||
api_level = AConfiguration_getSdkVersion(configuration);
|
|
||||||
AConfiguration_delete(configuration);
|
ui_thread_jni_env_ = ui_thread_jni_env;
|
||||||
|
|
||||||
|
// Initialize the asset manager for retrieving the current configuration.
|
||||||
|
asset_manager_jobject_ = ui_thread_jni_env_->NewGlobalRef(asset_manager);
|
||||||
|
if (!asset_manager_jobject_) {
|
||||||
|
__android_log_write(
|
||||||
|
ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
|
||||||
|
"Failed to create a global reference to the asset manager");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
xe::InitializeAndroidAppFromMainThread(api_level);
|
asset_manager_ =
|
||||||
|
AAssetManager_fromJava(ui_thread_jni_env_, asset_manager_jobject_);
|
||||||
|
if (!asset_manager_) {
|
||||||
|
__android_log_write(ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
|
||||||
|
"Failed to create get the AAssetManager");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the initial configuration.
|
||||||
|
configuration_ = AConfiguration_new();
|
||||||
|
if (!configuration_) {
|
||||||
|
__android_log_write(ANDROID_LOG_ERROR, "AndroidWindowedAppContext",
|
||||||
|
"Failed to create an AConfiguration");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AConfiguration_fromAssetManager(configuration_, asset_manager_);
|
||||||
|
|
||||||
|
// Initialize Xenia globals that may depend on the API level, as well as
|
||||||
|
// logging.
|
||||||
|
xe::InitializeAndroidAppFromMainThread(
|
||||||
|
AConfiguration_getSdkVersion(configuration_));
|
||||||
android_base_initialized_ = true;
|
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 =
|
||||||
|
ui_thread_jni_env_->GetObjectClass(activity);
|
||||||
|
if (!activity_class_local_ref) {
|
||||||
|
XELOGE("AndroidWindowedAppContext: Failed to get the activity class");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
activity_class_ = reinterpret_cast<jclass>(ui_thread_jni_env_->NewGlobalRef(
|
||||||
|
reinterpret_cast<jobject>(activity_class_local_ref)));
|
||||||
|
ui_thread_jni_env_->DeleteLocalRef(
|
||||||
|
reinterpret_cast<jobject>(activity_class_local_ref));
|
||||||
|
}
|
||||||
|
if (!activity_class_) {
|
||||||
|
XELOGE(
|
||||||
|
"AndroidWindowedAppContext: Failed to create a global reference to the "
|
||||||
|
"activity class");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool activity_ids_obtained = true;
|
||||||
|
activity_ids_obtained &=
|
||||||
|
(activity_method_finish_ = ui_thread_jni_env_->GetMethodID(
|
||||||
|
activity_class_, "finish", "()V")) != nullptr;
|
||||||
|
if (!activity_ids_obtained) {
|
||||||
|
XELOGE("AndroidWindowedAppContext: Failed to get the activity class IDs");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize sending commands to the UI thread looper callback, for
|
// Initialize sending commands to the UI thread looper callback, for
|
||||||
// requesting function calls in the UI thread.
|
// requesting function calls in the UI thread.
|
||||||
ui_thread_looper_ = ALooper_forThread();
|
ui_thread_looper_ = ALooper_forThread();
|
||||||
|
@ -117,10 +218,6 @@ bool AndroidWindowedAppContext::Initialize(ANativeActivity* activity) {
|
||||||
}
|
}
|
||||||
ui_thread_looper_callback_registered_ = true;
|
ui_thread_looper_callback_registered_ = true;
|
||||||
|
|
||||||
activity_ = activity;
|
|
||||||
activity_->instance = this;
|
|
||||||
activity_->callbacks->onDestroy = OnActivityDestroy;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,12 +232,6 @@ void AndroidWindowedAppContext::Shutdown() {
|
||||||
assert_null(activity_window_);
|
assert_null(activity_window_);
|
||||||
activity_window_ = nullptr;
|
activity_window_ = nullptr;
|
||||||
|
|
||||||
if (activity_) {
|
|
||||||
activity_->callbacks->onDestroy = nullptr;
|
|
||||||
activity_->instance = nullptr;
|
|
||||||
activity_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ui_thread_looper_callback_registered_) {
|
if (ui_thread_looper_callback_registered_) {
|
||||||
ALooper_removeFd(ui_thread_looper_, ui_thread_looper_callback_pipe_[0]);
|
ALooper_removeFd(ui_thread_looper_, ui_thread_looper_callback_pipe_[0]);
|
||||||
ui_thread_looper_callback_registered_ = false;
|
ui_thread_looper_callback_registered_ = false;
|
||||||
|
@ -157,10 +248,34 @@ void AndroidWindowedAppContext::Shutdown() {
|
||||||
ui_thread_looper_ = nullptr;
|
ui_thread_looper_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activity_method_finish_ = nullptr;
|
||||||
|
if (activity_class_) {
|
||||||
|
ui_thread_jni_env_->DeleteGlobalRef(
|
||||||
|
reinterpret_cast<jobject>(activity_class_));
|
||||||
|
activity_class_ = nullptr;
|
||||||
|
}
|
||||||
|
if (activity_) {
|
||||||
|
ui_thread_jni_env_->DeleteGlobalRef(activity_);
|
||||||
|
activity_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (android_base_initialized_) {
|
if (android_base_initialized_) {
|
||||||
xe::ShutdownAndroidAppFromMainThread();
|
xe::ShutdownAndroidAppFromMainThread();
|
||||||
android_base_initialized_ = false;
|
android_base_initialized_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (configuration_) {
|
||||||
|
AConfiguration_delete(configuration_);
|
||||||
|
configuration_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
asset_manager_ = nullptr;
|
||||||
|
if (asset_manager_jobject_) {
|
||||||
|
ui_thread_jni_env_->DeleteGlobalRef(asset_manager_jobject_);
|
||||||
|
asset_manager_jobject_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_thread_jni_env_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidWindowedAppContext::RequestDestruction() {
|
void AndroidWindowedAppContext::RequestDestruction() {
|
||||||
|
@ -260,15 +375,26 @@ bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr<WindowedApp> (
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidWindowedAppContext::OnActivityDestroy(ANativeActivity* activity) {
|
|
||||||
auto& app_context =
|
|
||||||
*static_cast<AndroidWindowedAppContext*>(activity->instance);
|
|
||||||
if (app_context.app_) {
|
|
||||||
app_context.app_->InvokeOnDestroy();
|
|
||||||
app_context.app_.reset();
|
|
||||||
}
|
|
||||||
app_context.RequestDestruction();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_jp_xenia_emulator_WindowedAppActivity_initializeWindowedAppOnCreateNative(
|
||||||
|
JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier,
|
||||||
|
jobject asset_manager) {
|
||||||
|
return reinterpret_cast<jlong>(
|
||||||
|
xe::ui::AndroidWindowedAppContext ::
|
||||||
|
JniActivityInitializeWindowedAppOnCreate(
|
||||||
|
jni_env, activity, windowed_app_identifier, asset_manager));
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_jp_xenia_emulator_WindowedAppActivity_onDestroyNative(
|
||||||
|
JNIEnv* jni_env, jobject activity, jlong app_context_ptr) {
|
||||||
|
reinterpret_cast<xe::ui::AndroidWindowedAppContext*>(app_context_ptr)
|
||||||
|
->JniActivityOnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
|
@ -10,8 +10,10 @@
|
||||||
#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
|
#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
|
||||||
#define XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
|
#define XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
|
||||||
|
|
||||||
|
#include <android/asset_manager.h>
|
||||||
|
#include <android/configuration.h>
|
||||||
#include <android/looper.h>
|
#include <android/looper.h>
|
||||||
#include <android/native_activity.h>
|
#include <jni.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
@ -25,13 +27,6 @@ class WindowedApp;
|
||||||
|
|
||||||
class AndroidWindowedAppContext final : public WindowedAppContext {
|
class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
public:
|
public:
|
||||||
// For calling from android.app.func_name exports.
|
|
||||||
static void StartAppOnActivityCreate(
|
|
||||||
ANativeActivity* activity, void* saved_state, size_t saved_state_size,
|
|
||||||
std::unique_ptr<WindowedApp> (*app_creator)(
|
|
||||||
WindowedAppContext& app_context));
|
|
||||||
|
|
||||||
ANativeActivity* activity() const { return activity_; }
|
|
||||||
WindowedApp* app() const { return app_.get(); }
|
WindowedApp* app() const { return app_.get(); }
|
||||||
|
|
||||||
void NotifyUILoopOfPendingFunctions() override;
|
void NotifyUILoopOfPendingFunctions() override;
|
||||||
|
@ -45,6 +40,12 @@ class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
AndroidWindow* GetActivityWindow() const { return activity_window_; }
|
AndroidWindow* GetActivityWindow() const { return activity_window_; }
|
||||||
void SetActivityWindow(AndroidWindow* window) { activity_window_ = window; }
|
void SetActivityWindow(AndroidWindow* window) { activity_window_ = window; }
|
||||||
|
|
||||||
|
// For calling from WindowedAppActivity native methods.
|
||||||
|
static AndroidWindowedAppContext* JniActivityInitializeWindowedAppOnCreate(
|
||||||
|
JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier,
|
||||||
|
jobject asset_manager);
|
||||||
|
void JniActivityOnDestroy();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class UIThreadLooperCallbackCommand : uint8_t {
|
enum class UIThreadLooperCallbackCommand : uint8_t {
|
||||||
kDestroy,
|
kDestroy,
|
||||||
|
@ -55,13 +56,14 @@ class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
|
|
||||||
// Don't delete this object directly externally if successfully initialized as
|
// Don't delete this object directly externally if successfully initialized as
|
||||||
// the looper may still execute the callback for pending commands after an
|
// the looper may still execute the callback for pending commands after an
|
||||||
// external ANativeActivity_removeFd, and the callback receives a pointer to
|
// external ALooper_removeFd, and the callback receives a pointer to the
|
||||||
// the context - deletion must be deferred and done in the callback itself.
|
// context - deletion must be deferred and done in the callback itself.
|
||||||
// Defined in the translation unit where WindowedApp is complete because of
|
// Defined in the translation unit where WindowedApp is complete because of
|
||||||
// std::unique_ptr.
|
// std::unique_ptr.
|
||||||
~AndroidWindowedAppContext();
|
~AndroidWindowedAppContext();
|
||||||
|
|
||||||
bool Initialize(ANativeActivity* activity);
|
bool Initialize(JNIEnv* ui_thread_jni_env, jobject activity,
|
||||||
|
jobject asset_manager);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
// Call this function instead of deleting the object directly, so if needed,
|
// Call this function instead of deleting the object directly, so if needed,
|
||||||
|
@ -75,10 +77,29 @@ class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
bool InitializeApp(std::unique_ptr<WindowedApp> (*app_creator)(
|
bool InitializeApp(std::unique_ptr<WindowedApp> (*app_creator)(
|
||||||
WindowedAppContext& app_context));
|
WindowedAppContext& app_context));
|
||||||
|
|
||||||
static void OnActivityDestroy(ANativeActivity* activity);
|
// Useful notes about JNI usage on Android within Xenia:
|
||||||
|
// - All static libraries defining JNI native functions must be linked to
|
||||||
|
// shared libraries via LOCAL_WHOLE_STATIC_LIBRARIES.
|
||||||
|
// - If method or field IDs are cached, a global reference to the class needs
|
||||||
|
// to be held - it prevents the class from being unloaded by the class
|
||||||
|
// loaders (in a way that would make the IDs invalid when it's reloaded).
|
||||||
|
// - GetStringUTFChars (UTF-8) returns null-terminated strings, GetStringChars
|
||||||
|
// (UTF-16) does not.
|
||||||
|
JNIEnv* ui_thread_jni_env_ = nullptr;
|
||||||
|
|
||||||
|
// The object reference must be held by the app according to
|
||||||
|
// AAssetManager_fromJava documentation.
|
||||||
|
jobject asset_manager_jobject_ = nullptr;
|
||||||
|
AAssetManager* asset_manager_ = nullptr;
|
||||||
|
|
||||||
|
AConfiguration* configuration_ = nullptr;
|
||||||
|
|
||||||
bool android_base_initialized_ = false;
|
bool android_base_initialized_ = false;
|
||||||
|
|
||||||
|
jobject activity_ = nullptr;
|
||||||
|
jclass activity_class_ = nullptr;
|
||||||
|
jmethodID activity_method_finish_ = nullptr;
|
||||||
|
|
||||||
// May be read by non-UI threads in NotifyUILoopOfPendingFunctions.
|
// May be read by non-UI threads in NotifyUILoopOfPendingFunctions.
|
||||||
ALooper* ui_thread_looper_ = nullptr;
|
ALooper* ui_thread_looper_ = nullptr;
|
||||||
// [1] (the write file descriptor) may be referenced as read-only by non-UI
|
// [1] (the write file descriptor) may be referenced as read-only by non-UI
|
||||||
|
@ -86,11 +107,6 @@ class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
std::array<int, 2> ui_thread_looper_callback_pipe_{-1, -1};
|
std::array<int, 2> ui_thread_looper_callback_pipe_{-1, -1};
|
||||||
bool ui_thread_looper_callback_registered_ = false;
|
bool ui_thread_looper_callback_registered_ = false;
|
||||||
|
|
||||||
// TODO(Triang3l): Switch from ANativeActivity to the context itself being the
|
|
||||||
// object for communication with the Java code when NativeActivity isn't used
|
|
||||||
// anymore as its functionality is heavily limited.
|
|
||||||
ANativeActivity* activity_ = nullptr;
|
|
||||||
|
|
||||||
AndroidWindow* activity_window_ = nullptr;
|
AndroidWindow* activity_window_ = nullptr;
|
||||||
|
|
||||||
std::unique_ptr<WindowedApp> app_;
|
std::unique_ptr<WindowedApp> app_;
|
||||||
|
|
Loading…
Reference in New Issue