1312 lines
47 KiB
C++
1312 lines
47 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
#define LOG_TAG "GameActivity"
|
|
|
|
#include "GameActivity.h"
|
|
|
|
#include <android/api-level.h>
|
|
#include <android/asset_manager.h>
|
|
#include <android/asset_manager_jni.h>
|
|
#include <android/log.h>
|
|
#include <android/looper.h>
|
|
#include <android/native_window.h>
|
|
#include <android/native_window_jni.h>
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <jni.h>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/system_properties.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
|
|
// TODO(b/187147166): these functions were extracted from the Game SDK
|
|
// (gamesdk/src/common/system_utils.h). system_utils.h/cpp should be used
|
|
// instead.
|
|
namespace {
|
|
|
|
#if __ANDROID_API__ >= 26
|
|
std::string getSystemPropViaCallback(const char *key,
|
|
const char *default_value = "") {
|
|
const prop_info *prop = __system_property_find(key);
|
|
if (prop == nullptr) {
|
|
return default_value;
|
|
}
|
|
std::string return_value;
|
|
auto thunk = [](void *cookie, const char * /*name*/, const char *value,
|
|
uint32_t /*serial*/) {
|
|
if (value != nullptr) {
|
|
std::string *r = static_cast<std::string *>(cookie);
|
|
*r = value;
|
|
}
|
|
};
|
|
__system_property_read_callback(prop, thunk, &return_value);
|
|
return return_value;
|
|
}
|
|
#else
|
|
std::string getSystemPropViaGet(const char *key,
|
|
const char *default_value = "") {
|
|
char buffer[PROP_VALUE_MAX + 1] = ""; // +1 for terminator
|
|
int bufferLen = __system_property_get(key, buffer);
|
|
if (bufferLen > 0)
|
|
return buffer;
|
|
else
|
|
return "";
|
|
}
|
|
#endif
|
|
|
|
std::string GetSystemProp(const char *key, const char *default_value = "") {
|
|
#if __ANDROID_API__ >= 26
|
|
return getSystemPropViaCallback(key, default_value);
|
|
#else
|
|
return getSystemPropViaGet(key, default_value);
|
|
#endif
|
|
}
|
|
|
|
int GetSystemPropAsInt(const char *key, int default_value = 0) {
|
|
std::string prop = GetSystemProp(key);
|
|
return prop == "" ? default_value : strtoll(prop.c_str(), nullptr, 10);
|
|
}
|
|
|
|
struct OwnedGameTextInputState {
|
|
OwnedGameTextInputState &operator=(const GameTextInputState &rhs) {
|
|
inner = rhs;
|
|
owned_string = std::string(rhs.text_UTF8, rhs.text_length);
|
|
inner.text_UTF8 = owned_string.data();
|
|
return *this;
|
|
}
|
|
GameTextInputState inner;
|
|
std::string owned_string;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);
|
|
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);
|
|
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);
|
|
#ifdef NDEBUG
|
|
#define ALOGV(...)
|
|
#else
|
|
#define ALOGV(...) \
|
|
__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
|
|
#endif
|
|
|
|
/* Returns 2nd arg. Used to substitute default value if caller's vararg list
|
|
* is empty.
|
|
*/
|
|
#define __android_second(first, second, ...) second
|
|
|
|
/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
|
|
* returns nothing.
|
|
*/
|
|
#define __android_rest(first, ...) , ##__VA_ARGS__
|
|
|
|
#define android_printAssert(cond, tag, fmt...) \
|
|
__android_log_assert(cond, tag, \
|
|
__android_second(0, ##fmt, NULL) __android_rest(fmt))
|
|
|
|
#define CONDITION(cond) (__builtin_expect((cond) != 0, 0))
|
|
|
|
#ifndef LOG_ALWAYS_FATAL_IF
|
|
#define LOG_ALWAYS_FATAL_IF(cond, ...) \
|
|
((CONDITION(cond)) \
|
|
? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \
|
|
: (void)0)
|
|
#endif
|
|
|
|
#ifndef LOG_ALWAYS_FATAL
|
|
#define LOG_ALWAYS_FATAL(...) \
|
|
(((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
|
|
#endif
|
|
|
|
/*
|
|
* Simplified macro to send a warning system log message using current LOG_TAG.
|
|
*/
|
|
#ifndef SLOGW
|
|
#define SLOGW(...) \
|
|
((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
|
|
#endif
|
|
|
|
#ifndef SLOGW_IF
|
|
#define SLOGW_IF(cond, ...) \
|
|
((__predict_false(cond)) \
|
|
? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
|
|
: (void)0)
|
|
#endif
|
|
|
|
/*
|
|
* Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
|
|
* are stripped out of release builds.
|
|
*/
|
|
#if LOG_NDEBUG
|
|
|
|
#ifndef LOG_FATAL_IF
|
|
#define LOG_FATAL_IF(cond, ...) ((void)0)
|
|
#endif
|
|
#ifndef LOG_FATAL
|
|
#define LOG_FATAL(...) ((void)0)
|
|
#endif
|
|
|
|
#else
|
|
|
|
#ifndef LOG_FATAL_IF
|
|
#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__)
|
|
#endif
|
|
#ifndef LOG_FATAL
|
|
#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
|
|
#endif
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Assertion that generates a log message when the assertion fails.
|
|
* Stripped out of release builds. Uses the current LOG_TAG.
|
|
*/
|
|
#ifndef ALOG_ASSERT
|
|
#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
|
|
#endif
|
|
|
|
#define LOG_TRACE(...)
|
|
|
|
#ifndef NELEM
|
|
#define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0])))
|
|
#endif
|
|
|
|
/*
|
|
* JNI methods of the GameActivity Java class.
|
|
*/
|
|
static struct {
|
|
jmethodID finish;
|
|
jmethodID setWindowFlags;
|
|
jmethodID getWindowInsets;
|
|
jmethodID getWaterfallInsets;
|
|
jmethodID setImeEditorInfoFields;
|
|
} gGameActivityClassInfo;
|
|
|
|
/*
|
|
* JNI fields of the androidx.core.graphics.Insets Java class.
|
|
*/
|
|
static struct {
|
|
jfieldID left;
|
|
jfieldID right;
|
|
jfieldID top;
|
|
jfieldID bottom;
|
|
} gInsetsClassInfo;
|
|
|
|
/*
|
|
* JNI methods of the WindowInsetsCompat.Type Java class.
|
|
*/
|
|
static struct {
|
|
jmethodID methods[GAMECOMMON_INSETS_TYPE_COUNT];
|
|
jclass clazz;
|
|
} gWindowInsetsCompatTypeClassInfo;
|
|
|
|
/*
|
|
* Contains a command to be executed by the GameActivity
|
|
* on the application main thread.
|
|
*/
|
|
struct ActivityWork {
|
|
int32_t cmd;
|
|
int64_t arg1;
|
|
int64_t arg2;
|
|
};
|
|
|
|
/*
|
|
* The type of commands that can be passed to the GameActivity and that
|
|
* are executed on the application main thread.
|
|
*/
|
|
enum {
|
|
CMD_FINISH = 1,
|
|
CMD_SET_WINDOW_FORMAT,
|
|
CMD_SET_WINDOW_FLAGS,
|
|
CMD_SHOW_SOFT_INPUT,
|
|
CMD_HIDE_SOFT_INPUT,
|
|
CMD_SET_SOFT_INPUT_STATE
|
|
};
|
|
|
|
/*
|
|
* Write a command to be executed by the GameActivity on the application main
|
|
* thread.
|
|
*/
|
|
static void write_work(int fd, int32_t cmd, int64_t arg1 = 0,
|
|
int64_t arg2 = 0) {
|
|
ActivityWork work;
|
|
work.cmd = cmd;
|
|
work.arg1 = arg1;
|
|
work.arg2 = arg2;
|
|
|
|
LOG_TRACE("write_work: cmd=%d", cmd);
|
|
restart:
|
|
int res = write(fd, &work, sizeof(work));
|
|
if (res < 0 && errno == EINTR) {
|
|
goto restart;
|
|
}
|
|
|
|
if (res == sizeof(work)) return;
|
|
|
|
if (res < 0) {
|
|
ALOGW("Failed writing to work fd: %s", strerror(errno));
|
|
} else {
|
|
ALOGW("Truncated writing to work fd: %d", res);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read commands to be executed by the GameActivity on the application main
|
|
* thread.
|
|
*/
|
|
static bool read_work(int fd, ActivityWork *outWork) {
|
|
int res = read(fd, outWork, sizeof(ActivityWork));
|
|
// no need to worry about EINTR, poll loop will just come back again.
|
|
if (res == sizeof(ActivityWork)) return true;
|
|
|
|
if (res < 0) {
|
|
ALOGW("Failed reading work fd: %s", strerror(errno));
|
|
} else {
|
|
ALOGW("Truncated reading work fd: %d", res);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Native state for interacting with the GameActivity class.
|
|
*/
|
|
struct NativeCode : public GameActivity {
|
|
NativeCode(void *_dlhandle, GameActivity_createFunc *_createFunc) {
|
|
memset((GameActivity *)this, 0, sizeof(GameActivity));
|
|
memset(&callbacks, 0, sizeof(callbacks));
|
|
memset(&insetsState, 0, sizeof(insetsState));
|
|
dlhandle = _dlhandle;
|
|
createActivityFunc = _createFunc;
|
|
nativeWindow = NULL;
|
|
mainWorkRead = mainWorkWrite = -1;
|
|
gameTextInput = NULL;
|
|
}
|
|
|
|
~NativeCode() {
|
|
if (callbacks.onDestroy != NULL) {
|
|
callbacks.onDestroy(this);
|
|
}
|
|
if (env != NULL) {
|
|
if (javaGameActivity != NULL) {
|
|
env->DeleteGlobalRef(javaGameActivity);
|
|
}
|
|
if (javaAssetManager != NULL) {
|
|
env->DeleteGlobalRef(javaAssetManager);
|
|
}
|
|
}
|
|
GameTextInput_destroy(gameTextInput);
|
|
if (looper != NULL && mainWorkRead >= 0) {
|
|
ALooper_removeFd(looper, mainWorkRead);
|
|
}
|
|
ALooper_release(looper);
|
|
looper = NULL;
|
|
|
|
setSurface(NULL);
|
|
if (mainWorkRead >= 0) close(mainWorkRead);
|
|
if (mainWorkWrite >= 0) close(mainWorkWrite);
|
|
if (dlhandle != NULL) {
|
|
// for now don't unload... we probably should clean this
|
|
// up and only keep one open dlhandle per proc, since there
|
|
// is really no benefit to unloading the code.
|
|
// dlclose(dlhandle);
|
|
}
|
|
}
|
|
|
|
void setSurface(jobject _surface) {
|
|
if (nativeWindow != NULL) {
|
|
ANativeWindow_release(nativeWindow);
|
|
}
|
|
if (_surface != NULL) {
|
|
nativeWindow = ANativeWindow_fromSurface(env, _surface);
|
|
} else {
|
|
nativeWindow = NULL;
|
|
}
|
|
}
|
|
|
|
GameActivityCallbacks callbacks;
|
|
|
|
void *dlhandle;
|
|
GameActivity_createFunc *createActivityFunc;
|
|
|
|
std::string internalDataPathObj;
|
|
std::string externalDataPathObj;
|
|
std::string obbPathObj;
|
|
|
|
ANativeWindow *nativeWindow;
|
|
int32_t lastWindowWidth;
|
|
int32_t lastWindowHeight;
|
|
|
|
// These are used to wake up the main thread to process work.
|
|
int mainWorkRead;
|
|
int mainWorkWrite;
|
|
ALooper *looper;
|
|
|
|
// Need to hold on to a reference here in case the upper layers destroy our
|
|
// AssetManager.
|
|
jobject javaAssetManager;
|
|
|
|
GameTextInput *gameTextInput;
|
|
// Set by users in GameActivity_setTextInputState, then passed to
|
|
// GameTextInput.
|
|
OwnedGameTextInputState gameTextInputState;
|
|
std::mutex gameTextInputStateMutex;
|
|
|
|
ARect insetsState[GAMECOMMON_INSETS_TYPE_COUNT];
|
|
};
|
|
|
|
extern "C" void GameActivity_finish(GameActivity *activity) {
|
|
NativeCode *code = static_cast<NativeCode *>(activity);
|
|
write_work(code->mainWorkWrite, CMD_FINISH, 0);
|
|
}
|
|
|
|
extern "C" void GameActivity_setWindowFlags(GameActivity *activity,
|
|
uint32_t values, uint32_t mask) {
|
|
NativeCode *code = static_cast<NativeCode *>(activity);
|
|
write_work(code->mainWorkWrite, CMD_SET_WINDOW_FLAGS, values, mask);
|
|
}
|
|
|
|
extern "C" void GameActivity_showSoftInput(GameActivity *activity,
|
|
uint32_t flags) {
|
|
NativeCode *code = static_cast<NativeCode *>(activity);
|
|
write_work(code->mainWorkWrite, CMD_SHOW_SOFT_INPUT, flags);
|
|
}
|
|
|
|
extern "C" void GameActivity_setTextInputState(
|
|
GameActivity *activity, const GameTextInputState *state) {
|
|
NativeCode *code = static_cast<NativeCode *>(activity);
|
|
std::lock_guard<std::mutex> lock(code->gameTextInputStateMutex);
|
|
code->gameTextInputState = *state;
|
|
write_work(code->mainWorkWrite, CMD_SET_SOFT_INPUT_STATE);
|
|
}
|
|
|
|
extern "C" void GameActivity_getTextInputState(
|
|
GameActivity *activity, GameTextInputGetStateCallback callback,
|
|
void *context) {
|
|
NativeCode *code = static_cast<NativeCode *>(activity);
|
|
return GameTextInput_getState(code->gameTextInput, callback, context);
|
|
}
|
|
|
|
extern "C" void GameActivity_hideSoftInput(GameActivity *activity,
|
|
uint32_t flags) {
|
|
NativeCode *code = static_cast<NativeCode *>(activity);
|
|
write_work(code->mainWorkWrite, CMD_HIDE_SOFT_INPUT, flags);
|
|
}
|
|
|
|
extern "C" void GameActivity_getWindowInsets(GameActivity *activity,
|
|
GameCommonInsetsType type,
|
|
ARect *insets) {
|
|
if (type < 0 || type >= GAMECOMMON_INSETS_TYPE_COUNT) return;
|
|
NativeCode *code = static_cast<NativeCode *>(activity);
|
|
*insets = code->insetsState[type];
|
|
}
|
|
|
|
extern "C" GameTextInput *GameActivity_getTextInput(
|
|
const GameActivity *activity) {
|
|
const NativeCode *code = static_cast<const NativeCode *>(activity);
|
|
return code->gameTextInput;
|
|
}
|
|
|
|
/*
|
|
* Log the JNI exception, if any.
|
|
*/
|
|
static void checkAndClearException(JNIEnv *env, const char *methodName) {
|
|
if (env->ExceptionCheck()) {
|
|
ALOGE("Exception while running %s", methodName);
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Callback for handling native events on the application's main thread.
|
|
*/
|
|
static int mainWorkCallback(int fd, int events, void *data) {
|
|
ALOGD("************** mainWorkCallback *********");
|
|
NativeCode *code = (NativeCode *)data;
|
|
if ((events & POLLIN) == 0) {
|
|
return 1;
|
|
}
|
|
|
|
ActivityWork work;
|
|
if (!read_work(code->mainWorkRead, &work)) {
|
|
return 1;
|
|
}
|
|
LOG_TRACE("mainWorkCallback: cmd=%d", work.cmd);
|
|
switch (work.cmd) {
|
|
case CMD_FINISH: {
|
|
code->env->CallVoidMethod(code->javaGameActivity,
|
|
gGameActivityClassInfo.finish);
|
|
checkAndClearException(code->env, "finish");
|
|
} break;
|
|
case CMD_SET_WINDOW_FLAGS: {
|
|
code->env->CallVoidMethod(code->javaGameActivity,
|
|
gGameActivityClassInfo.setWindowFlags,
|
|
work.arg1, work.arg2);
|
|
checkAndClearException(code->env, "setWindowFlags");
|
|
} break;
|
|
case CMD_SHOW_SOFT_INPUT: {
|
|
GameTextInput_showIme(code->gameTextInput, work.arg1);
|
|
} break;
|
|
case CMD_SET_SOFT_INPUT_STATE: {
|
|
std::lock_guard<std::mutex> lock(code->gameTextInputStateMutex);
|
|
GameTextInput_setState(code->gameTextInput,
|
|
&code->gameTextInputState.inner);
|
|
checkAndClearException(code->env, "setTextInputState");
|
|
} break;
|
|
case CMD_HIDE_SOFT_INPUT: {
|
|
GameTextInput_hideIme(code->gameTextInput, work.arg1);
|
|
} break;
|
|
default:
|
|
ALOGW("Unknown work command: %d", work.cmd);
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
static thread_local std::string g_error_msg;
|
|
|
|
static jlong loadNativeCode_native(JNIEnv *env, jobject javaGameActivity,
|
|
jstring path, jstring funcName,
|
|
jstring internalDataDir, jstring obbDir,
|
|
jstring externalDataDir, jobject jAssetMgr,
|
|
jbyteArray savedState) {
|
|
LOG_TRACE("loadNativeCode_native");
|
|
const char *pathStr = env->GetStringUTFChars(path, NULL);
|
|
NativeCode *code = NULL;
|
|
|
|
void *handle = dlopen(pathStr, RTLD_LAZY);
|
|
|
|
env->ReleaseStringUTFChars(path, pathStr);
|
|
|
|
if (handle == nullptr) {
|
|
g_error_msg = dlerror();
|
|
ALOGE("GameActivity dlopen(\"%s\") failed: %s", pathStr,
|
|
g_error_msg.c_str());
|
|
return 0;
|
|
}
|
|
|
|
const char *funcStr = env->GetStringUTFChars(funcName, NULL);
|
|
code = new NativeCode(handle,
|
|
(GameActivity_createFunc *)dlsym(handle, funcStr));
|
|
env->ReleaseStringUTFChars(funcName, funcStr);
|
|
|
|
if (code->createActivityFunc == nullptr) {
|
|
g_error_msg = dlerror();
|
|
ALOGW("GameActivity_onCreate not found: %s", g_error_msg.c_str());
|
|
delete code;
|
|
return 0;
|
|
}
|
|
|
|
code->looper = ALooper_forThread();
|
|
if (code->looper == nullptr) {
|
|
g_error_msg = "Unable to retrieve native ALooper";
|
|
ALOGW("%s", g_error_msg.c_str());
|
|
delete code;
|
|
return 0;
|
|
}
|
|
ALooper_acquire(code->looper);
|
|
|
|
int msgpipe[2];
|
|
if (pipe(msgpipe)) {
|
|
g_error_msg = "could not create pipe: ";
|
|
g_error_msg += strerror(errno);
|
|
|
|
ALOGW("%s", g_error_msg.c_str());
|
|
delete code;
|
|
return 0;
|
|
}
|
|
code->mainWorkRead = msgpipe[0];
|
|
code->mainWorkWrite = msgpipe[1];
|
|
int result = fcntl(code->mainWorkRead, F_SETFL, O_NONBLOCK);
|
|
SLOGW_IF(result != 0,
|
|
"Could not make main work read pipe "
|
|
"non-blocking: %s",
|
|
strerror(errno));
|
|
result = fcntl(code->mainWorkWrite, F_SETFL, O_NONBLOCK);
|
|
SLOGW_IF(result != 0,
|
|
"Could not make main work write pipe "
|
|
"non-blocking: %s",
|
|
strerror(errno));
|
|
ALooper_addFd(code->looper, code->mainWorkRead, 0, ALOOPER_EVENT_INPUT,
|
|
mainWorkCallback, code);
|
|
|
|
code->GameActivity::callbacks = &code->callbacks;
|
|
if (env->GetJavaVM(&code->vm) < 0) {
|
|
ALOGW("GameActivity GetJavaVM failed");
|
|
delete code;
|
|
return 0;
|
|
}
|
|
code->env = env;
|
|
code->javaGameActivity = env->NewGlobalRef(javaGameActivity);
|
|
|
|
const char *dirStr =
|
|
internalDataDir ? env->GetStringUTFChars(internalDataDir, NULL) : "";
|
|
code->internalDataPathObj = dirStr;
|
|
code->internalDataPath = code->internalDataPathObj.c_str();
|
|
if (internalDataDir) env->ReleaseStringUTFChars(internalDataDir, dirStr);
|
|
|
|
dirStr =
|
|
externalDataDir ? env->GetStringUTFChars(externalDataDir, NULL) : "";
|
|
code->externalDataPathObj = dirStr;
|
|
code->externalDataPath = code->externalDataPathObj.c_str();
|
|
if (externalDataDir) env->ReleaseStringUTFChars(externalDataDir, dirStr);
|
|
|
|
code->javaAssetManager = env->NewGlobalRef(jAssetMgr);
|
|
code->assetManager = AAssetManager_fromJava(env, jAssetMgr);
|
|
|
|
dirStr = obbDir ? env->GetStringUTFChars(obbDir, NULL) : "";
|
|
code->obbPathObj = dirStr;
|
|
code->obbPath = code->obbPathObj.c_str();
|
|
if (obbDir) env->ReleaseStringUTFChars(obbDir, dirStr);
|
|
|
|
jbyte *rawSavedState = NULL;
|
|
jsize rawSavedSize = 0;
|
|
if (savedState != NULL) {
|
|
rawSavedState = env->GetByteArrayElements(savedState, NULL);
|
|
rawSavedSize = env->GetArrayLength(savedState);
|
|
}
|
|
code->createActivityFunc(code, rawSavedState, rawSavedSize);
|
|
|
|
code->gameTextInput = GameTextInput_init(env, 0);
|
|
GameTextInput_setEventCallback(code->gameTextInput,
|
|
reinterpret_cast<GameTextInputEventCallback>(
|
|
code->callbacks.onTextInputEvent),
|
|
code);
|
|
|
|
if (rawSavedState != NULL) {
|
|
env->ReleaseByteArrayElements(savedState, rawSavedState, 0);
|
|
}
|
|
|
|
return reinterpret_cast<jlong>(code);
|
|
}
|
|
|
|
static jstring getDlError_native(JNIEnv *env, jobject javaGameActivity) {
|
|
jstring result = env->NewStringUTF(g_error_msg.c_str());
|
|
g_error_msg.clear();
|
|
return result;
|
|
}
|
|
|
|
static void unloadNativeCode_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle) {
|
|
LOG_TRACE("unloadNativeCode_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
delete code;
|
|
}
|
|
}
|
|
|
|
static void onStart_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle) {
|
|
ALOGV("onStart_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onStart != NULL) {
|
|
code->callbacks.onStart(code);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void onResume_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle) {
|
|
LOG_TRACE("onResume_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onResume != NULL) {
|
|
code->callbacks.onResume(code);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SaveInstanceLocals {
|
|
JNIEnv *env;
|
|
jbyteArray array;
|
|
};
|
|
|
|
static jbyteArray onSaveInstanceState_native(JNIEnv *env,
|
|
jobject javaGameActivity,
|
|
jlong handle) {
|
|
LOG_TRACE("onSaveInstanceState_native");
|
|
|
|
SaveInstanceLocals locals{
|
|
env, NULL}; // Passed through the user's state prep function.
|
|
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onSaveInstanceState != NULL) {
|
|
code->callbacks.onSaveInstanceState(
|
|
code,
|
|
[](const char *bytes, int len, void *context) {
|
|
auto locals = static_cast<SaveInstanceLocals *>(context);
|
|
if (len > 0) {
|
|
locals->array = locals->env->NewByteArray(len);
|
|
if (locals->array != NULL) {
|
|
locals->env->SetByteArrayRegion(
|
|
locals->array, 0, len, (const jbyte *)bytes);
|
|
}
|
|
}
|
|
},
|
|
&locals);
|
|
}
|
|
}
|
|
return locals.array;
|
|
}
|
|
|
|
static void onPause_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle) {
|
|
LOG_TRACE("onPause_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onPause != NULL) {
|
|
code->callbacks.onPause(code);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void onStop_native(JNIEnv *env, jobject javaGameActivity, jlong handle) {
|
|
LOG_TRACE("onStop_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onStop != NULL) {
|
|
code->callbacks.onStop(code);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void onConfigurationChanged_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle) {
|
|
LOG_TRACE("onConfigurationChanged_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onConfigurationChanged != NULL) {
|
|
code->callbacks.onConfigurationChanged(code);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void onTrimMemory_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle, jint level) {
|
|
LOG_TRACE("onTrimMemory_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onTrimMemory != NULL) {
|
|
code->callbacks.onTrimMemory(code, level);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void onWindowFocusChanged_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle, jboolean focused) {
|
|
LOG_TRACE("onWindowFocusChanged_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onWindowFocusChanged != NULL) {
|
|
code->callbacks.onWindowFocusChanged(code, focused ? 1 : 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void onSurfaceCreated_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle, jobject surface) {
|
|
ALOGV("onSurfaceCreated_native");
|
|
LOG_TRACE("onSurfaceCreated_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
code->setSurface(surface);
|
|
|
|
if (code->nativeWindow != NULL &&
|
|
code->callbacks.onNativeWindowCreated != NULL) {
|
|
code->callbacks.onNativeWindowCreated(code, code->nativeWindow);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void onSurfaceChanged_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle, jobject surface, jint format,
|
|
jint width, jint height) {
|
|
LOG_TRACE("onSurfaceChanged_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
ANativeWindow *oldNativeWindow = code->nativeWindow;
|
|
// Fix for window being destroyed behind the scenes on older Android
|
|
// versions.
|
|
if (oldNativeWindow != NULL) {
|
|
ANativeWindow_acquire(oldNativeWindow);
|
|
}
|
|
code->setSurface(surface);
|
|
if (oldNativeWindow != code->nativeWindow) {
|
|
if (oldNativeWindow != NULL &&
|
|
code->callbacks.onNativeWindowDestroyed != NULL) {
|
|
code->callbacks.onNativeWindowDestroyed(code, oldNativeWindow);
|
|
}
|
|
if (code->nativeWindow != NULL) {
|
|
if (code->callbacks.onNativeWindowCreated != NULL) {
|
|
code->callbacks.onNativeWindowCreated(code,
|
|
code->nativeWindow);
|
|
}
|
|
|
|
if (width != code->lastWindowWidth ||
|
|
height != code->lastWindowHeight) {
|
|
code->lastWindowWidth = width;
|
|
code->lastWindowHeight = height;
|
|
if (code->callbacks.onNativeWindowResized != NULL) {
|
|
code->callbacks.onNativeWindowResized(
|
|
code, code->nativeWindow, width, height);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Maybe it was resized?
|
|
int32_t newWidth = ANativeWindow_getWidth(code->nativeWindow);
|
|
int32_t newHeight = ANativeWindow_getHeight(code->nativeWindow);
|
|
if (newWidth != code->lastWindowWidth ||
|
|
newHeight != code->lastWindowHeight) {
|
|
code->lastWindowWidth = newWidth;
|
|
code->lastWindowHeight = newHeight;
|
|
if (code->callbacks.onNativeWindowResized != NULL) {
|
|
code->callbacks.onNativeWindowResized(
|
|
code, code->nativeWindow, newWidth, newHeight);
|
|
}
|
|
}
|
|
}
|
|
// Release the window we acquired earlier.
|
|
if (oldNativeWindow != NULL) {
|
|
ANativeWindow_release(oldNativeWindow);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void onSurfaceRedrawNeeded_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle) {
|
|
LOG_TRACE("onSurfaceRedrawNeeded_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->nativeWindow != NULL &&
|
|
code->callbacks.onNativeWindowRedrawNeeded != NULL) {
|
|
code->callbacks.onNativeWindowRedrawNeeded(code,
|
|
code->nativeWindow);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void onSurfaceDestroyed_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle) {
|
|
LOG_TRACE("onSurfaceDestroyed_native");
|
|
if (handle != 0) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->nativeWindow != NULL &&
|
|
code->callbacks.onNativeWindowDestroyed != NULL) {
|
|
code->callbacks.onNativeWindowDestroyed(code, code->nativeWindow);
|
|
}
|
|
code->setSurface(NULL);
|
|
}
|
|
}
|
|
|
|
static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = {
|
|
/* AMOTION_EVENT_AXIS_X */ true,
|
|
/* AMOTION_EVENT_AXIS_Y */ true,
|
|
// Disable all other axes by default (they can be enabled using
|
|
// `GameActivityPointerAxes_enableAxis`).
|
|
false};
|
|
|
|
extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) {
|
|
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
|
return;
|
|
}
|
|
|
|
enabledAxes[axis] = true;
|
|
}
|
|
|
|
extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) {
|
|
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
|
return;
|
|
}
|
|
|
|
enabledAxes[axis] = false;
|
|
}
|
|
|
|
extern "C" void GameActivity_setImeEditorInfo(GameActivity *activity,
|
|
int inputType, int actionId,
|
|
int imeOptions) {
|
|
JNIEnv *env;
|
|
if (activity->vm->AttachCurrentThread(&env, NULL) == JNI_OK) {
|
|
env->CallVoidMethod(activity->javaGameActivity,
|
|
gGameActivityClassInfo.setImeEditorInfoFields,
|
|
inputType, actionId, imeOptions);
|
|
}
|
|
}
|
|
|
|
static struct {
|
|
jmethodID getDeviceId;
|
|
jmethodID getSource;
|
|
jmethodID getAction;
|
|
|
|
jmethodID getEventTime;
|
|
jmethodID getDownTime;
|
|
|
|
jmethodID getFlags;
|
|
jmethodID getMetaState;
|
|
|
|
jmethodID getActionButton;
|
|
jmethodID getButtonState;
|
|
jmethodID getClassification;
|
|
jmethodID getEdgeFlags;
|
|
|
|
jmethodID getPointerCount;
|
|
jmethodID getPointerId;
|
|
jmethodID getRawX;
|
|
jmethodID getRawY;
|
|
jmethodID getXPrecision;
|
|
jmethodID getYPrecision;
|
|
jmethodID getAxisValue;
|
|
} gMotionEventClassInfo;
|
|
|
|
extern "C" void GameActivityMotionEvent_fromJava(
|
|
JNIEnv *env, jobject motionEvent, GameActivityMotionEvent *out_event) {
|
|
static bool gMotionEventClassInfoInitialized = false;
|
|
if (!gMotionEventClassInfoInitialized) {
|
|
int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk");
|
|
gMotionEventClassInfo = {0};
|
|
jclass motionEventClass = env->FindClass("android/view/MotionEvent");
|
|
gMotionEventClassInfo.getDeviceId =
|
|
env->GetMethodID(motionEventClass, "getDeviceId", "()I");
|
|
gMotionEventClassInfo.getSource =
|
|
env->GetMethodID(motionEventClass, "getSource", "()I");
|
|
gMotionEventClassInfo.getAction =
|
|
env->GetMethodID(motionEventClass, "getAction", "()I");
|
|
gMotionEventClassInfo.getEventTime =
|
|
env->GetMethodID(motionEventClass, "getEventTime", "()J");
|
|
gMotionEventClassInfo.getDownTime =
|
|
env->GetMethodID(motionEventClass, "getDownTime", "()J");
|
|
gMotionEventClassInfo.getFlags =
|
|
env->GetMethodID(motionEventClass, "getFlags", "()I");
|
|
gMotionEventClassInfo.getMetaState =
|
|
env->GetMethodID(motionEventClass, "getMetaState", "()I");
|
|
if (sdkVersion >= 23) {
|
|
gMotionEventClassInfo.getActionButton =
|
|
env->GetMethodID(motionEventClass, "getActionButton", "()I");
|
|
}
|
|
if (sdkVersion >= 14) {
|
|
gMotionEventClassInfo.getButtonState =
|
|
env->GetMethodID(motionEventClass, "getButtonState", "()I");
|
|
}
|
|
if (sdkVersion >= 29) {
|
|
gMotionEventClassInfo.getClassification =
|
|
env->GetMethodID(motionEventClass, "getClassification", "()I");
|
|
}
|
|
gMotionEventClassInfo.getEdgeFlags =
|
|
env->GetMethodID(motionEventClass, "getEdgeFlags", "()I");
|
|
gMotionEventClassInfo.getPointerCount =
|
|
env->GetMethodID(motionEventClass, "getPointerCount", "()I");
|
|
gMotionEventClassInfo.getPointerId =
|
|
env->GetMethodID(motionEventClass, "getPointerId", "(I)I");
|
|
if (sdkVersion >= 29) {
|
|
gMotionEventClassInfo.getRawX =
|
|
env->GetMethodID(motionEventClass, "getRawX", "(I)F");
|
|
gMotionEventClassInfo.getRawY =
|
|
env->GetMethodID(motionEventClass, "getRawY", "(I)F");
|
|
}
|
|
gMotionEventClassInfo.getXPrecision =
|
|
env->GetMethodID(motionEventClass, "getXPrecision", "()F");
|
|
gMotionEventClassInfo.getYPrecision =
|
|
env->GetMethodID(motionEventClass, "getYPrecision", "()F");
|
|
gMotionEventClassInfo.getAxisValue =
|
|
env->GetMethodID(motionEventClass, "getAxisValue", "(II)F");
|
|
|
|
gMotionEventClassInfoInitialized = true;
|
|
}
|
|
|
|
int pointerCount =
|
|
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getPointerCount);
|
|
pointerCount =
|
|
std::min(pointerCount, GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT);
|
|
out_event->pointerCount = pointerCount;
|
|
for (int i = 0; i < pointerCount; ++i) {
|
|
out_event->pointers[i] = {
|
|
/*id=*/env->CallIntMethod(motionEvent,
|
|
gMotionEventClassInfo.getPointerId, i),
|
|
/*axisValues=*/{0},
|
|
/*rawX=*/gMotionEventClassInfo.getRawX
|
|
? env->CallFloatMethod(motionEvent,
|
|
gMotionEventClassInfo.getRawX, i)
|
|
: 0,
|
|
/*rawY=*/gMotionEventClassInfo.getRawY
|
|
? env->CallFloatMethod(motionEvent,
|
|
gMotionEventClassInfo.getRawY, i)
|
|
: 0,
|
|
};
|
|
|
|
for (int axisIndex = 0;
|
|
axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; ++axisIndex) {
|
|
if (enabledAxes[axisIndex]) {
|
|
out_event->pointers[i].axisValues[axisIndex] =
|
|
env->CallFloatMethod(motionEvent,
|
|
gMotionEventClassInfo.getAxisValue,
|
|
axisIndex, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
out_event->deviceId =
|
|
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getDeviceId);
|
|
out_event->source =
|
|
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getSource);
|
|
out_event->action =
|
|
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getAction);
|
|
out_event->eventTime =
|
|
env->CallLongMethod(motionEvent, gMotionEventClassInfo.getEventTime) *
|
|
1000000;
|
|
out_event->downTime =
|
|
env->CallLongMethod(motionEvent, gMotionEventClassInfo.getDownTime) *
|
|
1000000;
|
|
out_event->flags =
|
|
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getFlags);
|
|
out_event->metaState =
|
|
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getMetaState);
|
|
out_event->actionButton =
|
|
gMotionEventClassInfo.getActionButton
|
|
? env->CallIntMethod(motionEvent,
|
|
gMotionEventClassInfo.getActionButton)
|
|
: 0;
|
|
out_event->buttonState =
|
|
gMotionEventClassInfo.getButtonState
|
|
? env->CallIntMethod(motionEvent,
|
|
gMotionEventClassInfo.getButtonState)
|
|
: 0;
|
|
out_event->classification =
|
|
gMotionEventClassInfo.getClassification
|
|
? env->CallIntMethod(motionEvent,
|
|
gMotionEventClassInfo.getClassification)
|
|
: 0;
|
|
out_event->edgeFlags =
|
|
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getEdgeFlags);
|
|
out_event->precisionX =
|
|
env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getXPrecision);
|
|
out_event->precisionY =
|
|
env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getYPrecision);
|
|
}
|
|
|
|
static struct {
|
|
jmethodID getDeviceId;
|
|
jmethodID getSource;
|
|
jmethodID getAction;
|
|
|
|
jmethodID getEventTime;
|
|
jmethodID getDownTime;
|
|
|
|
jmethodID getFlags;
|
|
jmethodID getMetaState;
|
|
|
|
jmethodID getModifiers;
|
|
jmethodID getRepeatCount;
|
|
jmethodID getKeyCode;
|
|
} gKeyEventClassInfo;
|
|
|
|
extern "C" void GameActivityKeyEvent_fromJava(JNIEnv *env, jobject keyEvent,
|
|
GameActivityKeyEvent *out_event) {
|
|
static bool gKeyEventClassInfoInitialized = false;
|
|
if (!gKeyEventClassInfoInitialized) {
|
|
int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk");
|
|
gKeyEventClassInfo = {0};
|
|
jclass keyEventClass = env->FindClass("android/view/KeyEvent");
|
|
gKeyEventClassInfo.getDeviceId =
|
|
env->GetMethodID(keyEventClass, "getDeviceId", "()I");
|
|
gKeyEventClassInfo.getSource =
|
|
env->GetMethodID(keyEventClass, "getSource", "()I");
|
|
gKeyEventClassInfo.getAction =
|
|
env->GetMethodID(keyEventClass, "getAction", "()I");
|
|
gKeyEventClassInfo.getEventTime =
|
|
env->GetMethodID(keyEventClass, "getEventTime", "()J");
|
|
gKeyEventClassInfo.getDownTime =
|
|
env->GetMethodID(keyEventClass, "getDownTime", "()J");
|
|
gKeyEventClassInfo.getFlags =
|
|
env->GetMethodID(keyEventClass, "getFlags", "()I");
|
|
gKeyEventClassInfo.getMetaState =
|
|
env->GetMethodID(keyEventClass, "getMetaState", "()I");
|
|
if (sdkVersion >= 13) {
|
|
gKeyEventClassInfo.getModifiers =
|
|
env->GetMethodID(keyEventClass, "getModifiers", "()I");
|
|
}
|
|
gKeyEventClassInfo.getRepeatCount =
|
|
env->GetMethodID(keyEventClass, "getRepeatCount", "()I");
|
|
gKeyEventClassInfo.getKeyCode =
|
|
env->GetMethodID(keyEventClass, "getKeyCode", "()I");
|
|
|
|
gKeyEventClassInfoInitialized = true;
|
|
}
|
|
|
|
*out_event = {
|
|
/*deviceId=*/env->CallIntMethod(keyEvent,
|
|
gKeyEventClassInfo.getDeviceId),
|
|
/*source=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getSource),
|
|
/*action=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getAction),
|
|
// TODO: introduce a millisecondsToNanoseconds helper:
|
|
/*eventTime=*/
|
|
env->CallLongMethod(keyEvent, gKeyEventClassInfo.getEventTime) *
|
|
1000000,
|
|
/*downTime=*/
|
|
env->CallLongMethod(keyEvent, gKeyEventClassInfo.getDownTime) * 1000000,
|
|
/*flags=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getFlags),
|
|
/*metaState=*/
|
|
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getMetaState),
|
|
/*modifiers=*/gKeyEventClassInfo.getModifiers
|
|
? env->CallIntMethod(keyEvent, gKeyEventClassInfo.getModifiers)
|
|
: 0,
|
|
/*repeatCount=*/
|
|
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getRepeatCount),
|
|
/*keyCode=*/
|
|
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getKeyCode)};
|
|
}
|
|
|
|
static bool onTouchEvent_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle, jobject motionEvent) {
|
|
if (handle == 0) return false;
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onTouchEvent == nullptr) return false;
|
|
|
|
static GameActivityMotionEvent c_event;
|
|
GameActivityMotionEvent_fromJava(env, motionEvent, &c_event);
|
|
return code->callbacks.onTouchEvent(code, &c_event);
|
|
}
|
|
|
|
static bool onKeyUp_native(JNIEnv *env, jobject javaGameActivity, jlong handle,
|
|
jobject keyEvent) {
|
|
if (handle == 0) return false;
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onKeyUp == nullptr) return false;
|
|
|
|
static GameActivityKeyEvent c_event;
|
|
GameActivityKeyEvent_fromJava(env, keyEvent, &c_event);
|
|
return code->callbacks.onKeyUp(code, &c_event);
|
|
}
|
|
|
|
static bool onKeyDown_native(JNIEnv *env, jobject javaGameActivity,
|
|
jlong handle, jobject keyEvent) {
|
|
if (handle == 0) return false;
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onKeyDown == nullptr) return false;
|
|
|
|
static GameActivityKeyEvent c_event;
|
|
GameActivityKeyEvent_fromJava(env, keyEvent, &c_event);
|
|
return code->callbacks.onKeyDown(code, &c_event);
|
|
}
|
|
|
|
static void onTextInput_native(JNIEnv *env, jobject activity, jlong handle,
|
|
jobject textInputEvent) {
|
|
if (handle == 0) return;
|
|
NativeCode *code = (NativeCode *)handle;
|
|
GameTextInput_processEvent(code->gameTextInput, textInputEvent);
|
|
}
|
|
|
|
static void onWindowInsetsChanged_native(JNIEnv *env, jobject activity,
|
|
jlong handle) {
|
|
if (handle == 0) return;
|
|
NativeCode *code = (NativeCode *)handle;
|
|
if (code->callbacks.onWindowInsetsChanged == nullptr) return;
|
|
for (int type = 0; type < GAMECOMMON_INSETS_TYPE_COUNT; ++type) {
|
|
jobject jinsets;
|
|
// Note that waterfall insets are handled differently on the Java side.
|
|
if (type == GAMECOMMON_INSETS_TYPE_WATERFALL) {
|
|
jinsets = env->CallObjectMethod(
|
|
code->javaGameActivity,
|
|
gGameActivityClassInfo.getWaterfallInsets);
|
|
} else {
|
|
jint jtype = env->CallStaticIntMethod(
|
|
gWindowInsetsCompatTypeClassInfo.clazz,
|
|
gWindowInsetsCompatTypeClassInfo.methods[type]);
|
|
jinsets = env->CallObjectMethod(
|
|
code->javaGameActivity, gGameActivityClassInfo.getWindowInsets,
|
|
jtype);
|
|
}
|
|
ARect &insets = code->insetsState[type];
|
|
if (jinsets == nullptr) {
|
|
insets.left = 0;
|
|
insets.right = 0;
|
|
insets.top = 0;
|
|
insets.bottom = 0;
|
|
} else {
|
|
insets.left = env->GetIntField(jinsets, gInsetsClassInfo.left);
|
|
insets.right = env->GetIntField(jinsets, gInsetsClassInfo.right);
|
|
insets.top = env->GetIntField(jinsets, gInsetsClassInfo.top);
|
|
insets.bottom = env->GetIntField(jinsets, gInsetsClassInfo.bottom);
|
|
}
|
|
}
|
|
GameTextInput_processImeInsets(
|
|
code->gameTextInput, &code->insetsState[GAMECOMMON_INSETS_TYPE_IME]);
|
|
code->callbacks.onWindowInsetsChanged(code);
|
|
}
|
|
|
|
static void setInputConnection_native(JNIEnv *env, jobject activity,
|
|
jlong handle, jobject inputConnection) {
|
|
NativeCode *code = (NativeCode *)handle;
|
|
GameTextInput_setInputConnection(code->gameTextInput, inputConnection);
|
|
}
|
|
|
|
static const JNINativeMethod g_methods[] = {
|
|
{"loadNativeCode",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
|
|
"String;Ljava/lang/String;Landroid/content/res/AssetManager;[B)J",
|
|
(void *)loadNativeCode_native},
|
|
{"getDlError", "()Ljava/lang/String;", (void *)getDlError_native},
|
|
{"unloadNativeCode", "(J)V", (void *)unloadNativeCode_native},
|
|
{"onStartNative", "(J)V", (void *)onStart_native},
|
|
{"onResumeNative", "(J)V", (void *)onResume_native},
|
|
{"onSaveInstanceStateNative", "(J)[B", (void *)onSaveInstanceState_native},
|
|
{"onPauseNative", "(J)V", (void *)onPause_native},
|
|
{"onStopNative", "(J)V", (void *)onStop_native},
|
|
{"onConfigurationChangedNative", "(J)V",
|
|
(void *)onConfigurationChanged_native},
|
|
{"onTrimMemoryNative", "(JI)V", (void *)onTrimMemory_native},
|
|
{"onWindowFocusChangedNative", "(JZ)V",
|
|
(void *)onWindowFocusChanged_native},
|
|
{"onSurfaceCreatedNative", "(JLandroid/view/Surface;)V",
|
|
(void *)onSurfaceCreated_native},
|
|
{"onSurfaceChangedNative", "(JLandroid/view/Surface;III)V",
|
|
(void *)onSurfaceChanged_native},
|
|
{"onSurfaceRedrawNeededNative", "(JLandroid/view/Surface;)V",
|
|
(void *)onSurfaceRedrawNeeded_native},
|
|
{"onSurfaceDestroyedNative", "(J)V", (void *)onSurfaceDestroyed_native},
|
|
{"onTouchEventNative", "(JLandroid/view/MotionEvent;)Z",
|
|
(void *)onTouchEvent_native},
|
|
{"onKeyDownNative", "(JLandroid/view/KeyEvent;)Z",
|
|
(void *)onKeyDown_native},
|
|
{"onKeyUpNative", "(JLandroid/view/KeyEvent;)Z", (void *)onKeyUp_native},
|
|
{"onTextInputEventNative",
|
|
"(JLcom/google/androidgamesdk/gametextinput/State;)V",
|
|
(void *)onTextInput_native},
|
|
{"onWindowInsetsChangedNative", "(J)V",
|
|
(void *)onWindowInsetsChanged_native},
|
|
{"setInputConnectionNative",
|
|
"(JLcom/google/androidgamesdk/gametextinput/InputConnection;)V",
|
|
(void *)setInputConnection_native},
|
|
};
|
|
|
|
static const char *const kGameActivityPathName =
|
|
"com/google/androidgamesdk/GameActivity";
|
|
|
|
static const char *const kInsetsPathName = "androidx/core/graphics/Insets";
|
|
|
|
static const char *const kWindowInsetsCompatTypePathName =
|
|
"androidx/core/view/WindowInsetsCompat$Type";
|
|
|
|
#define FIND_CLASS(var, className) \
|
|
var = env->FindClass(className); \
|
|
LOG_FATAL_IF(!var, "Unable to find class %s", className);
|
|
|
|
#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
|
|
var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
|
|
LOG_FATAL_IF(!var, "Unable to find method %s", methodName);
|
|
|
|
#define GET_STATIC_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
|
|
var = env->GetStaticMethodID(clazz, methodName, fieldDescriptor); \
|
|
LOG_FATAL_IF(!var, "Unable to find static method %s", methodName);
|
|
|
|
#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
|
|
var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
|
|
LOG_FATAL_IF(!var, "Unable to find field %s", fieldName);
|
|
|
|
static int jniRegisterNativeMethods(JNIEnv *env, const char *className,
|
|
const JNINativeMethod *methods,
|
|
int numMethods) {
|
|
ALOGV("Registering %s's %d native methods...", className, numMethods);
|
|
jclass clazz = env->FindClass(className);
|
|
LOG_FATAL_IF(clazz == nullptr,
|
|
"Native registration unable to find class '%s'; aborting...",
|
|
className);
|
|
int result = env->RegisterNatives(clazz, methods, numMethods);
|
|
env->DeleteLocalRef(clazz);
|
|
if (result == 0) {
|
|
return 0;
|
|
}
|
|
|
|
// Failure to register natives is fatal. Try to report the corresponding
|
|
// exception, otherwise abort with generic failure message.
|
|
jthrowable thrown = env->ExceptionOccurred();
|
|
if (thrown != NULL) {
|
|
env->ExceptionDescribe();
|
|
env->DeleteLocalRef(thrown);
|
|
}
|
|
LOG_FATAL("RegisterNatives failed for '%s'; aborting...", className);
|
|
}
|
|
|
|
extern "C" int GameActivity_register(JNIEnv *env) {
|
|
ALOGD("GameActivity_register");
|
|
jclass activity_class;
|
|
FIND_CLASS(activity_class, kGameActivityPathName);
|
|
GET_METHOD_ID(gGameActivityClassInfo.finish, activity_class, "finish",
|
|
"()V");
|
|
GET_METHOD_ID(gGameActivityClassInfo.setWindowFlags, activity_class,
|
|
"setWindowFlags", "(II)V");
|
|
GET_METHOD_ID(gGameActivityClassInfo.getWindowInsets, activity_class,
|
|
"getWindowInsets", "(I)Landroidx/core/graphics/Insets;");
|
|
GET_METHOD_ID(gGameActivityClassInfo.getWaterfallInsets, activity_class,
|
|
"getWaterfallInsets", "()Landroidx/core/graphics/Insets;");
|
|
GET_METHOD_ID(gGameActivityClassInfo.setImeEditorInfoFields, activity_class,
|
|
"setImeEditorInfoFields", "(III)V");
|
|
jclass insets_class;
|
|
FIND_CLASS(insets_class, kInsetsPathName);
|
|
GET_FIELD_ID(gInsetsClassInfo.left, insets_class, "left", "I");
|
|
GET_FIELD_ID(gInsetsClassInfo.right, insets_class, "right", "I");
|
|
GET_FIELD_ID(gInsetsClassInfo.top, insets_class, "top", "I");
|
|
GET_FIELD_ID(gInsetsClassInfo.bottom, insets_class, "bottom", "I");
|
|
jclass windowInsetsCompatType_class;
|
|
FIND_CLASS(windowInsetsCompatType_class, kWindowInsetsCompatTypePathName);
|
|
gWindowInsetsCompatTypeClassInfo.clazz =
|
|
(jclass)env->NewGlobalRef(windowInsetsCompatType_class);
|
|
// These names must match, in order, the GameCommonInsetsType enum fields
|
|
// Note that waterfall is handled differently by the insets API, so we
|
|
// exclude it here.
|
|
const char *methodNames[GAMECOMMON_INSETS_TYPE_WATERFALL] = {
|
|
"captionBar",
|
|
"displayCutout",
|
|
"ime",
|
|
"mandatorySystemGestures",
|
|
"navigationBars",
|
|
"statusBars",
|
|
"systemBars",
|
|
"systemGestures",
|
|
"tappableElement"};
|
|
for (int i = 0; i < GAMECOMMON_INSETS_TYPE_WATERFALL; ++i) {
|
|
GET_STATIC_METHOD_ID(gWindowInsetsCompatTypeClassInfo.methods[i],
|
|
windowInsetsCompatType_class, methodNames[i],
|
|
"()I");
|
|
}
|
|
return jniRegisterNativeMethods(env, kGameActivityPathName, g_methods,
|
|
NELEM(g_methods));
|
|
}
|
|
|
|
// Register this method so that GameActiviy_register does not need to be called
|
|
// manually.
|
|
extern "C" JNIEXPORT jlong JNICALL Java_com_google_androidgamesdk_GameActivity_loadNativeCode(
|
|
JNIEnv *env, jobject javaGameActivity, jstring path, jstring funcName,
|
|
jstring internalDataDir, jstring obbDir, jstring externalDataDir,
|
|
jobject jAssetMgr, jbyteArray savedState) {
|
|
GameActivity_register(env);
|
|
jlong nativeCode = loadNativeCode_native(
|
|
env, javaGameActivity, path, funcName, internalDataDir, obbDir,
|
|
externalDataDir, jAssetMgr, savedState);
|
|
return nativeCode;
|
|
}
|