#include "android.h"
#if defined(SK_OS_ANDROID)
#include "../log.h"
#include "../device.h"
#include "../sk_math.h"
#include "../xr_backends/openxr.h"
#include "../systems/render.h"
#include "../systems/system.h"
#include "../_stereokit.h"
#include "../libraries/sokol_time.h"
#include <sk_gpu.h>
#include <android/native_activity.h>
#include <android/native_window_jni.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <android/font_matcher.h>
#include <android/font.h>
#include <unistd.h>
#include <dlfcn.h>
namespace sk {
struct window_event_t {
platform_evt_ type;
platform_evt_data_t data;
};
struct window_t {
array_t<window_event_t> events;
ANativeWindow* window;
skg_swapchain_t swapchain;
bool has_swapchain;
bool uses_swapchain;
};
struct platform_android_persistent_state_t {
JavaVM *vm;
ANativeWindow *next_window;
jobject next_window_xam;
bool next_win_ready;
};
struct platform_android_state_t {
JNIEnv *env;
jobject activity;
AAssetManager *asset_manager;
jobject asset_manager_obj;
window_t window;
};
static platform_android_state_t local = {};
static platform_android_persistent_state_t local_persist = {};
void platform_win_resize(platform_win_t window_id, int32_t width, int32_t height);
#if defined(SK_BUILD_SHARED)
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) {
local_persist.vm = vm;
return JNI_VERSION_1_6;
}
extern "C" jint JNI_OnLoad_L(JavaVM* vm, void* reserved) {
return JNI_OnLoad(vm, reserved);
}
#endif
bool platform_impl_init() {
const sk_settings_t* settings = sk_get_settings_ref();
local = {};
local.activity = (jobject)settings->android_activity;
if (local_persist.vm == nullptr)
local_persist.vm = (JavaVM*)settings->android_java_vm;
if (local_persist.vm == nullptr || local.activity == nullptr) {
log_fail_reason(95, log_error, "Couldn't find Android's Java VM or Activity, you should load the StereoKitC library with something like Xamarin's JavaSystem.LoadLibrary, or manually assign it using sk_set_settings()");
return false;
}
int result = local_persist.vm->GetEnv((void **)&local.env, JNI_VERSION_1_6);
if (result == JNI_EDETACHED) {
if (local_persist.vm->AttachCurrentThread(&local.env, nullptr) != JNI_OK) {
log_fail_reason(95, log_error, "Couldn't attach the Java Environment to the current thread!");
return false;
}
} else if (result != JNI_OK) {
log_fail_reason(95, log_error, "Couldn't get the Java Environment from the VM, this needs to be called from the main thread.");
return false;
}
jclass activity_class = local.env->GetObjectClass(local.activity);
jmethodID activity_class_getAssets = local.env->GetMethodID(activity_class, "getAssets", "()Landroid/content/res/AssetManager;");
jobject asset_manager = local.env->CallObjectMethod(local.activity, activity_class_getAssets); local.asset_manager_obj = local.env->NewGlobalRef(asset_manager);
local.asset_manager = AAssetManager_fromJava(local.env, local.asset_manager_obj);
if (local.asset_manager == nullptr) {
log_fail_reason(95, log_error, "Couldn't get the Android asset manager!");
return false;
}
local.env->DeleteLocalRef(activity_class);
local.env->DeleteLocalRef(asset_manager);
#if defined(SK_DYNAMIC_OPENXR)
if (settings->mode == app_mode_xr && dlopen("libopenxr_loader.so", RTLD_NOW) == nullptr) {
log_fail_reason(95, log_error, "openxr_loader failed to load!");
return false;
}
#endif
return true;
}
void platform_impl_shutdown() {
local.env->DeleteGlobalRef(local.asset_manager_obj);
}
void platform_impl_step() {
if (!local_persist.next_win_ready) return;
if (local_persist.next_window_xam != nullptr) {
local_persist.next_window = ANativeWindow_fromSurface(local.env, local_persist.next_window_xam);
local_persist.next_window_xam = nullptr;
}
if (local.window.window && local.window.window == local_persist.next_window) {
int32_t width = ANativeWindow_getWidth (local.window.window);
int32_t height = ANativeWindow_getHeight(local.window.window);
platform_win_resize(1, width, height);
} else {
if (local.window.has_swapchain) {
local.window.has_swapchain = false;
skg_swapchain_destroy(&local.window.swapchain);
}
local.window.window = (ANativeWindow*)local_persist.next_window;
if (local.window.window) {
int32_t width = ANativeWindow_getWidth (local.window.window);
int32_t height = ANativeWindow_getHeight(local.window.window);
platform_win_resize(1, width, height);
}
}
local_persist.next_win_ready = false;
local_persist.next_window = nullptr;
}
void platform_win_resize(platform_win_t window_id, int32_t width, int32_t height) {
if (window_id != 1) return;
window_t * win = &local.window;
width = maxi(1, width);
height = maxi(1, height);
window_event_t e = { platform_evt_resize };
e.data.resize = { width, height };
win->events.add(e);
if (win->uses_swapchain == false) return;
if (win->has_swapchain == false) {
skg_tex_fmt_ color_fmt = skg_tex_fmt_rgba32_linear;
skg_tex_fmt_ depth_fmt = (skg_tex_fmt_)render_preferred_depth_fmt();
win->swapchain = skg_swapchain_create(win->window, color_fmt, skg_tex_fmt_none, width, height);
win->has_swapchain = true;
log_diagf("Created swapchain: %dx%d color:%s depth:%s", win->swapchain.width, win->swapchain.height, render_fmt_name((tex_format_)color_fmt), render_fmt_name((tex_format_)depth_fmt));
}
else if (width == win->swapchain.width && height == win->swapchain.height) {
skg_swapchain_resize(&win->swapchain, width, height);
}
}
void android_set_window(void *window) {
local_persist.next_window = (ANativeWindow*)window;
local_persist.next_win_ready = true;
}
void android_set_window_xam(void *window) {
local_persist.next_window_xam = (jobject)window;
local_persist.next_win_ready = true;
}
bool android_read_asset(const char* asset_name, void** out_data, size_t* out_size) {
AAsset *asset = AAssetManager_open(local.asset_manager, asset_name, AASSET_MODE_BUFFER);
if (asset) {
*out_size = AAsset_getLength(asset);
*out_data = sk_malloc(*out_size + 1);
AAsset_read (asset, *out_data, *out_size);
AAsset_close(asset);
((uint8_t *)*out_data)[*out_size] = 0;
return true;
}
return false;
}
void *backend_android_get_java_vm () { return local_persist.vm; }
void *backend_android_get_activity() { return local.activity; }
void *backend_android_get_jni_env () { return local.env; }
platform_win_type_ platform_win_type() { return platform_win_type_existing; }
platform_win_t platform_win_make(const char* title, recti_t win_rect, platform_surface_ surface_type) { return -1; }
platform_win_t platform_win_get_existing(platform_surface_ surface_type) {
window_t* win = &local.window;
if (surface_type == platform_surface_swapchain) {
win->uses_swapchain = true;
if (win->window) {
int32_t width = ANativeWindow_getWidth (win->window);
int32_t height = ANativeWindow_getHeight(win->window);
platform_win_resize(1, width, height);
}
}
return 1;
}
void platform_win_destroy(platform_win_t window) {
if (window != 1) return;
window_t* win = &local.window;
if (win->has_swapchain) {
skg_swapchain_destroy(&win->swapchain);
}
win->events.free();
*win = {};
}
void platform_check_events() {
}
bool platform_win_next_event(platform_win_t window_id, platform_evt_* out_event, platform_evt_data_t* out_event_data) {
if (window_id != 1) return false;
window_t* win = &local.window;
if (win->events.count > 0) {
*out_event = win->events[0].type;
*out_event_data = win->events[0].data;
win->events.remove(0);
return true;
} return false;
}
skg_swapchain_t* platform_win_get_swapchain(platform_win_t window) {
if (window != 1) return nullptr;
window_t* win = &local.window;
return win->has_swapchain ? &win->swapchain : nullptr;
}
recti_t platform_win_rect(platform_win_t window_id) {
if (window_id != 1) return {};
window_t* win = &local.window;
return recti_t{ 0, 0,
win->swapchain.width,
win->swapchain.height };
}
bool platform_key_save_bytes(const char* key, void* data, int32_t data_size) { return false; }
bool platform_key_load_bytes(const char* key, void* ref_buffer, int32_t buffer_size) { return false; }
void platform_msgbox_err(const char* text, const char* header) {
log_warn("No messagebox capability for this platform!");
}
bool platform_get_cursor(vec2 *out_pos) { *out_pos = { 0,0 }; return false; }
void platform_set_cursor(vec2 window_pos) { }
float platform_get_scroll() { return 0; }
void platform_xr_keyboard_show (bool show) { }
bool platform_xr_keyboard_present() { return false; }
bool platform_xr_keyboard_visible() { return false; }
font_t platform_default_font() {
array_t<const char *> fonts = array_t<const char *>::make(2);
font_t result = nullptr;
const char *file_latin = nullptr;
const char *file_japanese = nullptr;
#if __ANDROID_API__ >= 29
AFontMatcher *matcher = AFontMatcher_create();
AFont *font_latin = AFontMatcher_match(matcher, "sans-serif", (uint16_t*)u"A", 1, nullptr);
AFont *font_japanese = AFontMatcher_match(matcher, "sans-serif", (uint16_t*)u"\u3042", 1, nullptr);
if (font_latin ) file_latin = AFont_getFontFilePath(font_latin);
if (font_japanese) file_japanese = AFont_getFontFilePath(font_japanese);
#endif
if (file_latin != nullptr) fonts.add(file_latin);
else if (platform_file_exists("/system/fonts/NotoSans-Regular.ttf")) fonts.add("/system/fonts/NotoSans-Regular.ttf");
else if (platform_file_exists("/system/fonts/Roboto-Regular.ttf" )) fonts.add("/system/fonts/Roboto-Regular.ttf");
else if (platform_file_exists("/system/fonts/DroidSans.ttf" )) fonts.add("/system/fonts/DroidSans.ttf");
if (file_japanese != nullptr) fonts.add(file_japanese);
else if (platform_file_exists("/system/fonts/NotoSansCJK-Regular.ttc")) fonts.add("/system/fonts/NotoSansCJK-Regular.ttc");
else if (platform_file_exists("/system/fonts/DroidSansJapanese.ttf" )) fonts.add("/system/fonts/DroidSansJapanese.ttf");
if (fonts.count > 0)
result = font_create_files(fonts.data, fonts.count);
#if __ANDROID_API__ >= 29
if (font_latin ) AFont_close(font_latin);
if (font_japanese) AFont_close(font_japanese);
AFontMatcher_destroy(matcher);
#endif
fonts.free();
return result;
}
void platform_iterate_dir(const char* directory_path, void* callback_data, void (*on_item)(void* callback_data, const char* name, const platform_file_attr_t file_attr)) {}
#include <unwind.h>
#include <dlfcn.h>
#include <cxxabi.h>
struct android_backtrace_state {
void **current;
void **end;
};
void platform_print_callstack() {
const int max = 100;
void* buffer[max];
android_backtrace_state state;
state.current = buffer;
state.end = buffer + max;
_Unwind_Backtrace([](struct _Unwind_Context* context, void* arg) {
android_backtrace_state* state = (android_backtrace_state *)arg;
uintptr_t pc = _Unwind_GetIP(context);
if (pc) {
if (state->current == state->end)
return (_Unwind_Reason_Code)_URC_END_OF_STACK;
else
*state->current++ = reinterpret_cast<void*>(pc);
}
return (_Unwind_Reason_Code)_URC_NO_REASON;
}, &state);
int count = (int)(state.current - buffer);
for (int idx = 0; idx < count; idx++) {
const void* addr = buffer[idx];
const char* symbol = "";
Dl_info info;
if (dladdr(addr, &info) && info.dli_sname)
symbol = info.dli_sname;
int status = 0;
char *demangled = __cxxabiv1::__cxa_demangle(symbol, 0, 0, &status);
sk::log_diagf("%03d: 0x%p %s", idx, addr,
(nullptr != demangled && 0 == status) ?
demangled : symbol);
sk_free(demangled);
}
}
void platform_sleep(int ms) {
usleep(ms * 1000);
}
}
#endif