blob: 69b218254961aa444feff0835793f80f3a04c244 [file]
/*
* Copyright 2024 Rive
*/
#if defined(RIVE_ANDROID) && !defined(RIVE_UNREAL)
#include "common/rive_android_app.hpp"
#include <vector>
static android_app* _app;
static bool _windowInitialized = false;
// Dimensions the window was at when we initialized it (and therefore built the
// swapchain from). Used to distinguish a genuine live resize from the redundant
// same-size APP_CMD_WINDOW_RESIZED that Android emits during startup.
static int32_t _initialWindowWidth = 0;
static int32_t _initialWindowHeight = 0;
// Parses CLI arguments from the NativeActivity's launch intent and then calls
// rive_android_main().
extern "C" JNIEXPORT void JNICALL android_main(android_app* app)
{
_app = app;
_windowInitialized = false;
_app->onAppCmd = [](android_app* _app, int32_t cmd) {
// Wait until the window is initialized to call android_player_main.
if (cmd == APP_CMD_INIT_WINDOW)
{
_windowInitialized = true;
_initialWindowWidth = ANativeWindow_getWidth(_app->window);
_initialWindowHeight = ANativeWindow_getHeight(_app->window);
}
else if (cmd == APP_CMD_WINDOW_RESIZED && _windowInitialized)
{
// We build the swapchain once from the initial window size and
// never recreate it, so a live resize isn't handled: the render
// surface would be stale (or the swapchain would go out-of-date).
// Android emits a redundant same-size resize during startup, so
// only warn when the dimensions actually changed.
int32_t width = ANativeWindow_getWidth(_app->window);
int32_t height = ANativeWindow_getHeight(_app->window);
if (width != _initialWindowWidth || height != _initialWindowHeight)
{
fprintf(stderr,
"WARNING: window resized from %dx%d to %dx%d after "
"startup; the swapchain is not recreated, so rendering "
"may be wrong-sized or fail.\n",
_initialWindowWidth,
_initialWindowHeight,
width,
height);
}
}
};
JNIEnv* env;
_app->activity->vm->AttachCurrentThread(&env, NULL);
ANativeActivity* nativeActivity = _app->activity;
jobject javaActivity = nativeActivity->clazz;
// activity.getIntent()
jclass activityClass = env->GetObjectClass(javaActivity);
jmethodID getIntentFn = env->GetMethodID(activityClass,
"getIntent",
"()Landroid/content/Intent;");
jobject intent = env->CallObjectMethod(javaActivity, getIntentFn);
// ... .getExtras()
jclass intentClass = env->FindClass("android/content/Intent");
jmethodID getExtrasFn =
env->GetMethodID(intentClass, "getExtras", "()Landroid/os/Bundle;");
jobject extras = env->CallObjectMethod(intent, getExtrasFn);
std::vector<char> rawArgs;
std::vector<const char*> argv;
// argv[0] is conventionally the program name; rive_android_main (and all
// other main-like entry points) start parsing at index 1.
// If we ever care, we could query this (I think using getPackageCodePath),
// but until then make it an obviously-fake name.
argv.push_back("fake_program_name");
if (extras != nullptr)
{
// ... .getString("args")
jclass bundleClass = env->FindClass("android/os/Bundle");
jmethodID getStringFn =
env->GetMethodID(bundleClass,
"getString",
"(Ljava/lang/String;)Ljava/lang/String;");
jstring args = static_cast<jstring>(
env->CallObjectMethod(extras,
getStringFn,
env->NewStringUTF("args")));
if (args != nullptr)
{
// Split args by space.
const char* argsUtf8 = env->GetStringUTFChars(args, 0);
rawArgs.resize(strlen(argsUtf8) + 1);
strcpy(rawArgs.data(), argsUtf8);
env->ReleaseStringUTFChars(args, argsUtf8);
const char* arg = strtok(rawArgs.data(), " ");
while (arg != nullptr)
{
argv.push_back(arg);
arg = strtok(nullptr, " ");
}
}
}
rive_android_main(static_cast<int>(argv.size()), argv.data());
ANativeActivity_finish(_app->activity);
_app->activity->vm->DetachCurrentThread();
while (!_app->destroyRequested)
{
rive_android_app_poll_once(-1);
}
}
ANativeWindow* rive_android_app_wait_for_window()
{
// Pump messages until the window is initialized.
while (!_app->destroyRequested && !_windowInitialized)
{
rive_android_app_poll_once(-1);
}
return _windowInitialized ? _app->window : nullptr;
}
bool rive_android_app_poll_once(int timeoutMillis)
{
android_poll_source* source = nullptr;
int result = ALooper_pollOnce(timeoutMillis,
nullptr,
nullptr,
reinterpret_cast<void**>(&source));
if (result == ALOOPER_POLL_ERROR)
{
fprintf(stderr, "ALooper_pollOnce returned an error");
abort();
}
if (source != nullptr)
{
source->process(_app, source);
}
return !_app->destroyRequested;
}
#endif