mirror of
https://github.com/rust-mobile/android-activity.git
synced 2026-07-10 00:37:28 +00:00
Merge pull request #35 from rib/pure-rust-native-activity
Pure-Rust native activity backend
This commit is contained in:
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## [0.4] - 2022-09-20
|
||||
### Changed
|
||||
- *Breaking*: `input_events` callback now return whether an event was handled or not to allow for fallback handling ([#31](https://github.com/rib/android-activity/issues/31))
|
||||
- The native-activity backend is now implemented in Rust only, without building on `android_native_app_glue.c` ([#35](https://github.com/rib/android-activity/pull/35))
|
||||
|
||||
## [0.3] - 2022-09-15
|
||||
### Added
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
fn build_glue_for_native_activity() {
|
||||
cc::Build::new()
|
||||
.include("native-activity-csrc")
|
||||
.include("native-activity-csrc/native-activity/native_app_glue")
|
||||
.file("native-activity-csrc/native-activity/native_app_glue/android_native_app_glue.c")
|
||||
.compile("libnative_app_glue.a");
|
||||
}
|
||||
|
||||
fn build_glue_for_game_activity() {
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
@@ -38,6 +30,4 @@ fn build_glue_for_game_activity() {
|
||||
fn main() {
|
||||
#[cfg(feature = "game-activity")]
|
||||
build_glue_for_game_activity();
|
||||
#[cfg(feature = "native-activity")]
|
||||
build_glue_for_native_activity();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ while read ARCH && read TARGET ; do
|
||||
--blocklist-item 'C?_?JNIEnv' \
|
||||
--blocklist-item '_?JavaVM' \
|
||||
--blocklist-item '_?j\w+' \
|
||||
--blocklist-item 'size_t' \
|
||||
--blocklist-item 'pthread_\w*' \
|
||||
--blocklist-function 'pthread_\w' \
|
||||
--blocklist-item 'ARect' \
|
||||
--blocklist-item 'ALooper\w*' \
|
||||
--blocklist-function 'ALooper\w*' \
|
||||
--blocklist-item 'AAsset\w*' \
|
||||
@@ -33,29 +37,6 @@ while read ARCH && read TARGET ; do
|
||||
-Igame-activity-csrc \
|
||||
--sysroot="$SYSROOT" --target=$TARGET
|
||||
|
||||
bindgen native-activity-ffi.h -o src/native_activity/ffi_$ARCH.rs \
|
||||
--blocklist-item 'JNI\w+' \
|
||||
--blocklist-item 'C?_?JNIEnv' \
|
||||
--blocklist-item '_?JavaVM' \
|
||||
--blocklist-item '_?j\w+' \
|
||||
--blocklist-item 'ALooper\w*' \
|
||||
--blocklist-function 'ALooper\w*' \
|
||||
--blocklist-item 'AAsset\w*' \
|
||||
--blocklist-item 'AAssetManager\w*' \
|
||||
--blocklist-function 'AAssetManager\w*' \
|
||||
--blocklist-item 'ANativeWindow\w*' \
|
||||
--blocklist-function 'ANativeWindow\w*' \
|
||||
--blocklist-item 'AConfiguration\w*' \
|
||||
--blocklist-function 'AConfiguration\w*' \
|
||||
--blocklist-function 'android_main' \
|
||||
--blocklist-item 'AInputQueue\w*' \
|
||||
--blocklist-function 'AInputQueue\w*' \
|
||||
--blocklist-item 'GameActivity_onCreate' \
|
||||
--blocklist-function 'GameActivity_onCreate_C' \
|
||||
--newtype-enum '\w+_(result|status)_t' \
|
||||
-- \
|
||||
-Inative-activity-csrc \
|
||||
--sysroot="$SYSROOT" --target=$TARGET
|
||||
done << EOF
|
||||
arm
|
||||
arm-linux-androideabi
|
||||
|
||||
-457
@@ -1,457 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include "android_native_app_glue.h"
|
||||
#include <android/log.h>
|
||||
|
||||
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
|
||||
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__))
|
||||
|
||||
/* For debug builds, always enable the debug traces in this library */
|
||||
#ifndef NDEBUG
|
||||
# define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", __VA_ARGS__))
|
||||
#else
|
||||
# define LOGV(...) ((void)0)
|
||||
#endif
|
||||
|
||||
static void free_saved_state(struct android_app* android_app) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
if (android_app->savedState != NULL) {
|
||||
free(android_app->savedState);
|
||||
android_app->savedState = NULL;
|
||||
android_app->savedStateSize = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
int8_t android_app_read_cmd(struct android_app* android_app) {
|
||||
int8_t cmd;
|
||||
if (read(android_app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) {
|
||||
switch (cmd) {
|
||||
case APP_CMD_SAVE_STATE:
|
||||
free_saved_state(android_app);
|
||||
break;
|
||||
}
|
||||
return cmd;
|
||||
} else {
|
||||
LOGE("No data on command pipe!");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void print_cur_config(struct android_app* android_app) {
|
||||
char lang[2], country[2];
|
||||
AConfiguration_getLanguage(android_app->config, lang);
|
||||
AConfiguration_getCountry(android_app->config, country);
|
||||
|
||||
LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
|
||||
"keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
|
||||
"modetype=%d modenight=%d",
|
||||
AConfiguration_getMcc(android_app->config),
|
||||
AConfiguration_getMnc(android_app->config),
|
||||
lang[0], lang[1], country[0], country[1],
|
||||
AConfiguration_getOrientation(android_app->config),
|
||||
AConfiguration_getTouchscreen(android_app->config),
|
||||
AConfiguration_getDensity(android_app->config),
|
||||
AConfiguration_getKeyboard(android_app->config),
|
||||
AConfiguration_getNavigation(android_app->config),
|
||||
AConfiguration_getKeysHidden(android_app->config),
|
||||
AConfiguration_getNavHidden(android_app->config),
|
||||
AConfiguration_getSdkVersion(android_app->config),
|
||||
AConfiguration_getScreenSize(android_app->config),
|
||||
AConfiguration_getScreenLong(android_app->config),
|
||||
AConfiguration_getUiModeType(android_app->config),
|
||||
AConfiguration_getUiModeNight(android_app->config));
|
||||
}
|
||||
|
||||
void android_app_attach_input_queue_looper(struct android_app* android_app) {
|
||||
if (android_app->inputQueue != NULL) {
|
||||
LOGV("Attaching input queue to looper");
|
||||
AInputQueue_attachLooper(android_app->inputQueue,
|
||||
android_app->looper, LOOPER_ID_INPUT, NULL,
|
||||
&android_app->inputPollSource);
|
||||
}
|
||||
}
|
||||
|
||||
void android_app_detach_input_queue_looper(struct android_app* android_app) {
|
||||
if (android_app->inputQueue != NULL) {
|
||||
LOGV("Detaching input queue from looper");
|
||||
AInputQueue_detachLooper(android_app->inputQueue);
|
||||
}
|
||||
}
|
||||
|
||||
void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
switch (cmd) {
|
||||
case APP_CMD_INPUT_CHANGED:
|
||||
LOGV("APP_CMD_INPUT_CHANGED\n");
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
if (android_app->inputQueue != NULL) {
|
||||
android_app_detach_input_queue_looper(android_app);
|
||||
}
|
||||
android_app->inputQueue = android_app->pendingInputQueue;
|
||||
if (android_app->inputQueue != NULL) {
|
||||
android_app_attach_input_queue_looper(android_app);
|
||||
}
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
break;
|
||||
|
||||
case APP_CMD_INIT_WINDOW:
|
||||
LOGV("APP_CMD_INIT_WINDOW\n");
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->window = android_app->pendingWindow;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
break;
|
||||
|
||||
case APP_CMD_TERM_WINDOW:
|
||||
LOGV("APP_CMD_TERM_WINDOW\n");
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
break;
|
||||
|
||||
case APP_CMD_RESUME:
|
||||
case APP_CMD_START:
|
||||
case APP_CMD_PAUSE:
|
||||
case APP_CMD_STOP:
|
||||
LOGV("activityState=%d\n", cmd);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->activityState = cmd;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
break;
|
||||
|
||||
case APP_CMD_CONFIG_CHANGED:
|
||||
LOGV("APP_CMD_CONFIG_CHANGED\n");
|
||||
AConfiguration_fromAssetManager(android_app->config,
|
||||
android_app->activity->assetManager);
|
||||
print_cur_config(android_app);
|
||||
break;
|
||||
|
||||
case APP_CMD_DESTROY:
|
||||
LOGV("APP_CMD_DESTROY\n");
|
||||
android_app->destroyRequested = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
switch (cmd) {
|
||||
case APP_CMD_TERM_WINDOW:
|
||||
LOGV("APP_CMD_TERM_WINDOW\n");
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->window = NULL;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
break;
|
||||
|
||||
case APP_CMD_SAVE_STATE:
|
||||
LOGV("APP_CMD_SAVE_STATE\n");
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->stateSaved = 1;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
break;
|
||||
|
||||
case APP_CMD_RESUME:
|
||||
free_saved_state(android_app);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_dummy() {
|
||||
|
||||
}
|
||||
|
||||
static void android_app_destroy(struct android_app* android_app) {
|
||||
LOGV("android_app_destroy!");
|
||||
free_saved_state(android_app);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
if (android_app->inputQueue != NULL) {
|
||||
AInputQueue_detachLooper(android_app->inputQueue);
|
||||
}
|
||||
AConfiguration_delete(android_app->config);
|
||||
android_app->destroyed = 1;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
// Can't touch android_app object after this.
|
||||
}
|
||||
|
||||
/*
|
||||
static void process_input(struct android_app* app, __attribute__((unused)) struct android_poll_source* source) {
|
||||
AInputEvent* event = NULL;
|
||||
while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) {
|
||||
LOGV("New input event: type=%d\n", AInputEvent_getType(event));
|
||||
if (AInputQueue_preDispatchEvent(app->inputQueue, event)) {
|
||||
continue;
|
||||
}
|
||||
int32_t handled = 0;
|
||||
if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event);
|
||||
AInputQueue_finishEvent(app->inputQueue, event, handled);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
static void process_cmd(struct android_app* app, __attribute__((unused)) struct android_poll_source* source) {
|
||||
int8_t cmd = android_app_read_cmd(app);
|
||||
android_app_pre_exec_cmd(app, cmd);
|
||||
if (app->onAppCmd != NULL) app->onAppCmd(app, cmd);
|
||||
android_app_post_exec_cmd(app, cmd);
|
||||
}
|
||||
|
||||
static void* android_app_entry(void* param) {
|
||||
struct android_app* android_app = (struct android_app*)param;
|
||||
|
||||
android_app->config = AConfiguration_new();
|
||||
AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager);
|
||||
|
||||
print_cur_config(android_app);
|
||||
|
||||
android_app->cmdPollSource.id = LOOPER_ID_MAIN;
|
||||
android_app->cmdPollSource.app = android_app;
|
||||
android_app->cmdPollSource.process = process_cmd;
|
||||
//android_app->inputPollSource.id = LOOPER_ID_INPUT;
|
||||
//android_app->inputPollSource.app = android_app;
|
||||
//android_app->inputPollSource.process = process_input;
|
||||
|
||||
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
|
||||
ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL,
|
||||
&android_app->cmdPollSource);
|
||||
android_app->looper = looper;
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->running = 1;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
_rust_glue_entry(android_app);
|
||||
|
||||
android_app_destroy(android_app);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Native activity interaction (called from main thread)
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
static struct android_app* android_app_create(ANativeActivity* activity,
|
||||
void* savedState, size_t savedStateSize) {
|
||||
struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app));
|
||||
memset(android_app, 0, sizeof(struct android_app));
|
||||
android_app->activity = activity;
|
||||
|
||||
pthread_mutex_init(&android_app->mutex, NULL);
|
||||
pthread_cond_init(&android_app->cond, NULL);
|
||||
|
||||
if (savedState != NULL) {
|
||||
android_app->savedState = malloc(savedStateSize);
|
||||
android_app->savedStateSize = savedStateSize;
|
||||
memcpy(android_app->savedState, savedState, savedStateSize);
|
||||
}
|
||||
|
||||
int msgpipe[2];
|
||||
if (pipe(msgpipe)) {
|
||||
LOGE("could not create pipe: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
android_app->msgread = msgpipe[0];
|
||||
android_app->msgwrite = msgpipe[1];
|
||||
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
pthread_create(&android_app->thread, &attr, android_app_entry, android_app);
|
||||
|
||||
// Wait for thread to start.
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
while (!android_app->running) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
return android_app;
|
||||
}
|
||||
|
||||
static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
|
||||
LOGE("Failure writing android_app cmd: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->pendingInputQueue = inputQueue;
|
||||
android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED);
|
||||
while (android_app->inputQueue != android_app->pendingInputQueue) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
if (android_app->pendingWindow != NULL) {
|
||||
android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW);
|
||||
}
|
||||
android_app->pendingWindow = window;
|
||||
if (window != NULL) {
|
||||
android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);
|
||||
}
|
||||
while (android_app->window != android_app->pendingWindow) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app_write_cmd(android_app, cmd);
|
||||
while (android_app->activityState != cmd) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_free(struct android_app* android_app) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app_write_cmd(android_app, APP_CMD_DESTROY);
|
||||
while (!android_app->destroyed) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
close(android_app->msgread);
|
||||
close(android_app->msgwrite);
|
||||
pthread_cond_destroy(&android_app->cond);
|
||||
pthread_mutex_destroy(&android_app->mutex);
|
||||
free(android_app);
|
||||
}
|
||||
|
||||
static void onDestroy(ANativeActivity* activity) {
|
||||
LOGV("Destroy: %p\n", activity);
|
||||
android_app_free((struct android_app*)activity->instance);
|
||||
}
|
||||
|
||||
static void onStart(ANativeActivity* activity) {
|
||||
LOGV("Start: %p\n", activity);
|
||||
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START);
|
||||
}
|
||||
|
||||
static void onResume(ANativeActivity* activity) {
|
||||
LOGV("Resume: %p\n", activity);
|
||||
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_RESUME);
|
||||
}
|
||||
|
||||
static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) {
|
||||
struct android_app* android_app = (struct android_app*)activity->instance;
|
||||
void* savedState = NULL;
|
||||
|
||||
LOGV("SaveInstanceState: %p\n", activity);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->stateSaved = 0;
|
||||
android_app_write_cmd(android_app, APP_CMD_SAVE_STATE);
|
||||
while (!android_app->stateSaved) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
|
||||
if (android_app->savedState != NULL) {
|
||||
savedState = android_app->savedState;
|
||||
*outLen = android_app->savedStateSize;
|
||||
android_app->savedState = NULL;
|
||||
android_app->savedStateSize = 0;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
return savedState;
|
||||
}
|
||||
|
||||
static void onPause(ANativeActivity* activity) {
|
||||
LOGV("Pause: %p\n", activity);
|
||||
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE);
|
||||
}
|
||||
|
||||
static void onStop(ANativeActivity* activity) {
|
||||
LOGV("Stop: %p\n", activity);
|
||||
android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_STOP);
|
||||
}
|
||||
|
||||
static void onConfigurationChanged(ANativeActivity* activity) {
|
||||
struct android_app* android_app = (struct android_app*)activity->instance;
|
||||
LOGV("ConfigurationChanged: %p\n", activity);
|
||||
android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED);
|
||||
}
|
||||
|
||||
static void onLowMemory(ANativeActivity* activity) {
|
||||
struct android_app* android_app = (struct android_app*)activity->instance;
|
||||
LOGV("LowMemory: %p\n", activity);
|
||||
android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY);
|
||||
}
|
||||
|
||||
static void onWindowFocusChanged(ANativeActivity* activity, int focused) {
|
||||
LOGV("WindowFocusChanged: %p -- %d\n", activity, focused);
|
||||
android_app_write_cmd((struct android_app*)activity->instance,
|
||||
focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS);
|
||||
}
|
||||
|
||||
static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) {
|
||||
LOGV("NativeWindowCreated: %p -- %p\n", activity, window);
|
||||
android_app_set_window((struct android_app*)activity->instance, window);
|
||||
}
|
||||
|
||||
static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) {
|
||||
LOGV("NativeWindowDestroyed: %p -- %p\n", activity, window);
|
||||
android_app_set_window((struct android_app*)activity->instance, NULL);
|
||||
}
|
||||
|
||||
static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) {
|
||||
LOGV("InputQueueCreated: %p -- %p\n", activity, queue);
|
||||
android_app_set_input((struct android_app*)activity->instance, queue);
|
||||
}
|
||||
|
||||
static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) {
|
||||
LOGV("InputQueueDestroyed: %p -- %p\n", activity, queue);
|
||||
android_app_set_input((struct android_app*)activity->instance, NULL);
|
||||
}
|
||||
|
||||
JNIEXPORT
|
||||
void ANativeActivity_onCreate_C(ANativeActivity* activity, void* savedState,
|
||||
size_t savedStateSize) {
|
||||
LOGV("Creating: %p\n", activity);
|
||||
activity->callbacks->onDestroy = onDestroy;
|
||||
activity->callbacks->onStart = onStart;
|
||||
activity->callbacks->onResume = onResume;
|
||||
activity->callbacks->onSaveInstanceState = onSaveInstanceState;
|
||||
activity->callbacks->onPause = onPause;
|
||||
activity->callbacks->onStop = onStop;
|
||||
activity->callbacks->onConfigurationChanged = onConfigurationChanged;
|
||||
activity->callbacks->onLowMemory = onLowMemory;
|
||||
activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
|
||||
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
|
||||
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
|
||||
activity->callbacks->onInputQueueCreated = onInputQueueCreated;
|
||||
activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
|
||||
|
||||
activity->instance = android_app_create(activity, savedState, savedStateSize);
|
||||
}
|
||||
-357
@@ -1,357 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ANDROID_NATIVE_APP_GLUE_H
|
||||
#define _ANDROID_NATIVE_APP_GLUE_H
|
||||
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
|
||||
#include <android/configuration.h>
|
||||
#include <android/looper.h>
|
||||
#include <android/native_activity.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The native activity interface provided by <android/native_activity.h>
|
||||
* is based on a set of application-provided callbacks that will be called
|
||||
* by the Activity's main thread when certain events occur.
|
||||
*
|
||||
* This means that each one of this callbacks _should_ _not_ block, or they
|
||||
* risk having the system force-close the application. This programming
|
||||
* model is direct, lightweight, but constraining.
|
||||
*
|
||||
* The 'android_native_app_glue' static library is used to provide a different
|
||||
* execution model where the application can implement its own main event
|
||||
* loop in a different thread instead. Here's how it works:
|
||||
*
|
||||
* 1/ The application must provide a function named "android_main()" that
|
||||
* will be called when the activity is created, in a new thread that is
|
||||
* distinct from the activity's main thread.
|
||||
*
|
||||
* 2/ android_main() receives a pointer to a valid "android_app" structure
|
||||
* that contains references to other important objects, e.g. the
|
||||
* ANativeActivity obejct instance the application is running in.
|
||||
*
|
||||
* 3/ the "android_app" object holds an ALooper instance that already
|
||||
* listens to two important things:
|
||||
*
|
||||
* - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX
|
||||
* declarations below.
|
||||
*
|
||||
* - input events coming from the AInputQueue attached to the activity.
|
||||
*
|
||||
* Each of these correspond to an ALooper identifier returned by
|
||||
* ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT,
|
||||
* respectively.
|
||||
*
|
||||
* Your application can use the same ALooper to listen to additional
|
||||
* file-descriptors. They can either be callback based, or with return
|
||||
* identifiers starting with LOOPER_ID_USER.
|
||||
*
|
||||
* 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event,
|
||||
* the returned data will point to an android_poll_source structure. You
|
||||
* can call the process() function on it, and fill in android_app->onAppCmd
|
||||
* and android_app->onInputEvent to be called for your own processing
|
||||
* of the event.
|
||||
*
|
||||
* Alternatively, you can call the low-level functions to read and process
|
||||
* the data directly... look at the process_cmd() and process_input()
|
||||
* implementations in the glue to see how to do this.
|
||||
*
|
||||
* See the sample named "native-activity" that comes with the NDK with a
|
||||
* full usage example. Also look at the JavaDoc of NativeActivity.
|
||||
*/
|
||||
|
||||
struct android_app;
|
||||
|
||||
/**
|
||||
* Data associated with an ALooper fd that will be returned as the "outData"
|
||||
* when that source has data ready.
|
||||
*/
|
||||
struct android_poll_source {
|
||||
// The identifier of this source. May be LOOPER_ID_MAIN or
|
||||
// LOOPER_ID_INPUT.
|
||||
int32_t id;
|
||||
|
||||
// The android_app this ident is associated with.
|
||||
struct android_app* app;
|
||||
|
||||
// Function to call to perform the standard processing of data from
|
||||
// this source.
|
||||
void (*process)(struct android_app* app, struct android_poll_source* source);
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the interface for the standard glue code of a threaded
|
||||
* application. In this model, the application's code is running
|
||||
* in its own thread separate from the main thread of the process.
|
||||
* It is not required that this thread be associated with the Java
|
||||
* VM, although it will need to be in order to make JNI calls any
|
||||
* Java objects.
|
||||
*/
|
||||
struct android_app {
|
||||
// The application can place a pointer to its own state object
|
||||
// here if it likes.
|
||||
void* userData;
|
||||
|
||||
// Fill this in with the function to process main app commands (APP_CMD_*)
|
||||
void (*onAppCmd)(struct android_app* app, int32_t cmd);
|
||||
|
||||
// Fill this in with the function to process input events. At this point
|
||||
// the event has already been pre-dispatched, and it will be finished upon
|
||||
// return. Return 1 if you have handled the event, 0 for any default
|
||||
// dispatching.
|
||||
int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event);
|
||||
|
||||
// The ANativeActivity object instance that this app is running in.
|
||||
ANativeActivity* activity;
|
||||
|
||||
// The current configuration the app is running in.
|
||||
AConfiguration* config;
|
||||
|
||||
// This is the last instance's saved state, as provided at creation time.
|
||||
// It is NULL if there was no state. You can use this as you need; the
|
||||
// memory will remain around until you call android_app_exec_cmd() for
|
||||
// APP_CMD_RESUME, at which point it will be freed and savedState set to NULL.
|
||||
// These variables should only be changed when processing a APP_CMD_SAVE_STATE,
|
||||
// at which point they will be initialized to NULL and you can malloc your
|
||||
// state and place the information here. In that case the memory will be
|
||||
// freed for you later.
|
||||
void* savedState;
|
||||
size_t savedStateSize;
|
||||
|
||||
// The ALooper associated with the app's thread.
|
||||
ALooper* looper;
|
||||
|
||||
// When non-NULL, this is the input queue from which the app will
|
||||
// receive user input events.
|
||||
AInputQueue* inputQueue;
|
||||
|
||||
// When non-NULL, this is the window surface that the app can draw in.
|
||||
ANativeWindow* window;
|
||||
|
||||
// Current content rectangle of the window; this is the area where the
|
||||
// window's content should be placed to be seen by the user.
|
||||
ARect contentRect;
|
||||
|
||||
// Current state of the app's activity. May be either APP_CMD_START,
|
||||
// APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below.
|
||||
int activityState;
|
||||
|
||||
// This is non-zero when the application's NativeActivity is being
|
||||
// destroyed and waiting for the app thread to complete.
|
||||
int destroyRequested;
|
||||
|
||||
// -------------------------------------------------
|
||||
// Below are "private" implementation of the glue code.
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
|
||||
int msgread;
|
||||
int msgwrite;
|
||||
|
||||
pthread_t thread;
|
||||
|
||||
struct android_poll_source cmdPollSource;
|
||||
struct android_poll_source inputPollSource;
|
||||
|
||||
int running;
|
||||
int stateSaved;
|
||||
int destroyed;
|
||||
int redrawNeeded;
|
||||
AInputQueue* pendingInputQueue;
|
||||
ANativeWindow* pendingWindow;
|
||||
ARect pendingContentRect;
|
||||
};
|
||||
|
||||
enum {
|
||||
/**
|
||||
* Looper data ID of commands coming from the app's main thread, which
|
||||
* is returned as an identifier from ALooper_pollOnce(). The data for this
|
||||
* identifier is a pointer to an android_poll_source structure.
|
||||
* These can be retrieved and processed with android_app_read_cmd()
|
||||
* and android_app_exec_cmd().
|
||||
*/
|
||||
LOOPER_ID_MAIN = 1,
|
||||
|
||||
/**
|
||||
* Looper data ID of events coming from the AInputQueue of the
|
||||
* application's window, which is returned as an identifier from
|
||||
* ALooper_pollOnce(). The data for this identifier is a pointer to an
|
||||
* android_poll_source structure. These can be read via the inputQueue
|
||||
* object of android_app.
|
||||
*/
|
||||
LOOPER_ID_INPUT = 2,
|
||||
|
||||
/**
|
||||
* Start of user-defined ALooper identifiers.
|
||||
*/
|
||||
LOOPER_ID_USER = 3,
|
||||
};
|
||||
|
||||
enum {
|
||||
/**
|
||||
* Command from main thread: the AInputQueue has changed. Upon processing
|
||||
* this command, android_app->inputQueue will be updated to the new queue
|
||||
* (or NULL).
|
||||
*/
|
||||
APP_CMD_INPUT_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: a new ANativeWindow is ready for use. Upon
|
||||
* receiving this command, android_app->window will contain the new window
|
||||
* surface.
|
||||
*/
|
||||
APP_CMD_INIT_WINDOW,
|
||||
|
||||
/**
|
||||
* Command from main thread: the existing ANativeWindow needs to be
|
||||
* terminated. Upon receiving this command, android_app->window still
|
||||
* contains the existing window; after calling android_app_exec_cmd
|
||||
* it will be set to NULL.
|
||||
*/
|
||||
APP_CMD_TERM_WINDOW,
|
||||
|
||||
/**
|
||||
* Command from main thread: the current ANativeWindow has been resized.
|
||||
* Please redraw with its new size.
|
||||
*/
|
||||
APP_CMD_WINDOW_RESIZED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the system needs that the current ANativeWindow
|
||||
* be redrawn. You should redraw the window before handing this to
|
||||
* android_app_exec_cmd() in order to avoid transient drawing glitches.
|
||||
*/
|
||||
APP_CMD_WINDOW_REDRAW_NEEDED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the content area of the window has changed,
|
||||
* such as from the soft input window being shown or hidden. You can
|
||||
* find the new content rect in android_app::contentRect.
|
||||
*/
|
||||
APP_CMD_CONTENT_RECT_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity window has gained
|
||||
* input focus.
|
||||
*/
|
||||
APP_CMD_GAINED_FOCUS,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity window has lost
|
||||
* input focus.
|
||||
*/
|
||||
APP_CMD_LOST_FOCUS,
|
||||
|
||||
/**
|
||||
* Command from main thread: the current device configuration has changed.
|
||||
*/
|
||||
APP_CMD_CONFIG_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the system is running low on memory.
|
||||
* Try to reduce your memory use.
|
||||
*/
|
||||
APP_CMD_LOW_MEMORY,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been started.
|
||||
*/
|
||||
APP_CMD_START,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been resumed.
|
||||
*/
|
||||
APP_CMD_RESUME,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app should generate a new saved state
|
||||
* for itself, to restore from later if needed. If you have saved state,
|
||||
* allocate it with malloc and place it in android_app.savedState with
|
||||
* the size in android_app.savedStateSize. The will be freed for you
|
||||
* later.
|
||||
*/
|
||||
APP_CMD_SAVE_STATE,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been paused.
|
||||
*/
|
||||
APP_CMD_PAUSE,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been stopped.
|
||||
*/
|
||||
APP_CMD_STOP,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity is being destroyed,
|
||||
* and waiting for the app thread to clean up and exit before proceeding.
|
||||
*/
|
||||
APP_CMD_DESTROY,
|
||||
};
|
||||
|
||||
/**
|
||||
* Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next
|
||||
* app command message.
|
||||
*/
|
||||
int8_t android_app_read_cmd(struct android_app* android_app);
|
||||
|
||||
/**
|
||||
* Call with the command returned by android_app_read_cmd() to do the
|
||||
* initial pre-processing of the given command. You can perform your own
|
||||
* actions for the command after calling this function.
|
||||
*/
|
||||
void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd);
|
||||
|
||||
/**
|
||||
* Call with the command returned by android_app_read_cmd() to do the
|
||||
* final post-processing of the given command. You must have done your own
|
||||
* actions for the command before calling this function.
|
||||
*/
|
||||
void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd);
|
||||
|
||||
void android_app_attach_input_queue_looper(struct android_app* android_app);
|
||||
void android_app_detach_input_queue_looper(struct android_app* android_app);
|
||||
|
||||
/**
|
||||
* Dummy function that used to be used to prevent the linker from stripping app
|
||||
* glue code. No longer necessary, since __attribute__((visibility("default")))
|
||||
* does this for us.
|
||||
*/
|
||||
__attribute__((
|
||||
deprecated("Calls to app_dummy are no longer necessary. See "
|
||||
"https://github.com/android-ndk/ndk/issues/381."))) void
|
||||
app_dummy();
|
||||
|
||||
/**
|
||||
* This is the function that application code must implement, representing
|
||||
* the main entry to the app.
|
||||
*/
|
||||
extern void _rust_glue_entry(struct android_app* app);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _ANDROID_NATIVE_APP_GLUE_H */
|
||||
@@ -13,9 +13,8 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use jni_sys::*;
|
||||
use ndk_sys::AAssetManager;
|
||||
use ndk_sys::ANativeWindow;
|
||||
use ndk_sys::{AConfiguration, ALooper, ALooper_callbackFunc};
|
||||
use libc::{pthread_cond_t, pthread_mutex_t, pthread_t, size_t};
|
||||
use ndk_sys::{AAssetManager, AConfiguration, ALooper, ALooper_callbackFunc, ANativeWindow, ARect};
|
||||
|
||||
#[cfg(all(
|
||||
any(target_os = "android", feature = "test"),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,7 @@ impl<'a> StateSaver<'a> {
|
||||
}
|
||||
|
||||
(*app_ptr).savedState = buf;
|
||||
(*app_ptr).savedStateSize = state.len() as ffi::size_t;
|
||||
(*app_ptr).savedStateSize = state.len() as _;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -549,7 +549,7 @@ extern "C" {
|
||||
pub fn GameActivity_onCreate_C(
|
||||
activity: *mut ffi::GameActivity,
|
||||
savedState: *mut ::std::os::raw::c_void,
|
||||
savedStateSize: ffi::size_t,
|
||||
savedStateSize: libc::size_t,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -582,7 +582,7 @@ pub unsafe extern "C" fn Java_com_google_androidgamesdk_GameActivity_loadNativeC
|
||||
pub unsafe extern "C" fn GameActivity_onCreate(
|
||||
activity: *mut ffi::GameActivity,
|
||||
saved_state: *mut ::std::os::raw::c_void,
|
||||
saved_state_size: ffi::size_t,
|
||||
saved_state_size: libc::size_t,
|
||||
) {
|
||||
GameActivity_onCreate_C(activity, saved_state, saved_state_size);
|
||||
}
|
||||
|
||||
@@ -69,6 +69,40 @@ pub struct Rect {
|
||||
pub bottom: i32,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// An empty `Rect` with all components set to zero.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ndk_sys::ARect> for Rect {
|
||||
fn into(self) -> ndk_sys::ARect {
|
||||
ndk_sys::ARect {
|
||||
left: self.left,
|
||||
right: self.right,
|
||||
top: self.top,
|
||||
bottom: self.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ndk_sys::ARect> for Rect {
|
||||
fn from(arect: ndk_sys::ARect) -> Self {
|
||||
Self {
|
||||
left: arect.left,
|
||||
right: arect.right,
|
||||
top: arect.top,
|
||||
bottom: arect.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type StateSaver<'a> = activity_impl::StateSaver<'a>;
|
||||
pub type StateLoader<'a> = activity_impl::StateLoader<'a>;
|
||||
|
||||
@@ -365,12 +399,6 @@ impl Hash for AndroidApp {
|
||||
}
|
||||
|
||||
impl AndroidApp {
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-activity")))]
|
||||
#[cfg(feature = "native-activity")]
|
||||
pub(crate) fn native_activity(&self) -> *const ndk_sys::ANativeActivity {
|
||||
self.inner.read().unwrap().native_activity()
|
||||
}
|
||||
|
||||
/// Queries the current [`NativeWindow`] for the application.
|
||||
///
|
||||
/// This will only return `Some(window)` between
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
//! The bindings are pre-generated and the right one for the platform is selected at compile time.
|
||||
|
||||
// Bindgen lints
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(improper_ctypes)]
|
||||
#![allow(clippy::all)]
|
||||
// Temporarily allow UB nullptr dereference in bindgen layout tests until fixed upstream:
|
||||
// https://github.com/rust-lang/rust-bindgen/pull/2055
|
||||
// https://github.com/rust-lang/rust-bindgen/pull/2064
|
||||
#![allow(deref_nullptr)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use jni_sys::*;
|
||||
use ndk_sys::AAssetManager;
|
||||
use ndk_sys::ANativeWindow;
|
||||
use ndk_sys::{AConfiguration, AInputQueue, ALooper};
|
||||
|
||||
#[cfg(all(
|
||||
any(target_os = "android", feature = "test"),
|
||||
any(target_arch = "arm", target_arch = "armv7")
|
||||
))]
|
||||
include!("ffi_arm.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "aarch64"))]
|
||||
include!("ffi_aarch64.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86"))]
|
||||
include!("ffi_i686.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86_64"))]
|
||||
include!("ffi_x86_64.rs");
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,835 @@
|
||||
//! This 'glue' layer acts as an IPC shim between the JVM main thread and the Rust
|
||||
//! main thread. Notifying Rust of lifecycle events from the JVM and handling
|
||||
//! synchronization between the two threads.
|
||||
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
ops::Deref,
|
||||
os::unix::prelude::{FromRawFd, RawFd},
|
||||
ptr::{self, NonNull},
|
||||
sync::{Arc, Condvar, Mutex, Weak},
|
||||
};
|
||||
|
||||
use libc;
|
||||
|
||||
use log::Level;
|
||||
use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow};
|
||||
use ndk_sys::ANativeActivity;
|
||||
|
||||
use crate::ConfigurationRef;
|
||||
|
||||
use super::{AndroidApp, Rect};
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub enum AppCmd {
|
||||
InputQueueChanged = 0,
|
||||
InitWindow = 1,
|
||||
TermWindow = 2,
|
||||
WindowResized = 3,
|
||||
WindowRedrawNeeded = 4,
|
||||
ContentRectChanged = 5,
|
||||
GainedFocus = 6,
|
||||
LostFocus = 7,
|
||||
ConfigChanged = 8,
|
||||
LowMemory = 9,
|
||||
Start = 10,
|
||||
Resume = 11,
|
||||
SaveState = 12,
|
||||
Pause = 13,
|
||||
Stop = 14,
|
||||
Destroy = 15,
|
||||
}
|
||||
impl TryFrom<i8> for AppCmd {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: i8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(AppCmd::InputQueueChanged),
|
||||
1 => Ok(AppCmd::InitWindow),
|
||||
2 => Ok(AppCmd::TermWindow),
|
||||
3 => Ok(AppCmd::WindowResized),
|
||||
4 => Ok(AppCmd::WindowRedrawNeeded),
|
||||
5 => Ok(AppCmd::ContentRectChanged),
|
||||
6 => Ok(AppCmd::GainedFocus),
|
||||
7 => Ok(AppCmd::LostFocus),
|
||||
8 => Ok(AppCmd::ConfigChanged),
|
||||
9 => Ok(AppCmd::LowMemory),
|
||||
10 => Ok(AppCmd::Start),
|
||||
11 => Ok(AppCmd::Resume),
|
||||
12 => Ok(AppCmd::SaveState),
|
||||
13 => Ok(AppCmd::Pause),
|
||||
14 => Ok(AppCmd::Stop),
|
||||
15 => Ok(AppCmd::Destroy),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub enum State {
|
||||
Init,
|
||||
Start,
|
||||
Resume,
|
||||
Pause,
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WaitableNativeActivityState {
|
||||
pub activity: *mut ndk_sys::ANativeActivity,
|
||||
|
||||
pub mutex: Mutex<NativeActivityState>,
|
||||
pub cond: Condvar,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NativeActivityGlue {
|
||||
pub inner: Arc<WaitableNativeActivityState>,
|
||||
}
|
||||
unsafe impl Send for NativeActivityGlue {}
|
||||
unsafe impl Sync for NativeActivityGlue {}
|
||||
|
||||
impl Deref for NativeActivityGlue {
|
||||
type Target = WaitableNativeActivityState;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeActivityGlue {
|
||||
pub fn new(
|
||||
activity: *mut ANativeActivity,
|
||||
saved_state: *const libc::c_void,
|
||||
saved_state_size: libc::size_t,
|
||||
) -> Self {
|
||||
let glue = Self {
|
||||
inner: Arc::new(WaitableNativeActivityState::new(
|
||||
activity,
|
||||
saved_state,
|
||||
saved_state_size,
|
||||
)),
|
||||
};
|
||||
|
||||
let weak_ref = Arc::downgrade(&glue.inner);
|
||||
let weak_ptr = Weak::into_raw(weak_ref);
|
||||
unsafe {
|
||||
(*activity).instance = weak_ptr as *mut _;
|
||||
|
||||
(*(*activity).callbacks).onDestroy = Some(on_destroy);
|
||||
(*(*activity).callbacks).onStart = Some(on_start);
|
||||
(*(*activity).callbacks).onResume = Some(on_resume);
|
||||
(*(*activity).callbacks).onSaveInstanceState = Some(on_save_instance_state);
|
||||
(*(*activity).callbacks).onPause = Some(on_pause);
|
||||
(*(*activity).callbacks).onStop = Some(on_stop);
|
||||
(*(*activity).callbacks).onConfigurationChanged = Some(on_configuration_changed);
|
||||
(*(*activity).callbacks).onLowMemory = Some(on_low_memory);
|
||||
(*(*activity).callbacks).onWindowFocusChanged = Some(on_window_focus_changed);
|
||||
(*(*activity).callbacks).onNativeWindowCreated = Some(on_native_window_created);
|
||||
(*(*activity).callbacks).onNativeWindowDestroyed = Some(on_native_window_destroyed);
|
||||
(*(*activity).callbacks).onInputQueueCreated = Some(on_input_queue_created);
|
||||
(*(*activity).callbacks).onInputQueueDestroyed = Some(on_input_queue_destroyed);
|
||||
}
|
||||
|
||||
glue
|
||||
}
|
||||
|
||||
/// Returns the file descriptor that needs to be polled by the Rust main thread
|
||||
/// for events/commands from the JVM thread
|
||||
pub fn cmd_read_fd(&self) -> libc::c_int {
|
||||
self.mutex.lock().unwrap().msg_read
|
||||
}
|
||||
|
||||
/// For the Rust main thread to read a single pending command sent from the JVM main thread
|
||||
pub fn read_cmd(&self) -> Option<AppCmd> {
|
||||
self.inner.mutex.lock().unwrap().read_cmd()
|
||||
}
|
||||
|
||||
/// For the Rust main thread to get an ndk::InputQueue that wraps the AInputQueue pointer
|
||||
/// we have and at the same time ensure that the input queue is attached to the given looper.
|
||||
///
|
||||
/// NB: it's expected that the input queue is detached as soon as we know there is new
|
||||
/// input (knowing the app will be notified) and only re-attached when the application
|
||||
/// reads the input (to avoid lots of redundant wake ups)
|
||||
pub fn looper_attached_input_queue(
|
||||
&self,
|
||||
looper: *mut ndk_sys::ALooper,
|
||||
ident: libc::c_int,
|
||||
) -> Option<InputQueue> {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
if guard.input_queue == ptr::null_mut() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// Reattach the input queue to the looper so future input will again deliver an
|
||||
// `InputAvailable` event.
|
||||
guard.attach_input_queue_to_looper(looper, ident);
|
||||
Some(InputQueue::from_ptr(NonNull::new_unchecked(
|
||||
guard.input_queue,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detach_input_queue_from_looper(&self) {
|
||||
unsafe {
|
||||
self.inner
|
||||
.mutex
|
||||
.lock()
|
||||
.unwrap()
|
||||
.detach_input_queue_from_looper();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(&self) -> ConfigurationRef {
|
||||
self.mutex.lock().unwrap().config.clone()
|
||||
}
|
||||
|
||||
pub fn content_rect(&self) -> Rect {
|
||||
self.mutex.lock().unwrap().content_rect.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NativeActivityState {
|
||||
pub msg_read: libc::c_int,
|
||||
pub msg_write: libc::c_int,
|
||||
pub config: super::ConfigurationRef,
|
||||
pub saved_state: Vec<u8>,
|
||||
pub input_queue: *mut ndk_sys::AInputQueue,
|
||||
pub window: Option<NativeWindow>,
|
||||
pub content_rect: ndk_sys::ARect,
|
||||
pub activity_state: State,
|
||||
pub destroy_requested: bool,
|
||||
pub running: bool,
|
||||
pub app_has_saved_state: bool,
|
||||
pub destroyed: bool,
|
||||
pub redraw_needed: bool,
|
||||
pub pending_input_queue: *mut ndk_sys::AInputQueue,
|
||||
pub pending_window: Option<NativeWindow>,
|
||||
pub pending_content_rect: ndk_sys::ARect,
|
||||
}
|
||||
|
||||
impl NativeActivityState {
|
||||
pub fn read_cmd(&mut self) -> Option<AppCmd> {
|
||||
let mut cmd_i: i8 = 0;
|
||||
loop {
|
||||
match unsafe { libc::read(self.msg_read, &mut cmd_i as *mut _ as *mut _, 1) } {
|
||||
1 => {
|
||||
let cmd = AppCmd::try_from(cmd_i);
|
||||
return match cmd {
|
||||
Ok(cmd) => Some(cmd),
|
||||
Err(_) => {
|
||||
log::error!("Spurious, unknown NativeActivityGlue cmd: {}", cmd_i);
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
-1 => {
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.kind() != std::io::ErrorKind::Interrupted {
|
||||
log::error!("Failure reading NativeActivityGlue cmd: {}", err);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
count => {
|
||||
log::error!(
|
||||
"Spurious read of {count} bytes while reading NativeActivityGlue cmd"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_cmd(&mut self, cmd: AppCmd) {
|
||||
let cmd = cmd as i8;
|
||||
loop {
|
||||
match unsafe { libc::write(self.msg_write, &cmd as *const _ as *const _, 1) } {
|
||||
1 => break,
|
||||
-1 => {
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.kind() != std::io::ErrorKind::Interrupted {
|
||||
log::error!("Failure writing NativeActivityGlue cmd: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
count => {
|
||||
log::error!(
|
||||
"Spurious write of {count} bytes while writing NativeActivityGlue cmd"
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn attach_input_queue_to_looper(
|
||||
&mut self,
|
||||
looper: *mut ndk_sys::ALooper,
|
||||
ident: libc::c_int,
|
||||
) {
|
||||
if self.input_queue != ptr::null_mut() {
|
||||
log::trace!("Attaching input queue to looper");
|
||||
ndk_sys::AInputQueue_attachLooper(
|
||||
self.input_queue,
|
||||
looper,
|
||||
ident,
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn detach_input_queue_from_looper(&mut self) {
|
||||
if self.input_queue != ptr::null_mut() {
|
||||
log::trace!("Detaching input queue from looper");
|
||||
ndk_sys::AInputQueue_detachLooper(self.input_queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WaitableNativeActivityState {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("WaitableNativeActivityState::drop!");
|
||||
unsafe {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.detach_input_queue_from_looper();
|
||||
guard.destroyed = true;
|
||||
self.cond.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WaitableNativeActivityState {
|
||||
///////////////////////////////
|
||||
// Java-side callback handling
|
||||
///////////////////////////////
|
||||
|
||||
pub fn new(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
saved_state_in: *const libc::c_void,
|
||||
saved_state_size: libc::size_t,
|
||||
) -> Self {
|
||||
let mut msgpipe: [libc::c_int; 2] = [-1, -1];
|
||||
unsafe {
|
||||
if libc::pipe(msgpipe.as_mut_ptr()) != 0 {
|
||||
panic!(
|
||||
"could not create Rust <-> Java IPC pipe: {}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let saved_state = unsafe {
|
||||
std::slice::from_raw_parts(saved_state_in as *const u8, saved_state_size as _)
|
||||
};
|
||||
|
||||
let config = unsafe {
|
||||
let config = ndk_sys::AConfiguration_new();
|
||||
ndk_sys::AConfiguration_fromAssetManager(config, (*activity).assetManager);
|
||||
|
||||
let config = super::ConfigurationRef::new(Configuration::from_ptr(
|
||||
NonNull::new_unchecked(config),
|
||||
));
|
||||
eprintln!("Config: {:#?}", config);
|
||||
config
|
||||
};
|
||||
|
||||
Self {
|
||||
activity,
|
||||
mutex: Mutex::new(NativeActivityState {
|
||||
msg_read: msgpipe[0],
|
||||
msg_write: msgpipe[1],
|
||||
config,
|
||||
saved_state: saved_state.into(),
|
||||
input_queue: ptr::null_mut(),
|
||||
window: None,
|
||||
content_rect: Rect::empty().into(),
|
||||
activity_state: State::Init,
|
||||
destroy_requested: false,
|
||||
running: false,
|
||||
app_has_saved_state: false,
|
||||
destroyed: false,
|
||||
redraw_needed: false,
|
||||
pending_input_queue: ptr::null_mut(),
|
||||
pending_window: None,
|
||||
pending_content_rect: Rect::empty().into(),
|
||||
}),
|
||||
cond: Condvar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_destroyed(&self) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
unsafe {
|
||||
guard.write_cmd(AppCmd::Destroy);
|
||||
while !guard.destroyed {
|
||||
guard = self.cond.wait(guard).unwrap();
|
||||
}
|
||||
|
||||
libc::close(guard.msg_read);
|
||||
guard.msg_read = -1;
|
||||
libc::close(guard.msg_write);
|
||||
guard.msg_write = -1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_config_changed(&self) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.write_cmd(AppCmd::ConfigChanged);
|
||||
}
|
||||
|
||||
pub fn notify_low_memory(&self) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.write_cmd(AppCmd::LowMemory);
|
||||
}
|
||||
|
||||
pub fn notify_focus_changed(&self, focused: bool) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.write_cmd(if focused {
|
||||
AppCmd::GainedFocus
|
||||
} else {
|
||||
AppCmd::LostFocus
|
||||
});
|
||||
}
|
||||
|
||||
unsafe fn set_input(&self, input_queue: *mut ndk_sys::AInputQueue) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
// The pending_input_queue state should only be set while in this method, and since
|
||||
// it doesn't allow re-entrance and is cleared before returning then we expect
|
||||
// this to be null
|
||||
debug_assert!(
|
||||
guard.pending_input_queue.is_null(),
|
||||
"InputQueue update clash"
|
||||
);
|
||||
|
||||
guard.pending_input_queue = input_queue;
|
||||
guard.write_cmd(AppCmd::InputQueueChanged);
|
||||
while guard.input_queue != guard.pending_input_queue {
|
||||
guard = self.cond.wait(guard).unwrap();
|
||||
}
|
||||
guard.pending_input_queue = ptr::null_mut();
|
||||
}
|
||||
|
||||
unsafe fn set_window(&self, window: Option<NativeWindow>) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
// The pending_window state should only be set while in this method, and since
|
||||
// it doesn't allow re-entrance and is cleared before returning then we expect
|
||||
// this to be None
|
||||
debug_assert!(guard.pending_window.is_none(), "NativeWindow update clash");
|
||||
|
||||
if guard.window.is_some() {
|
||||
guard.write_cmd(AppCmd::TermWindow);
|
||||
}
|
||||
guard.pending_window = window;
|
||||
if guard.pending_window.is_some() {
|
||||
guard.write_cmd(AppCmd::InitWindow);
|
||||
}
|
||||
while guard.window != guard.pending_window {
|
||||
guard = self.cond.wait(guard).unwrap();
|
||||
}
|
||||
guard.pending_window = None;
|
||||
}
|
||||
|
||||
unsafe fn set_activity_state(&self, state: State) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
let cmd = match state {
|
||||
State::Init => panic!("Can't explicitly transition into 'init' state"),
|
||||
State::Start => AppCmd::Start,
|
||||
State::Resume => AppCmd::Resume,
|
||||
State::Pause => AppCmd::Pause,
|
||||
State::Stop => AppCmd::Stop,
|
||||
};
|
||||
guard.write_cmd(cmd);
|
||||
|
||||
while guard.activity_state != state {
|
||||
guard = self.cond.wait(guard).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn request_save_state(&self) -> (*mut libc::c_void, libc::size_t) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
// The state_saved flag should only be set while in this method, and since
|
||||
// it doesn't allow re-entrance and is cleared before returning then we expect
|
||||
// this to be None
|
||||
debug_assert!(
|
||||
guard.app_has_saved_state == false,
|
||||
"SaveState request clash"
|
||||
);
|
||||
guard.write_cmd(AppCmd::SaveState);
|
||||
while guard.app_has_saved_state == false {
|
||||
guard = self.cond.wait(guard).unwrap();
|
||||
}
|
||||
guard.app_has_saved_state = false;
|
||||
|
||||
// `ANativeActivity` explicitly documents that it expects save state to be
|
||||
// given via a `malloc()` allocated pointer since it will automatically
|
||||
// `free()` the state after it has been converted to a buffer for the JVM.
|
||||
if guard.saved_state.len() > 0 {
|
||||
let saved_state_size = guard.saved_state.len() as _;
|
||||
let saved_state_src_ptr = guard.saved_state.as_ptr();
|
||||
unsafe {
|
||||
let saved_state = libc::malloc(saved_state_size);
|
||||
assert!(
|
||||
saved_state != ptr::null_mut(),
|
||||
"Failed to allocate {} bytes for restoring saved application state",
|
||||
saved_state_size
|
||||
);
|
||||
libc::memcpy(saved_state, saved_state_src_ptr as _, saved_state_size);
|
||||
(saved_state, saved_state_size)
|
||||
}
|
||||
} else {
|
||||
(ptr::null_mut(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn saved_state(&self) -> Option<Vec<u8>> {
|
||||
let guard = self.mutex.lock().unwrap();
|
||||
if guard.saved_state.len() > 0 {
|
||||
Some(guard.saved_state.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_saved_state(&self, state: &[u8]) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
guard.saved_state.clear();
|
||||
guard.saved_state.extend_from_slice(state);
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// Rust-side event loop
|
||||
////////////////////////////
|
||||
|
||||
pub fn notify_main_thread_running(&self) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.running = true;
|
||||
self.cond.notify_one();
|
||||
}
|
||||
|
||||
pub unsafe fn pre_exec_cmd(
|
||||
&self,
|
||||
cmd: AppCmd,
|
||||
looper: *mut ndk_sys::ALooper,
|
||||
input_queue_ident: libc::c_int,
|
||||
) {
|
||||
log::trace!("Pre: AppCmd::{:#?}", cmd);
|
||||
match cmd {
|
||||
AppCmd::InputQueueChanged => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.detach_input_queue_from_looper();
|
||||
guard.input_queue = guard.pending_input_queue;
|
||||
if guard.input_queue != ptr::null_mut() {
|
||||
guard.attach_input_queue_to_looper(looper, input_queue_ident);
|
||||
}
|
||||
self.cond.notify_one();
|
||||
}
|
||||
AppCmd::InitWindow => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.window = guard.pending_window.clone();
|
||||
self.cond.notify_one();
|
||||
}
|
||||
AppCmd::Resume | AppCmd::Start | AppCmd::Pause | AppCmd::Stop => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.activity_state = match cmd {
|
||||
AppCmd::Start => State::Start,
|
||||
AppCmd::Pause => State::Pause,
|
||||
AppCmd::Resume => State::Resume,
|
||||
AppCmd::Stop => State::Stop,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.cond.notify_one();
|
||||
}
|
||||
AppCmd::ConfigChanged => {
|
||||
let guard = self.mutex.lock().unwrap();
|
||||
let config = ndk_sys::AConfiguration_new();
|
||||
ndk_sys::AConfiguration_fromAssetManager(config, (*self.activity).assetManager);
|
||||
let config = Configuration::from_ptr(NonNull::new_unchecked(config));
|
||||
guard.config.replace(config);
|
||||
log::debug!("Config: {:#?}", guard.config);
|
||||
}
|
||||
AppCmd::Destroy => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.destroy_requested = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn post_exec_cmd(&self, cmd: AppCmd) {
|
||||
log::trace!("Post: AppCmd::{:#?}", cmd);
|
||||
match cmd {
|
||||
AppCmd::TermWindow => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.window = None;
|
||||
self.cond.notify_one();
|
||||
}
|
||||
AppCmd::SaveState => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.app_has_saved_state = true;
|
||||
self.cond.notify_one();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
pub fn android_main(app: AndroidApp);
|
||||
}
|
||||
|
||||
fn android_log(level: Level, tag: &CStr, msg: &CStr) {
|
||||
let prio = match level {
|
||||
Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR,
|
||||
Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN,
|
||||
Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO,
|
||||
Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG,
|
||||
Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE,
|
||||
};
|
||||
unsafe {
|
||||
ndk_sys::__android_log_write(prio.0 as libc::c_int, tag.as_ptr(), msg.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn try_with_waitable_activity_ref(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
closure: impl FnOnce(Arc<WaitableNativeActivityState>),
|
||||
) {
|
||||
assert!(!(*activity).instance.is_null());
|
||||
let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast();
|
||||
let weak_ref = Weak::from_raw(weak_ptr);
|
||||
if let Some(waitable_activity) = weak_ref.upgrade() {
|
||||
closure(waitable_activity);
|
||||
} else {
|
||||
log::error!("Ignoring spurious JVM callback after last activity reference was dropped!")
|
||||
}
|
||||
let _ = weak_ref.into_raw();
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_destroy(activity: *mut ndk_sys::ANativeActivity) {
|
||||
log::debug!("Destroy: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_destroyed()
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_start(activity: *mut ndk_sys::ANativeActivity) {
|
||||
log::debug!("Start: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_activity_state(State::Start);
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_resume(activity: *mut ndk_sys::ANativeActivity) {
|
||||
log::debug!("Resume: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_activity_state(State::Resume);
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_save_instance_state(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
out_len: *mut ndk_sys::size_t,
|
||||
) -> *mut libc::c_void {
|
||||
log::debug!("SaveInstanceState: {:p}\n", activity);
|
||||
*out_len = 0;
|
||||
let mut ret = ptr::null_mut();
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
let (state, len) = waitable_activity.request_save_state();
|
||||
*out_len = len as ndk_sys::size_t;
|
||||
ret = state
|
||||
});
|
||||
|
||||
log::debug!("Saved state = {:p}, len = {}", ret, *out_len);
|
||||
ret
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_pause(activity: *mut ndk_sys::ANativeActivity) {
|
||||
log::debug!("Pause: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_activity_state(State::Pause);
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_stop(activity: *mut ndk_sys::ANativeActivity) {
|
||||
log::debug!("Stop: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_activity_state(State::Stop);
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_configuration_changed(activity: *mut ndk_sys::ANativeActivity) {
|
||||
log::debug!("ConfigurationChanged: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_config_changed();
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_low_memory(activity: *mut ndk_sys::ANativeActivity) {
|
||||
log::debug!("LowMemory: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_low_memory();
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_window_focus_changed(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
focused: libc::c_int,
|
||||
) {
|
||||
log::debug!("WindowFocusChanged: {:p} -- {}\n", activity, focused);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_focus_changed(focused != 0);
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_native_window_created(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
window: *mut ndk_sys::ANativeWindow,
|
||||
) {
|
||||
log::debug!("NativeWindowCreated: {:p} -- {:p}\n", activity, window);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
// It's important that we use ::clone_from_ptr() here because NativeWindow
|
||||
// has a Drop implementation that will unconditionally _release() the native window
|
||||
let window = NativeWindow::clone_from_ptr(NonNull::new_unchecked(window));
|
||||
waitable_activity.set_window(Some(window));
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_native_window_destroyed(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
window: *mut ndk_sys::ANativeWindow,
|
||||
) {
|
||||
log::debug!("NativeWindowDestroyed: {:p} -- {:p}\n", activity, window);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_window(None);
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_input_queue_created(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
queue: *mut ndk_sys::AInputQueue,
|
||||
) {
|
||||
log::debug!("InputQueueCreated: {:p} -- {:p}\n", activity, queue);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_input(queue);
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_input_queue_destroyed(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
queue: *mut ndk_sys::AInputQueue,
|
||||
) {
|
||||
log::debug!("InputQueueDestroyed: {:p} -- {:p}\n", activity, queue);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_input(ptr::null_mut());
|
||||
});
|
||||
}
|
||||
|
||||
/// This is the native entrypoint for our cdylib library that `ANativeActivity` will look for via `dlsym`
|
||||
#[no_mangle]
|
||||
extern "C" fn ANativeActivity_onCreate(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
saved_state: *const libc::c_void,
|
||||
saved_state_size: libc::size_t,
|
||||
) {
|
||||
// Maybe make this stdout/stderr redirection an optional / opt-in feature?...
|
||||
unsafe {
|
||||
let mut logpipe: [RawFd; 2] = Default::default();
|
||||
libc::pipe(logpipe.as_mut_ptr());
|
||||
libc::dup2(logpipe[1], libc::STDOUT_FILENO);
|
||||
libc::dup2(logpipe[1], libc::STDERR_FILENO);
|
||||
std::thread::spawn(move || {
|
||||
let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap();
|
||||
let file = File::from_raw_fd(logpipe[0]);
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut buffer = String::new();
|
||||
loop {
|
||||
buffer.clear();
|
||||
if let Ok(len) = reader.read_line(&mut buffer) {
|
||||
if len == 0 {
|
||||
break;
|
||||
} else if let Ok(msg) = CString::new(buffer.clone()) {
|
||||
android_log(Level::Info, tag, &msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"Creating: {:p}, saved_state = {:p}, save_state_size = {}",
|
||||
activity, saved_state, saved_state_size
|
||||
);
|
||||
|
||||
// Conceptually we associate a glue reference with the JVM main thread, and another
|
||||
// reference with the Rust main thread
|
||||
let jvm_glue = NativeActivityGlue::new(activity, saved_state, saved_state_size);
|
||||
|
||||
let rust_glue = jvm_glue.clone();
|
||||
// Let us Send the NativeActivity pointer to the Rust main() thread without a wrapper type
|
||||
let activity_ptr: libc::intptr_t = activity as _;
|
||||
|
||||
// Note: we drop the thread handle which will detach the thread
|
||||
std::thread::spawn(move || {
|
||||
let activity: *mut ANativeActivity = activity_ptr as *mut _;
|
||||
|
||||
let jvm = unsafe {
|
||||
let na = activity;
|
||||
let jvm = (*na).vm;
|
||||
let activity = (*na).clazz; // Completely bogus name; this is the _instance_ not class pointer
|
||||
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
|
||||
|
||||
// Since this is a newly spawned thread then the JVM hasn't been attached
|
||||
// to the thread yet. Attach before calling the applications main function
|
||||
// so they can safely make JNI calls
|
||||
let mut jenv_out: *mut core::ffi::c_void = std::ptr::null_mut();
|
||||
if let Some(attach_current_thread) = (*(*jvm)).AttachCurrentThread {
|
||||
attach_current_thread(jvm, &mut jenv_out, std::ptr::null_mut());
|
||||
}
|
||||
|
||||
jvm
|
||||
};
|
||||
|
||||
let app = AndroidApp::new(rust_glue.clone());
|
||||
|
||||
rust_glue.notify_main_thread_running();
|
||||
|
||||
unsafe {
|
||||
// XXX: If we were in control of the Java Activity subclass then
|
||||
// we could potentially run the android_main function via a Java native method
|
||||
// springboard (e.g. call an Activity subclass method that calls a jni native
|
||||
// method that then just calls android_main()) that would make sure there was
|
||||
// a Java frame at the base of our call stack which would then be recognised
|
||||
// when calling FindClass to lookup a suitable classLoader, instead of
|
||||
// defaulting to the system loader. Without this then it's difficult for native
|
||||
// code to look up non-standard Java classes.
|
||||
android_main(app);
|
||||
|
||||
// Since this is a newly spawned thread then the JVM hasn't been attached
|
||||
// to the thread yet. Attach before calling the applications main function
|
||||
// so they can safely make JNI calls
|
||||
if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread {
|
||||
detach_current_thread(jvm);
|
||||
}
|
||||
|
||||
ndk_context::release_android_context();
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for thread to start.
|
||||
let mut guard = jvm_glue.mutex.lock().unwrap();
|
||||
while !guard.running {
|
||||
guard = jvm_glue.cond.wait(guard).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,23 @@
|
||||
#![cfg(any(feature = "native-activity", doc))]
|
||||
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::ops::Deref;
|
||||
use std::os::raw;
|
||||
use std::os::unix::prelude::*;
|
||||
use std::ptr;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
use std::{ptr, thread};
|
||||
|
||||
use log::{error, info, trace, Level};
|
||||
use log::{error, info, trace};
|
||||
|
||||
use ndk_sys::ALooper_wake;
|
||||
use ndk_sys::{ALooper, ALooper_pollAll};
|
||||
|
||||
use ndk::asset::AssetManager;
|
||||
use ndk::configuration::Configuration;
|
||||
use ndk::input_queue::InputQueue;
|
||||
use ndk::native_window::NativeWindow;
|
||||
|
||||
use crate::{
|
||||
util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
|
||||
};
|
||||
|
||||
mod ffi;
|
||||
use self::glue::NativeActivityGlue;
|
||||
|
||||
pub mod input {
|
||||
pub use ndk::event::{
|
||||
@@ -44,9 +36,15 @@ pub mod input {
|
||||
}
|
||||
}
|
||||
|
||||
// The only time it's safe to update the android_app->savedState pointer is
|
||||
mod glue;
|
||||
|
||||
pub const LOOPER_ID_MAIN: libc::c_int = 1;
|
||||
pub const LOOPER_ID_INPUT: libc::c_int = 2;
|
||||
//pub const LOOPER_ID_USER: ::std::os::raw::c_uint = 3;
|
||||
|
||||
// The only time it's safe to update the saved_state pointer is
|
||||
// while handling a SaveState event, so this API is only exposed for those
|
||||
// events...
|
||||
// events
|
||||
#[derive(Debug)]
|
||||
pub struct StateSaver<'a> {
|
||||
app: &'a AndroidAppInner,
|
||||
@@ -54,37 +52,7 @@ pub struct StateSaver<'a> {
|
||||
|
||||
impl<'a> StateSaver<'a> {
|
||||
pub fn store(&self, state: &'a [u8]) {
|
||||
// android_native_app_glue specifically expects savedState to have been allocated
|
||||
// via libc::malloc since it will automatically handle freeing the data once it
|
||||
// has been handed over to the Java Activity / main thread.
|
||||
unsafe {
|
||||
let app_ptr = self.app.native_app.as_ptr();
|
||||
|
||||
// In case the application calls store() multiple times for some reason we
|
||||
// make sure to free any pre-existing state...
|
||||
if (*app_ptr).savedState != ptr::null_mut() {
|
||||
libc::free((*app_ptr).savedState);
|
||||
(*app_ptr).savedState = ptr::null_mut();
|
||||
(*app_ptr).savedStateSize = 0;
|
||||
}
|
||||
|
||||
let buf = libc::malloc(state.len());
|
||||
if buf == ptr::null_mut() {
|
||||
panic!("Failed to allocate save_state buffer");
|
||||
}
|
||||
|
||||
// Since it's a byte array there's no special alignment requirement here.
|
||||
//
|
||||
// Since we re-define `buf` we ensure it's not possible to access the buffer
|
||||
// via its original pointer for the lifetime of the slice.
|
||||
{
|
||||
let buf: &mut [u8] = std::slice::from_raw_parts_mut(buf.cast(), state.len());
|
||||
buf.copy_from_slice(state);
|
||||
}
|
||||
|
||||
(*app_ptr).savedState = buf;
|
||||
(*app_ptr).savedStateSize = state.len() as _;
|
||||
}
|
||||
self.app.native_activity.set_saved_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,19 +62,7 @@ pub struct StateLoader<'a> {
|
||||
}
|
||||
impl<'a> StateLoader<'a> {
|
||||
pub fn load(&self) -> Option<Vec<u8>> {
|
||||
unsafe {
|
||||
let app_ptr = self.app.native_app.as_ptr();
|
||||
if (*app_ptr).savedState != ptr::null_mut() && (*app_ptr).savedStateSize > 0 {
|
||||
let buf: &mut [u8] = std::slice::from_raw_parts_mut(
|
||||
(*app_ptr).savedState.cast(),
|
||||
(*app_ptr).savedStateSize as usize,
|
||||
);
|
||||
let state = buf.to_vec();
|
||||
Some(state)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
self.app.native_activity.saved_state()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,53 +85,64 @@ impl AndroidAppWaker {
|
||||
}
|
||||
|
||||
impl AndroidApp {
|
||||
pub(crate) unsafe fn from_ptr(ptr: NonNull<ffi::android_app>) -> AndroidApp {
|
||||
// Note: we don't use from_ptr since we don't own the android_app.config
|
||||
// and need to keep in mind that the Drop handler is going to call
|
||||
// AConfiguration_delete()
|
||||
let config = Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config));
|
||||
|
||||
AndroidApp {
|
||||
pub(crate) fn new(native_activity: NativeActivityGlue) -> Self {
|
||||
let app = Self {
|
||||
inner: Arc::new(RwLock::new(AndroidAppInner {
|
||||
native_app: NativeAppGlue { ptr },
|
||||
config: ConfigurationRef::new(config),
|
||||
native_window: Default::default(),
|
||||
native_activity,
|
||||
looper: Looper {
|
||||
ptr: ptr::null_mut(),
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
{
|
||||
let mut guard = app.inner.write().unwrap();
|
||||
|
||||
let main_fd = guard.native_activity.cmd_read_fd();
|
||||
unsafe {
|
||||
guard.looper.ptr = ndk_sys::ALooper_prepare(
|
||||
ndk_sys::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as libc::c_int,
|
||||
);
|
||||
ndk_sys::ALooper_addFd(
|
||||
guard.looper.ptr,
|
||||
main_fd,
|
||||
LOOPER_ID_MAIN,
|
||||
ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int,
|
||||
None,
|
||||
//&mut guard.cmd_poll_source as *mut _ as *mut _);
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NativeAppGlue {
|
||||
ptr: NonNull<ffi::android_app>,
|
||||
struct Looper {
|
||||
pub ptr: *mut ALooper,
|
||||
}
|
||||
impl Deref for NativeAppGlue {
|
||||
type Target = NonNull<ffi::android_app>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.ptr
|
||||
}
|
||||
}
|
||||
unsafe impl Send for NativeAppGlue {}
|
||||
unsafe impl Sync for NativeAppGlue {}
|
||||
unsafe impl Send for Looper {}
|
||||
unsafe impl Sync for Looper {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AndroidAppInner {
|
||||
native_app: NativeAppGlue,
|
||||
config: ConfigurationRef,
|
||||
native_window: RwLock<Option<NativeWindow>>,
|
||||
pub(crate) native_activity: NativeActivityGlue,
|
||||
looper: Looper,
|
||||
}
|
||||
|
||||
impl AndroidAppInner {
|
||||
pub(crate) fn native_activity(&self) -> *const ndk_sys::ANativeActivity {
|
||||
unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
(*app_ptr).activity.cast()
|
||||
}
|
||||
self.native_activity.activity
|
||||
}
|
||||
|
||||
pub(crate) fn looper(&self) -> *mut ndk_sys::ALooper {
|
||||
self.looper.ptr
|
||||
}
|
||||
|
||||
pub fn native_window<'a>(&self) -> Option<NativeWindow> {
|
||||
self.native_window.read().unwrap().clone()
|
||||
self.native_activity.mutex.lock().unwrap().window.clone()
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&self, timeout: Option<Duration>, mut callback: F)
|
||||
@@ -185,8 +152,6 @@ impl AndroidAppInner {
|
||||
trace!("poll_events");
|
||||
|
||||
unsafe {
|
||||
let native_app = &self.native_app;
|
||||
|
||||
let mut fd: i32 = 0;
|
||||
let mut events: i32 = 0;
|
||||
let mut source: *mut core::ffi::c_void = ptr::null_mut();
|
||||
@@ -196,7 +161,12 @@ impl AndroidAppInner {
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
|
||||
info!("Calling ALooper_pollAll, timeout = {timeout_milliseconds}");
|
||||
assert!(
|
||||
ndk_sys::ALooper_forThread() != ptr::null_mut(),
|
||||
"Application tried to poll events from non-main thread"
|
||||
);
|
||||
let id = ALooper_pollAll(
|
||||
timeout_milliseconds,
|
||||
&mut fd,
|
||||
@@ -205,117 +175,88 @@ impl AndroidAppInner {
|
||||
);
|
||||
info!("pollAll id = {id}");
|
||||
match id {
|
||||
ffi::ALOOPER_POLL_WAKE => {
|
||||
ndk_sys::ALOOPER_POLL_WAKE => {
|
||||
trace!("ALooper_pollAll returned POLL_WAKE");
|
||||
callback(PollEvent::Wake);
|
||||
}
|
||||
ffi::ALOOPER_POLL_CALLBACK => {
|
||||
ndk_sys::ALOOPER_POLL_CALLBACK => {
|
||||
// ALooper_pollAll is documented to handle all callback sources internally so it should
|
||||
// never return a _CALLBACK source id...
|
||||
error!("Spurious ALOOPER_POLL_CALLBACK from ALopper_pollAll() (ignored)");
|
||||
}
|
||||
ffi::ALOOPER_POLL_TIMEOUT => {
|
||||
ndk_sys::ALOOPER_POLL_TIMEOUT => {
|
||||
trace!("ALooper_pollAll returned POLL_TIMEOUT");
|
||||
callback(PollEvent::Timeout);
|
||||
}
|
||||
ffi::ALOOPER_POLL_ERROR => {
|
||||
ndk_sys::ALOOPER_POLL_ERROR => {
|
||||
// If we have an IO error with our pipe to the main Java thread that's surely
|
||||
// not something we can recover from
|
||||
panic!("ALooper_pollAll returned POLL_ERROR");
|
||||
}
|
||||
id if id >= 0 => {
|
||||
match id as u32 {
|
||||
ffi::LOOPER_ID_MAIN => {
|
||||
match id {
|
||||
LOOPER_ID_MAIN => {
|
||||
trace!("ALooper_pollAll returned ID_MAIN");
|
||||
let source: *mut ffi::android_poll_source = source.cast();
|
||||
if source != ptr::null_mut() {
|
||||
let cmd_i = ffi::android_app_read_cmd(native_app.as_ptr());
|
||||
|
||||
let cmd = match cmd_i as u32 {
|
||||
if let Some(ipc_cmd) = self.native_activity.read_cmd() {
|
||||
let main_cmd = match ipc_cmd {
|
||||
// We don't forward info about the AInputQueue to apps since it's
|
||||
// an implementation details that's also not compatible with
|
||||
// GameActivity
|
||||
ffi::APP_CMD_INPUT_CHANGED => None,
|
||||
glue::AppCmd::InputQueueChanged => None,
|
||||
|
||||
ffi::APP_CMD_INIT_WINDOW => Some(MainEvent::InitWindow {}),
|
||||
ffi::APP_CMD_TERM_WINDOW => Some(MainEvent::TerminateWindow {}),
|
||||
ffi::APP_CMD_WINDOW_RESIZED => {
|
||||
glue::AppCmd::InitWindow => Some(MainEvent::InitWindow {}),
|
||||
glue::AppCmd::TermWindow => Some(MainEvent::TerminateWindow {}),
|
||||
glue::AppCmd::WindowResized => {
|
||||
Some(MainEvent::WindowResized {})
|
||||
}
|
||||
ffi::APP_CMD_WINDOW_REDRAW_NEEDED => {
|
||||
glue::AppCmd::WindowRedrawNeeded => {
|
||||
Some(MainEvent::RedrawNeeded {})
|
||||
}
|
||||
ffi::APP_CMD_CONTENT_RECT_CHANGED => {
|
||||
glue::AppCmd::ContentRectChanged => {
|
||||
Some(MainEvent::ContentRectChanged {})
|
||||
}
|
||||
ffi::APP_CMD_GAINED_FOCUS => Some(MainEvent::GainedFocus),
|
||||
ffi::APP_CMD_LOST_FOCUS => Some(MainEvent::LostFocus),
|
||||
ffi::APP_CMD_CONFIG_CHANGED => {
|
||||
glue::AppCmd::GainedFocus => Some(MainEvent::GainedFocus),
|
||||
glue::AppCmd::LostFocus => Some(MainEvent::LostFocus),
|
||||
glue::AppCmd::ConfigChanged => {
|
||||
Some(MainEvent::ConfigChanged {})
|
||||
}
|
||||
ffi::APP_CMD_LOW_MEMORY => Some(MainEvent::LowMemory),
|
||||
ffi::APP_CMD_START => Some(MainEvent::Start),
|
||||
ffi::APP_CMD_RESUME => Some(MainEvent::Resume {
|
||||
glue::AppCmd::LowMemory => Some(MainEvent::LowMemory),
|
||||
glue::AppCmd::Start => Some(MainEvent::Start),
|
||||
glue::AppCmd::Resume => Some(MainEvent::Resume {
|
||||
loader: StateLoader { app: &self },
|
||||
}),
|
||||
ffi::APP_CMD_SAVE_STATE => Some(MainEvent::SaveState {
|
||||
glue::AppCmd::SaveState => Some(MainEvent::SaveState {
|
||||
saver: StateSaver { app: &self },
|
||||
}),
|
||||
ffi::APP_CMD_PAUSE => Some(MainEvent::Pause),
|
||||
ffi::APP_CMD_STOP => Some(MainEvent::Stop),
|
||||
ffi::APP_CMD_DESTROY => Some(MainEvent::Destroy),
|
||||
|
||||
//ffi::NativeAppGlueAppCmd_APP_CMD_WINDOW_INSETS_CHANGED => MainEvent::InsetsChanged {},
|
||||
_ => unreachable!(),
|
||||
glue::AppCmd::Pause => Some(MainEvent::Pause),
|
||||
glue::AppCmd::Stop => Some(MainEvent::Stop),
|
||||
glue::AppCmd::Destroy => Some(MainEvent::Destroy),
|
||||
};
|
||||
|
||||
trace!("Calling android_app_pre_exec_cmd({cmd_i})");
|
||||
ffi::android_app_pre_exec_cmd(native_app.as_ptr(), cmd_i);
|
||||
trace!("Calling pre_exec_cmd({ipc_cmd:#?})");
|
||||
self.native_activity.pre_exec_cmd(
|
||||
ipc_cmd,
|
||||
self.looper(),
|
||||
LOOPER_ID_INPUT,
|
||||
);
|
||||
|
||||
if let Some(cmd) = cmd {
|
||||
trace!("Read ID_MAIN command {cmd_i} = {cmd:?}");
|
||||
match cmd {
|
||||
MainEvent::ConfigChanged { .. } => {
|
||||
self.config.replace(Configuration::clone_from_ptr(
|
||||
NonNull::new_unchecked(
|
||||
(*native_app.as_ptr()).config,
|
||||
),
|
||||
));
|
||||
}
|
||||
MainEvent::InitWindow { .. } => {
|
||||
let win_ptr = (*native_app.as_ptr()).window;
|
||||
// It's important that we use ::clone_from_ptr() here
|
||||
// because NativeWindow has a Drop implementation that
|
||||
// will unconditionally _release() the native window
|
||||
*self.native_window.write().unwrap() =
|
||||
Some(NativeWindow::clone_from_ptr(
|
||||
NonNull::new(win_ptr).unwrap(),
|
||||
));
|
||||
}
|
||||
MainEvent::TerminateWindow { .. } => {
|
||||
*self.native_window.write().unwrap() = None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
trace!("Invoking callback for ID_MAIN command = {:?}", cmd);
|
||||
callback(PollEvent::Main(cmd));
|
||||
if let Some(main_cmd) = main_cmd {
|
||||
trace!("Invoking callback for ID_MAIN command = {main_cmd:?}");
|
||||
callback(PollEvent::Main(main_cmd));
|
||||
}
|
||||
|
||||
trace!("Calling android_app_post_exec_cmd({cmd_i})");
|
||||
ffi::android_app_post_exec_cmd(native_app.as_ptr(), cmd_i);
|
||||
} else {
|
||||
panic!("ALooper_pollAll returned ID_MAIN event with NULL android_poll_source!");
|
||||
trace!("Calling post_exec_cmd({ipc_cmd:#?})");
|
||||
self.native_activity.post_exec_cmd(ipc_cmd);
|
||||
}
|
||||
}
|
||||
ffi::LOOPER_ID_INPUT => {
|
||||
LOOPER_ID_INPUT => {
|
||||
trace!("ALooper_pollAll returned ID_INPUT");
|
||||
|
||||
// To avoid spamming the application with event loop iterations notifying them of
|
||||
// input events then we only send one `InputAvailable` per iteration of input
|
||||
// handling. We re-attach the looper when the application calls
|
||||
// `AndroidApp::input_events()`
|
||||
ffi::android_app_detach_input_queue_looper(native_app.as_ptr());
|
||||
self.native_activity.detach_input_queue_from_looper();
|
||||
callback(PollEvent::Main(MainEvent::InputAvailable))
|
||||
}
|
||||
_ => {
|
||||
@@ -332,35 +273,26 @@ impl AndroidAppInner {
|
||||
|
||||
pub fn create_waker(&self) -> AndroidAppWaker {
|
||||
unsafe {
|
||||
// From the application's pov we assume the app_ptr and looper pointer
|
||||
// have static lifetimes and we can safely assume they are never NULL.
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
// From the application's pov we assume the looper pointer has a static
|
||||
// lifetimes and we can safely assume it is never NULL.
|
||||
AndroidAppWaker {
|
||||
looper: NonNull::new_unchecked((*app_ptr).looper),
|
||||
looper: NonNull::new_unchecked(self.looper.ptr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(&self) -> ConfigurationRef {
|
||||
self.config.clone()
|
||||
self.native_activity.config()
|
||||
}
|
||||
|
||||
pub fn content_rect(&self) -> Rect {
|
||||
unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
Rect {
|
||||
left: (*app_ptr).contentRect.left,
|
||||
right: (*app_ptr).contentRect.right,
|
||||
top: (*app_ptr).contentRect.top,
|
||||
bottom: (*app_ptr).contentRect.bottom,
|
||||
}
|
||||
}
|
||||
self.native_activity.content_rect()
|
||||
}
|
||||
|
||||
pub fn asset_manager(&self) -> AssetManager {
|
||||
unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
let am_ptr = NonNull::new_unchecked((*(*app_ptr).activity).assetManager);
|
||||
let activity_ptr = self.native_activity.activity;
|
||||
let am_ptr = NonNull::new_unchecked((*activity_ptr).assetManager);
|
||||
AssetManager::from_ptr(am_ptr)
|
||||
}
|
||||
}
|
||||
@@ -373,7 +305,7 @@ impl AndroidAppInner {
|
||||
let na = self.native_activity();
|
||||
let na_mut = na as *mut ndk_sys::ANativeActivity;
|
||||
unsafe {
|
||||
ffi::ANativeActivity_setWindowFlags(
|
||||
ndk_sys::ANativeActivity_setWindowFlags(
|
||||
na_mut.cast(),
|
||||
add_flags.bits(),
|
||||
remove_flags.bits(),
|
||||
@@ -386,11 +318,11 @@ impl AndroidAppInner {
|
||||
let na = self.native_activity();
|
||||
unsafe {
|
||||
let flags = if show_implicit {
|
||||
ffi::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT
|
||||
ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ffi::ANativeActivity_showSoftInput(na as *mut _, flags);
|
||||
ndk_sys::ANativeActivity_showSoftInput(na as *mut _, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,11 +331,11 @@ impl AndroidAppInner {
|
||||
let na = self.native_activity();
|
||||
unsafe {
|
||||
let flags = if hide_implicit_only {
|
||||
ffi::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY
|
||||
ndk_sys::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ffi::ANativeActivity_hideSoftInput(na as *mut _, flags);
|
||||
ndk_sys::ANativeActivity_hideSoftInput(na as *mut _, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,18 +351,15 @@ impl AndroidAppInner {
|
||||
where
|
||||
F: FnMut(&input::InputEvent) -> InputStatus,
|
||||
{
|
||||
let queue = unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
if (*app_ptr).inputQueue == ptr::null_mut() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reattach the input queue to the looper so future input will again deliver an
|
||||
// `InputAvailable` event.
|
||||
ffi::android_app_attach_input_queue_looper(app_ptr);
|
||||
|
||||
let queue = NonNull::new_unchecked((*app_ptr).inputQueue);
|
||||
InputQueue::from_ptr(queue)
|
||||
// Get the InputQueue for the NativeActivity (if there is one) and also ensure
|
||||
// the queue is re-attached to our event Looper (so new input events will again
|
||||
// trigger a wake up)
|
||||
let queue = self
|
||||
.native_activity
|
||||
.looper_attached_input_queue(self.looper(), LOOPER_ID_INPUT);
|
||||
let queue = match queue {
|
||||
Some(queue) => queue,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Note: we basically ignore errors from get_event() currently. Looking
|
||||
@@ -478,104 +407,3 @@ impl AndroidAppInner {
|
||||
unsafe { util::try_get_path_from_ptr((*na).obbPath) }
|
||||
}
|
||||
}
|
||||
|
||||
// Rust doesn't give us a clean way to directly export symbols from C/C++
|
||||
// so we rename the C/C++ symbols and re-export this entrypoint from
|
||||
// Rust...
|
||||
//
|
||||
// https://github.com/rust-lang/rfcs/issues/2771
|
||||
extern "C" {
|
||||
pub fn ANativeActivity_onCreate_C(
|
||||
activity: *mut std::os::raw::c_void,
|
||||
savedState: *mut ::std::os::raw::c_void,
|
||||
savedStateSize: usize,
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn ANativeActivity_onCreate(
|
||||
activity: *mut std::os::raw::c_void,
|
||||
saved_state: *mut std::os::raw::c_void,
|
||||
saved_state_size: usize,
|
||||
) {
|
||||
ANativeActivity_onCreate_C(activity, saved_state, saved_state_size);
|
||||
}
|
||||
|
||||
fn android_log(level: Level, tag: &CStr, msg: &CStr) {
|
||||
let prio = match level {
|
||||
Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR,
|
||||
Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN,
|
||||
Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO,
|
||||
Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG,
|
||||
Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE,
|
||||
};
|
||||
unsafe {
|
||||
ndk_sys::__android_log_write(prio.0 as raw::c_int, tag.as_ptr(), msg.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
pub fn android_main(app: AndroidApp);
|
||||
}
|
||||
|
||||
// This is a spring board between android_native_app_glue and the user's
|
||||
// `app_main` function. This is run on a dedicated thread spawned
|
||||
// by android_native_app_glue.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn _rust_glue_entry(app: *mut ffi::android_app) {
|
||||
// Maybe make this stdout/stderr redirection an optional / opt-in feature?...
|
||||
let mut logpipe: [RawFd; 2] = Default::default();
|
||||
libc::pipe(logpipe.as_mut_ptr());
|
||||
libc::dup2(logpipe[1], libc::STDOUT_FILENO);
|
||||
libc::dup2(logpipe[1], libc::STDERR_FILENO);
|
||||
thread::spawn(move || {
|
||||
let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap();
|
||||
let file = File::from_raw_fd(logpipe[0]);
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut buffer = String::new();
|
||||
loop {
|
||||
buffer.clear();
|
||||
if let Ok(len) = reader.read_line(&mut buffer) {
|
||||
if len == 0 {
|
||||
break;
|
||||
} else if let Ok(msg) = CString::new(buffer.clone()) {
|
||||
android_log(Level::Info, tag, &msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let app = AndroidApp::from_ptr(NonNull::new(app).unwrap());
|
||||
|
||||
let na = app.native_activity();
|
||||
let jvm = (*na).vm;
|
||||
let activity = (*na).clazz; // Completely bogus name; this is the _instance_ not class pointer
|
||||
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
|
||||
|
||||
// Since this is a newly spawned thread then the JVM hasn't been attached
|
||||
// to the thread yet. Attach before calling the applications main function
|
||||
// so they can safely make JNI calls
|
||||
let mut jenv_out: *mut core::ffi::c_void = std::ptr::null_mut();
|
||||
if let Some(attach_current_thread) = (*(*jvm)).AttachCurrentThread {
|
||||
attach_current_thread(jvm, &mut jenv_out, std::ptr::null_mut());
|
||||
}
|
||||
|
||||
// XXX: If we were in control of the Java Activity subclass then
|
||||
// we could potentially run the android_main function via a Java native method
|
||||
// springboard (e.g. call an Activity subclass method that calls a jni native
|
||||
// method that then just calls android_main()) that would make sure there was
|
||||
// a Java frame at the base of our call stack which would then be recognised
|
||||
// when calling FindClass to lookup a suitable classLoader, instead of
|
||||
// defaulting to the system loader. Without this then it's difficult for native
|
||||
// code to look up non-standard Java classes.
|
||||
android_main(app);
|
||||
|
||||
// Since this is a newly spawned thread then the JVM hasn't been attached
|
||||
// to the thread yet. Attach before calling the applications main function
|
||||
// so they can safely make JNI calls
|
||||
if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread {
|
||||
detach_current_thread(jvm);
|
||||
}
|
||||
|
||||
ndk_context::release_android_context();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user