/* * Copyright (c) 2012-2025 Daniele Bartolini et al. * SPDX-License-Identifier: MIT */ #include "config.h" #if CROWN_PLATFORM_ANDROID #include "core/error/error.inl" #include "core/guid.h" #include "core/memory/globals.h" #include "core/memory/memory.inl" #include "core/thread/spsc_queue.inl" #include "core/thread/thread.h" #include "device/device.h" #include "device/device_event_queue.inl" #include #include #include #include #define STB_SPRINTF_IMPLEMENTATION #define STB_SPRINTF_NOUNALIGNED #include extern "C" { #include } namespace crown { static bool push_event(const OsEvent &ev); struct AndroidDevice { SPSCQueue _events; DeviceEventQueue _queue; Thread _main_thread; DeviceOptions *_opts; ANativeWindow *_window; explicit AndroidDevice(Allocator &a) : _events(a) , _queue(push_event) , _opts(NULL) { } void run(struct android_app *app, DeviceOptions &opts) { _opts = &opts; app->userData = this; app->onAppCmd = crown::AndroidDevice::on_app_cmd; app->onInputEvent = crown::AndroidDevice::on_input_event; ANativeActivity_setWindowFlags(app->activity , AWINDOW_FLAG_FULLSCREEN | AWINDOW_FLAG_KEEP_SCREEN_ON , 0 ); while (app->destroyRequested == 0) { s32 num; android_poll_source *source; ALooper_pollOnce(-1, NULL, &num, (void **)&source); if (source != NULL) source->process(app, source); } _main_thread.stop(); } void process_command(struct android_app *app, s32 cmd) { switch (cmd) { case APP_CMD_SAVE_STATE: break; case APP_CMD_INIT_WINDOW: { CE_ASSERT(app->window != NULL, "Android window is NULL"); _window = app->window; // Push metrics here since Android does not trigger APP_CMD_WINDOW_RESIZED const s32 width = ANativeWindow_getWidth(app->window); const s32 height = ANativeWindow_getHeight(app->window); _queue.push_resolution_event(width, height); if (!_main_thread.is_running()) { _main_thread.start([](void *user_data) { return crown::main_runtime(*((DeviceOptions *)user_data)); } , _opts ); } break; } case APP_CMD_TERM_WINDOW: // The window is being hidden or closed, clean it up. break; case APP_CMD_WINDOW_RESIZED: // Not triggered by Android break; case APP_CMD_GAINED_FOCUS: break; case APP_CMD_LOST_FOCUS: break; case APP_CMD_DESTROY: _queue.push_exit_event(); break; } } s32 process_input(struct android_app *app, AInputEvent *event) { if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { const s32 action_raw = AMotionEvent_getAction(event); const s32 pointer_index = (action_raw & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; const s32 pointer_count = AMotionEvent_getPointerCount(event); const s32 pointer_id = AMotionEvent_getPointerId(event, pointer_index); const f32 x = AMotionEvent_getX(event, pointer_index); const f32 y = AMotionEvent_getY(event, pointer_index); const s32 action = (action_raw & AMOTION_EVENT_ACTION_MASK); switch (action) { case AMOTION_EVENT_ACTION_DOWN: case AMOTION_EVENT_ACTION_POINTER_DOWN: if (pointer_id < TouchButton::COUNT) _queue.push_button_event(InputDeviceType::TOUCHSCREEN, 0, pointer_id, true); if (pointer_id < TouchAxis::COUNT) _queue.push_axis_event(InputDeviceType::TOUCHSCREEN, 0, pointer_id, x, y, 0); break; case AMOTION_EVENT_ACTION_UP: case AMOTION_EVENT_ACTION_POINTER_UP: if (pointer_id < TouchAxis::COUNT) _queue.push_axis_event(InputDeviceType::TOUCHSCREEN, 0, pointer_id, x, y, 0); if (pointer_id < TouchButton::COUNT) _queue.push_button_event(InputDeviceType::TOUCHSCREEN, 0, pointer_id, false); break; case AMOTION_EVENT_ACTION_OUTSIDE: case AMOTION_EVENT_ACTION_CANCEL: if (pointer_id < TouchButton::COUNT) _queue.push_button_event(InputDeviceType::TOUCHSCREEN, 0, pointer_id, false); break; case AMOTION_EVENT_ACTION_MOVE: for (int index = 0; index < pointer_count; index++) { const s32 id = AMotionEvent_getPointerId(event, index); if (id < TouchAxis::COUNT) { const f32 xx = AMotionEvent_getX(event, index); const f32 yy = AMotionEvent_getY(event, index); _queue.push_axis_event(InputDeviceType::TOUCHSCREEN, 0, id, xx, yy, 0); } } break; } return 1; } else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) { const s32 keycode = AKeyEvent_getKeyCode(event); const s32 keyaction = AKeyEvent_getAction(event); if (keycode == AKEYCODE_BACK) { _queue.push_button_event(InputDeviceType::KEYBOARD , 0 , KeyboardButton::ESCAPE , keyaction == AKEY_EVENT_ACTION_DOWN ? true : false ); } return 1; } return 0; } static s32 on_input_event(struct android_app *app, AInputEvent *event) { return static_cast(app->userData)->process_input(app, event); } static void on_app_cmd(struct android_app *app, s32 cmd) { static_cast(app->userData)->process_command(app, cmd); } }; static AndroidDevice *s_android_device; struct WindowAndroid : public Window { WindowAndroid() { } void open(u16 x, u16 y, u16 width, u16 height, u32 parent) override { CE_UNUSED_5(x, y, width, height, parent); } void close() override { } void show() override { } void hide() override { } void resize(u16 width, u16 height) override { CE_UNUSED_2(width, height); } void move(u16 x, u16 y) override { CE_UNUSED_2(x, y); } void minimize() override { } void maximize() override { } void restore() override { } const char *title() override { return NULL; } void set_title(const char *title) override { CE_UNUSED(title); } void show_cursor(bool show) override { CE_UNUSED(show); } void set_fullscreen(bool fullscreen) override { CE_UNUSED(fullscreen); } void set_cursor(MouseCursor::Enum cursor) override { CE_UNUSED(cursor); } void set_cursor_mode(CursorMode::Enum mode) override { CE_UNUSED(mode); } void *native_handle() override { return s_android_device->_window; } void *native_display() override { return NULL; } }; namespace window { Window *create(Allocator &a) { return CE_NEW(a, WindowAndroid)(); } void destroy(Allocator &a, Window &w) { CE_DELETE(a, &w); } } // namespace window struct DisplayAndroid : public Display { void modes(Array &modes) override { CE_UNUSED(modes); } void set_mode(u32 id) override { CE_UNUSED(id); } }; namespace display { Display *create(Allocator &a) { return CE_NEW(a, DisplayAndroid)(); } void destroy(Allocator &a, Display &d) { CE_DELETE(a, &d); } } // namespace display static bool push_event(const OsEvent &ev) { return s_android_device->_events.push(ev); } bool next_event(OsEvent &ev) { return s_android_device->_events.pop(ev); } } // namespace crown void android_main(struct android_app *app) { using namespace crown; memory_globals::init(); guid_globals::init(); DeviceOptions opts(default_allocator(), 0, NULL); opts._asset_manager = app->activity->assetManager; s_android_device = CE_NEW(default_allocator(), AndroidDevice)(default_allocator()); s_android_device->run(app, opts); CE_DELETE(default_allocator(), s_android_device); guid_globals::shutdown(); memory_globals::shutdown(); } #endif // if CROWN_PLATFORM_ANDROID