diff --git a/android-activity/game-activity-csrc/.clang-format b/android-activity/game-activity-csrc/.clang-format new file mode 100644 index 0000000..208f7d8 --- /dev/null +++ b/android-activity/game-activity-csrc/.clang-format @@ -0,0 +1,4 @@ +# Use the Google style in this project. +BasedOnStyle: Google + +DerivePointerAlignment: true diff --git a/android-activity/game-activity-csrc/common/gamesdk_common.h b/android-activity/game-activity-csrc/common/gamesdk_common.h index d29ac01..f8920a9 100644 --- a/android-activity/game-activity-csrc/common/gamesdk_common.h +++ b/android-activity/game-activity-csrc/common/gamesdk_common.h @@ -31,11 +31,11 @@ // There are separate versions for each GameSDK component that use this format: #define ANDROID_GAMESDK_PACKED_VERSION(MAJOR, MINOR, BUGFIX) \ - ((MAJOR << 16) | (MINOR << 8) | (BUGFIX)) + ((MAJOR << 16) | (MINOR << 8) | (BUGFIX)) // Accessors #define ANDROID_GAMESDK_MAJOR_VERSION(PACKED) ((PACKED) >> 16) #define ANDROID_GAMESDK_MINOR_VERSION(PACKED) (((PACKED) >> 8) & 0xff) #define ANDROID_GAMESDK_BUGFIX_VERSION(PACKED) ((PACKED) & 0xff) #define AGDK_STRING_VERSION(MAJOR, MINOR, BUGFIX, GIT) \ -#MAJOR "." #MINOR "." #BUGFIX "." #GIT + #MAJOR "." #MINOR "." #BUGFIX "." #GIT diff --git a/android-activity/game-activity-csrc/game-activity/GameActivity.cpp b/android-activity/game-activity-csrc/game-activity/GameActivity.cpp index 4dbc9e8..a178793 100644 --- a/android-activity/game-activity-csrc/game-activity/GameActivity.cpp +++ b/android-activity/game-activity-csrc/game-activity/GameActivity.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "GameActivity.h" - #include #include #include @@ -26,31 +24,35 @@ #include #include #include +#include +#include #include #include #include #include #include #include +#include #include #include #include #include +#include -#include "GameActivityLog.h" +#include "GameActivityEvents_internal.h" namespace { 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; + 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 @@ -63,53 +65,73 @@ struct OwnedGameTextInputState { * JNI methods of the GameActivity Java class. */ static struct { - jmethodID finish; - jmethodID setWindowFlags; - jmethodID getWindowInsets; - jmethodID getWaterfallInsets; - jmethodID setImeEditorInfoFields; + jmethodID finish; + jmethodID setWindowFlags; + jmethodID getWindowInsets; + jmethodID getWaterfallInsets; + jmethodID setImeEditorInfoFields; } gGameActivityClassInfo; +/* + * JNI methods of the android.os.LocaleList class. + */ +static struct { + jmethodID size; + jmethodID get; +} gLocaleListClassInfo; + +/* + * JNI methods of the java.util.Locale class. + */ +static struct { + jmethodID getLanguage; + jmethodID getScript; + jmethodID getCountry; + jmethodID getVariant; +} gLocaleClassInfo; + /* * JNI fields of the androidx.core.graphics.Insets Java class. */ static struct { - jfieldID left; - jfieldID right; - jfieldID top; - jfieldID bottom; + jfieldID left; + jfieldID right; + jfieldID top; + jfieldID bottom; } gInsetsClassInfo; /* * JNI fields of the Configuration Java class. */ static struct ConfigurationClassInfo { - jfieldID colorMode; - jfieldID densityDpi; - jfieldID fontScale; - jfieldID fontWeightAdjustment; - jfieldID hardKeyboardHidden; - jfieldID keyboard; - jfieldID keyboardHidden; - jfieldID mcc; - jfieldID mnc; - jfieldID navigation; - jfieldID navigationHidden; - jfieldID orientation; - jfieldID screenHeightDp; - jfieldID screenLayout; - jfieldID screenWidthDp; - jfieldID smallestScreenWidthDp; - jfieldID touchscreen; - jfieldID uiMode; + jfieldID colorMode; + jfieldID densityDpi; + jfieldID fontScale; + jfieldID fontWeightAdjustment; + jfieldID hardKeyboardHidden; + jfieldID keyboard; + jfieldID keyboardHidden; + jfieldID mcc; + jfieldID mnc; + jfieldID navigation; + jfieldID navigationHidden; + jfieldID orientation; + jfieldID screenHeightDp; + jfieldID screenLayout; + jfieldID screenWidthDp; + jfieldID smallestScreenWidthDp; + jfieldID touchscreen; + jfieldID uiMode; + + jmethodID getLocales; } gConfigurationClassInfo; /* * JNI methods of the WindowInsetsCompat.Type Java class. */ static struct { - jmethodID methods[GAMECOMMON_INSETS_TYPE_COUNT]; - jclass clazz; + jmethodID methods[GAMECOMMON_INSETS_TYPE_COUNT]; + jclass clazz; } gWindowInsetsCompatTypeClassInfo; /* @@ -117,10 +139,10 @@ static struct { * on the application main thread. */ struct ActivityWork { - int32_t cmd; - int64_t arg1; - int64_t arg2; - int64_t arg3; + int32_t cmd; + int64_t arg1; + int64_t arg2; + int64_t arg3; }; /* @@ -128,13 +150,24 @@ struct ActivityWork { * 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, - CMD_SET_IME_EDITOR_INFO + CMD_FINISH = 1, + CMD_SET_WINDOW_FORMAT, + CMD_SET_WINDOW_FLAGS, + CMD_SHOW_SOFT_INPUT, + CMD_HIDE_SOFT_INPUT, + CMD_SET_SOFT_INPUT_STATE, + CMD_SET_IME_EDITOR_INFO, + CMD_RESTART_INPUT +}; + +/* + * A class for locale information, matching Android Java Locale class. + */ +struct Locale { + std::string language; + std::string script; + std::string country; + std::string variant; }; /* @@ -142,52 +175,55 @@ enum { * thread, this is why they are made atomic. */ static struct Configuration { - std::atomic_int colorMode; - std::atomic_int densityDpi; - std::atomic fontScale; - std::atomic_int fontWeightAdjustment; - std::atomic_int hardKeyboardHidden; - std::atomic_int keyboard; - std::atomic_int keyboardHidden; - std::atomic_int mcc; - std::atomic_int mnc; - std::atomic_int navigation; - std::atomic_int navigationHidden; - std::atomic_int orientation; - std::atomic_int screenHeightDp; - std::atomic_int screenLayout; - std::atomic_int screenWidthDp; - std::atomic_int smallestScreenWidthDp; - std::atomic_int touchscreen; - std::atomic_int uiMode; + int colorMode; + int densityDpi; + float fontScale; + int fontWeightAdjustment; + int hardKeyboardHidden; + int keyboard; + int keyboardHidden; + int mcc; + int mnc; + int navigation; + int navigationHidden; + int orientation; + int screenHeightDp; + int screenLayout; + int screenWidthDp; + int smallestScreenWidthDp; + int touchscreen; + int uiMode; + std::vector locales; } gConfiguration; +static std::mutex gConfigMutex; + /* * 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, int64_t arg3 = 0) { - ActivityWork work; - work.cmd = cmd; - work.arg1 = arg1; - work.arg2 = arg2; - work.arg3 = arg3; + ActivityWork work; + work.cmd = cmd; + work.arg1 = arg1; + work.arg2 = arg2; + work.arg3 = arg3; - LOG_TRACE("write_work: cmd=%d", cmd); + 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); - } + 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); + } } /* @@ -195,211 +231,228 @@ static void write_work(int fd, int32_t cmd, int64_t arg1 = 0, int64_t arg2 = 0, * 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; + 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() { - memset((GameActivity *)this, 0, sizeof(GameActivity)); - memset(&callbacks, 0, sizeof(callbacks)); - memset(&insetsState, 0, sizeof(insetsState)); - nativeWindow = NULL; - mainWorkRead = mainWorkWrite = -1; - gameTextInput = NULL; + NativeCode() { + memset((GameActivity *)this, 0, sizeof(GameActivity)); + memset(&callbacks, 0, sizeof(callbacks)); + memset(&insetsState, 0, sizeof(insetsState)); + nativeWindow = NULL; + mainWorkRead = mainWorkWrite = -1; + gameTextInput = NULL; + softwareKeyboardVisible = false; + sdkVersion = gamesdk::GetSystemPropAsInt("ro.build.version.sdk"); + ALOGD("SDK version: %d", sdkVersion); + } + + ~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; - ~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); + } - setSurface(NULL); - if (mainWorkRead >= 0) close(mainWorkRead); - if (mainWorkWrite >= 0) close(mainWorkWrite); + void setSurface(jobject _surface) { + if (nativeWindow != NULL) { + ANativeWindow_release(nativeWindow); } - - void setSurface(jobject _surface) { - if (nativeWindow != NULL) { - ANativeWindow_release(nativeWindow); - } - if (_surface != NULL) { - nativeWindow = ANativeWindow_fromSurface(env, _surface); - } else { - nativeWindow = NULL; - } + if (_surface != NULL) { + nativeWindow = ANativeWindow_fromSurface(env, _surface); + } else { + nativeWindow = NULL; } + } - GameActivityCallbacks callbacks; + GameActivityCallbacks callbacks; - std::string internalDataPathObj; - std::string externalDataPathObj; - std::string obbPathObj; + std::string internalDataPathObj; + std::string externalDataPathObj; + std::string obbPathObj; - ANativeWindow *nativeWindow; - int32_t lastWindowWidth; - int32_t lastWindowHeight; + 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; + // 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; + // 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; + GameTextInput *gameTextInput; + // Set by users in GameActivity_setTextInputState, then passed to + // GameTextInput. + OwnedGameTextInputState gameTextInputState; + std::mutex gameTextInputStateMutex; - ARect insetsState[GAMECOMMON_INSETS_TYPE_COUNT]; + ARect insetsState[GAMECOMMON_INSETS_TYPE_COUNT]; + bool softwareKeyboardVisible; }; static void readConfigurationValues(NativeCode *code, jobject javaConfig); extern "C" void GameActivity_finish(GameActivity *activity) { - NativeCode *code = static_cast(activity); - write_work(code->mainWorkWrite, CMD_FINISH, 0); + NativeCode *code = static_cast(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(activity); - write_work(code->mainWorkWrite, CMD_SET_WINDOW_FLAGS, values, mask); + NativeCode *code = static_cast(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(activity); - write_work(code->mainWorkWrite, CMD_SHOW_SOFT_INPUT, flags); + NativeCode *code = static_cast(activity); + write_work(code->mainWorkWrite, CMD_SHOW_SOFT_INPUT, flags); +} + +extern "C" void GameActivity_restartInput(GameActivity *activity) { + NativeCode *code = static_cast(activity); + write_work(code->mainWorkWrite, CMD_RESTART_INPUT); } extern "C" void GameActivity_setTextInputState( GameActivity *activity, const GameTextInputState *state) { - NativeCode *code = static_cast(activity); - std::lock_guard lock(code->gameTextInputStateMutex); - code->gameTextInputState = *state; - write_work(code->mainWorkWrite, CMD_SET_SOFT_INPUT_STATE); + NativeCode *code = static_cast(activity); + std::lock_guard 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(activity); - return GameTextInput_getState(code->gameTextInput, callback, context); + NativeCode *code = static_cast(activity); + return GameTextInput_getState(code->gameTextInput, callback, context); } extern "C" void GameActivity_hideSoftInput(GameActivity *activity, uint32_t flags) { - NativeCode *code = static_cast(activity); - write_work(code->mainWorkWrite, CMD_HIDE_SOFT_INPUT, flags); + NativeCode *code = static_cast(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(activity); - *insets = code->insetsState[type]; + if (type < 0 || type >= GAMECOMMON_INSETS_TYPE_COUNT) return; + NativeCode *code = static_cast(activity); + *insets = code->insetsState[type]; +} + +extern "C" bool GameActivity_isSoftwareKeyboardVisible(GameActivity *activity) { + NativeCode *code = static_cast(activity); + return code->softwareKeyboardVisible; } extern "C" GameTextInput *GameActivity_getTextInput( const GameActivity *activity) { - const NativeCode *code = static_cast(activity); - return code->gameTextInput; + const NativeCode *code = static_cast(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(); - } + 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 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; - case CMD_SET_IME_EDITOR_INFO: { - code->env->CallVoidMethod( - code->javaGameActivity, - gGameActivityClassInfo.setImeEditorInfoFields, work.arg1, - work.arg2, work.arg3); - checkAndClearException(code->env, "setImeEditorInfo"); - } break; - default: - ALOGW("Unknown work command: %d", work.cmd); - break; - } + 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, + static_cast(work.arg1), static_cast(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 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; + case CMD_SET_IME_EDITOR_INFO: { + code->env->CallVoidMethod( + code->javaGameActivity, gGameActivityClassInfo.setImeEditorInfoFields, + static_cast(work.arg1), static_cast(work.arg2), + static_cast(work.arg3)); + checkAndClearException(code->env, "setImeEditorInfo"); + } break; + case CMD_RESTART_INPUT: { + GameTextInput_restartInput(code->gameTextInput); + } break; + default: + ALOGW("Unknown work command: %d", work.cmd); + break; + } + + return 1; } // ------------------------------------------------------------------------ @@ -409,541 +462,714 @@ static jlong initializeNativeCode_native( JNIEnv *env, jobject javaGameActivity, jstring internalDataDir, jstring obbDir, jstring externalDataDir, jobject jAssetMgr, jbyteArray savedState, jobject javaConfig) { - LOG_TRACE("initializeNativeCode_native"); - NativeCode *code = NULL; - - code = new NativeCode(); - - 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); - } - - readConfigurationValues(code, javaConfig); - - GameActivity_onCreate_C(code, rawSavedState, rawSavedSize); - - code->gameTextInput = GameTextInput_init(env, 0); - GameTextInput_setEventCallback(code->gameTextInput, - reinterpret_cast( - code->callbacks.onTextInputEvent), - code); - - if (rawSavedState != NULL) { - env->ReleaseByteArrayElements(savedState, rawSavedState, 0); - } - - return reinterpret_cast(code); + LOG_TRACE("initializeNativeCode_native"); + NativeCode *code = NULL; + + code = new NativeCode(); + + 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); + } + + // read configuration for the first time + readConfigurationValues(code, javaConfig); + + GameActivity_onCreate_C(code, rawSavedState, rawSavedSize); + + code->gameTextInput = GameTextInput_init(env, 0); + GameTextInput_setEventCallback(code->gameTextInput, + reinterpret_cast( + code->callbacks.onTextInputEvent), + code); + + if (rawSavedState != NULL) { + env->ReleaseByteArrayElements(savedState, rawSavedState, 0); + } + + return reinterpret_cast(code); } static jstring getDlError_native(JNIEnv *env, jobject javaGameActivity) { - jstring result = env->NewStringUTF(g_error_msg.c_str()); - g_error_msg.clear(); - return result; + jstring result = env->NewStringUTF(g_error_msg.c_str()); + g_error_msg.clear(); + return result; } static void terminateNativeCode_native(JNIEnv *env, jobject javaGameActivity, - jlong handle) { - LOG_TRACE("terminateNativeCode_native"); - if (handle != 0) { - NativeCode *code = (NativeCode *)handle; - delete code; - } + jlong handle) { + LOG_TRACE("terminateNativeCode_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); - } + 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); - } + 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; + 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(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); - } + 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(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; + } + 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); - } + 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); - } + LOG_TRACE("onStop_native"); + if (handle != 0) { + NativeCode *code = (NativeCode *)handle; + if (code->callbacks.onStop != NULL) { + code->callbacks.onStop(code); } + } } -static void readConfigurationValues(NativeCode *code, jobject javaConfig) { - if (gConfigurationClassInfo.colorMode != NULL) { - gConfiguration.colorMode = code->env->GetIntField( - javaConfig, gConfigurationClassInfo.colorMode); - } - - gConfiguration.densityDpi = - code->env->GetIntField(javaConfig, gConfigurationClassInfo.densityDpi); - gConfiguration.fontScale = - code->env->GetFloatField(javaConfig, gConfigurationClassInfo.fontScale); - - if (gConfigurationClassInfo.fontWeightAdjustment != NULL) { - gConfiguration.fontWeightAdjustment = code->env->GetIntField( - javaConfig, gConfigurationClassInfo.fontWeightAdjustment); - } +static std::string getStringField(JNIEnv *env, jobject obj, jmethodID method) { + jstring str = (jstring)env->CallObjectMethod(obj, method); + const char *chars = env->GetStringUTFChars(str, NULL); + std::string res(chars); + env->ReleaseStringUTFChars(str, chars); + checkAndClearException(env, "getStringField"); + return res; +} - gConfiguration.hardKeyboardHidden = code->env->GetIntField( - javaConfig, gConfigurationClassInfo.hardKeyboardHidden); - gConfiguration.mcc = - code->env->GetIntField(javaConfig, gConfigurationClassInfo.mcc); - gConfiguration.mnc = - code->env->GetIntField(javaConfig, gConfigurationClassInfo.mnc); - gConfiguration.navigation = - code->env->GetIntField(javaConfig, gConfigurationClassInfo.navigation); - gConfiguration.navigationHidden = code->env->GetIntField( - javaConfig, gConfigurationClassInfo.navigationHidden); - gConfiguration.orientation = - code->env->GetIntField(javaConfig, gConfigurationClassInfo.orientation); - gConfiguration.screenHeightDp = code->env->GetIntField( - javaConfig, gConfigurationClassInfo.screenHeightDp); - gConfiguration.screenLayout = code->env->GetIntField( - javaConfig, gConfigurationClassInfo.screenLayout); - gConfiguration.screenWidthDp = code->env->GetIntField( - javaConfig, gConfigurationClassInfo.screenWidthDp); - gConfiguration.smallestScreenWidthDp = code->env->GetIntField( - javaConfig, gConfigurationClassInfo.smallestScreenWidthDp); - gConfiguration.touchscreen = - code->env->GetIntField(javaConfig, gConfigurationClassInfo.touchscreen); - gConfiguration.uiMode = - code->env->GetIntField(javaConfig, gConfigurationClassInfo.uiMode); - - checkAndClearException(code->env, "Configuration.get"); +static void readConfigurationValues(NativeCode *code, jobject javaConfig) { + const std::lock_guard lock(gConfigMutex); + + if (gConfigurationClassInfo.colorMode != NULL) { + gConfiguration.colorMode = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.colorMode); + } + + gConfiguration.densityDpi = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.densityDpi); + gConfiguration.fontScale = + code->env->GetFloatField(javaConfig, gConfigurationClassInfo.fontScale); + + if (gConfigurationClassInfo.fontWeightAdjustment != NULL) { + gConfiguration.fontWeightAdjustment = code->env->GetIntField( + javaConfig, gConfigurationClassInfo.fontWeightAdjustment); + } + + gConfiguration.hardKeyboardHidden = code->env->GetIntField( + javaConfig, gConfigurationClassInfo.hardKeyboardHidden); + gConfiguration.keyboard = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.keyboard); + gConfiguration.keyboardHidden = code->env->GetIntField( + javaConfig, gConfigurationClassInfo.keyboardHidden); + gConfiguration.mcc = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.mcc); + gConfiguration.mnc = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.mnc); + gConfiguration.navigation = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.navigation); + gConfiguration.navigationHidden = code->env->GetIntField( + javaConfig, gConfigurationClassInfo.navigationHidden); + gConfiguration.orientation = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.orientation); + gConfiguration.screenHeightDp = code->env->GetIntField( + javaConfig, gConfigurationClassInfo.screenHeightDp); + gConfiguration.screenLayout = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.screenLayout); + gConfiguration.screenWidthDp = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.screenWidthDp); + gConfiguration.smallestScreenWidthDp = code->env->GetIntField( + javaConfig, gConfigurationClassInfo.smallestScreenWidthDp); + gConfiguration.touchscreen = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.touchscreen); + gConfiguration.uiMode = + code->env->GetIntField(javaConfig, gConfigurationClassInfo.uiMode); + + checkAndClearException(code->env, "Configuration.get"); + + jobject locales = code->env->CallObjectMethod( + javaConfig, gConfigurationClassInfo.getLocales); + checkAndClearException(code->env, "getLocales"); + + int localesCount = + code->env->CallIntMethod(locales, gLocaleListClassInfo.size); + checkAndClearException(code->env, "size"); + gConfiguration.locales.resize(localesCount); + + // extract the data for every locale + for (int i = 0; i < localesCount; ++i) { + Locale &locale = gConfiguration.locales[i]; + + // get locale object from the array + jobject jniLocale = + code->env->CallObjectMethod(locales, gLocaleListClassInfo.get, i); + checkAndClearException(code->env, "GetObjectArrayElement"); + + // get data strings for this locale + locale.language = + getStringField(code->env, jniLocale, gLocaleClassInfo.getLanguage); + locale.script = + getStringField(code->env, jniLocale, gLocaleClassInfo.getScript); + locale.country = + getStringField(code->env, jniLocale, gLocaleClassInfo.getCountry); + locale.variant = + getStringField(code->env, jniLocale, gLocaleClassInfo.getVariant); + } } static void onConfigurationChanged_native(JNIEnv *env, jobject javaGameActivity, jlong handle, jobject javaNewConfig) { - LOG_TRACE("onConfigurationChanged_native"); - if (handle != 0) { - NativeCode *code = (NativeCode *)handle; - readConfigurationValues(code, javaNewConfig); + LOG_TRACE("onConfigurationChanged_native"); + if (handle != 0) { + NativeCode *code = (NativeCode *)handle; + readConfigurationValues(code, javaNewConfig); - if (code->callbacks.onConfigurationChanged != NULL) { - code->callbacks.onConfigurationChanged(code); - } + 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); - } + 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); - } + 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); - } + 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); - } - - code->lastWindowWidth = - ANativeWindow_getWidth(code->nativeWindow); - code->lastWindowHeight = - ANativeWindow_getHeight(code->nativeWindow); - } - } 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); - } - } + 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); } - // Release the window we acquired earlier. - if (oldNativeWindow != NULL) { - ANativeWindow_release(oldNativeWindow); + + code->lastWindowWidth = ANativeWindow_getWidth(code->nativeWindow); + code->lastWindowHeight = ANativeWindow_getHeight(code->nativeWindow); + } + } 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); - } + 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); + 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); + } } -extern "C" void GameActivity_setImeEditorInfo(GameActivity *activity, - int inputType, int actionId, - int imeOptions) { - NativeCode *code = static_cast(activity); - write_work(code->mainWorkWrite, CMD_SET_IME_EDITOR_INFO, inputType, - actionId, imeOptions); +extern "C" void GameActivity_setImeEditorInfo( + GameActivity *activity, GameTextInputType inputType, + GameTextInputActionType actionId, GameTextInputImeOptions imeOptions) { + NativeCode *code = static_cast(activity); + write_work(code->mainWorkWrite, CMD_SET_IME_EDITOR_INFO, inputType, actionId, + imeOptions); } extern "C" int GameActivity_getColorMode(GameActivity *) { - return gConfiguration.colorMode; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.colorMode; } extern "C" int GameActivity_getDensityDpi(GameActivity *) { - return gConfiguration.densityDpi; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.densityDpi; } extern "C" float GameActivity_getFontScale(GameActivity *) { - return gConfiguration.fontScale; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.fontScale; } extern "C" int GameActivity_getFontWeightAdjustment(GameActivity *) { - return gConfiguration.fontWeightAdjustment; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.fontWeightAdjustment; } extern "C" int GameActivity_getHardKeyboardHidden(GameActivity *) { - return gConfiguration.hardKeyboardHidden; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.hardKeyboardHidden; } extern "C" int GameActivity_getKeyboard(GameActivity *) { - return gConfiguration.keyboard; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.keyboard; } extern "C" int GameActivity_getKeyboardHidden(GameActivity *) { - return gConfiguration.keyboardHidden; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.keyboardHidden; +} + +extern "C" int GameActivity_getLocalesCount(GameActivity *activity) { + const std::lock_guard lock(gConfigMutex); + return gConfiguration.locales.size(); } extern "C" int GameActivity_getMcc(GameActivity *) { - return gConfiguration.mcc; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.mcc; } extern "C" int GameActivity_getMnc(GameActivity *) { - return gConfiguration.mnc; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.mnc; } extern "C" int GameActivity_getNavigation(GameActivity *) { - return gConfiguration.navigation; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.navigation; } extern "C" int GameActivity_getNavigationHidden(GameActivity *) { - return gConfiguration.navigationHidden; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.navigationHidden; } extern "C" int GameActivity_getOrientation(GameActivity *) { - return gConfiguration.orientation; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.orientation; } extern "C" int GameActivity_getScreenHeightDp(GameActivity *) { - return gConfiguration.screenHeightDp; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.screenHeightDp; } extern "C" int GameActivity_getScreenLayout(GameActivity *) { - return gConfiguration.screenLayout; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.screenLayout; } extern "C" int GameActivity_getScreenWidthDp(GameActivity *) { - return gConfiguration.screenWidthDp; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.screenWidthDp; } extern "C" int GameActivity_getSmallestScreenWidthDp(GameActivity *) { - return gConfiguration.smallestScreenWidthDp; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.smallestScreenWidthDp; } extern "C" int GameActivity_getTouchscreen(GameActivity *) { - return gConfiguration.touchscreen; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.touchscreen; } extern "C" int GameActivity_getUIMode(GameActivity *) { - return gConfiguration.uiMode; + const std::lock_guard lock(gConfigMutex); + return gConfiguration.uiMode; } -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 int copyStringIfFits(char *dst, size_t dst_size, const std::string &s) { + if (dst_size <= s.size()) { + return ENOBUFS; + } + + strcpy(dst, s.c_str()); + return 0; +} + +extern "C" int GameActivity_getLocaleLanguage(char *dst, size_t dst_size, + GameActivity *activity, + size_t localeIdx) { + const std::lock_guard lock(gConfigMutex); + + if (localeIdx >= gConfiguration.locales.size()) { + return EINVAL; + } + + return copyStringIfFits(dst, dst_size, + gConfiguration.locales[localeIdx].language); +} + +extern "C" int GameActivity_getLocaleScript(char *dst, size_t dst_size, + GameActivity *activity, + size_t localeIdx) { + const std::lock_guard lock(gConfigMutex); + + if (localeIdx >= gConfiguration.locales.size()) { + return EINVAL; + } + + return copyStringIfFits(dst, dst_size, + gConfiguration.locales[localeIdx].script); +} + +extern "C" int GameActivity_getLocaleCountry(char *dst, size_t dst_size, + GameActivity *activity, + size_t localeIdx) { + const std::lock_guard lock(gConfigMutex); - static GameActivityMotionEvent c_event; - GameActivityMotionEvent_fromJava(env, motionEvent, &c_event); - return code->callbacks.onTouchEvent(code, &c_event); + if (localeIdx >= gConfiguration.locales.size()) { + return EINVAL; + } + return copyStringIfFits(dst, dst_size, + gConfiguration.locales[localeIdx].country); +} + +extern "C" int GameActivity_getLocaleVariant(char *dst, size_t dst_size, + GameActivity *activity, + size_t localeIdx) { + const std::lock_guard lock(gConfigMutex); + + if (localeIdx >= gConfiguration.locales.size()) { + return EINVAL; + } + + return copyStringIfFits(dst, dst_size, + gConfiguration.locales[localeIdx].variant); +} + +static bool onTouchEvent_native(JNIEnv *env, jobject javaGameActivity, + jlong handle, jobject motionEvent, + int pointerCount, int historySize, int deviceId, + int source, int action, int64_t eventTime, + int64_t downTime, int flags, int metaState, + int actionButton, int buttonState, + int classification, int edgeFlags, + float precisionX, float precisionY) { + if (handle == 0) return false; + NativeCode *code = (NativeCode *)handle; + if (code->callbacks.onTouchEvent == nullptr) return false; + + static GameActivityMotionEvent c_event; + + c_event.deviceId = deviceId; + c_event.source = source; + c_event.action = action; + + c_event.eventTime = eventTime; + c_event.downTime = downTime; + + c_event.flags = flags; + c_event.metaState = metaState; + + c_event.actionButton = actionButton; + c_event.buttonState = buttonState; + c_event.classification = classification; + c_event.edgeFlags = edgeFlags; + + c_event.precisionX = precisionX; + c_event.precisionY = precisionY; + + GameActivityMotionEvent_fromJava(env, motionEvent, &c_event, pointerCount, + historySize); + 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; + 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 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; + 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 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); + 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); - } + 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); + } + 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); + NativeCode *code = (NativeCode *)handle; + GameTextInput_setInputConnection(code->gameTextInput, inputConnection); } static void onContentRectChangedNative_native(JNIEnv *env, jobject activity, jlong handle, jint x, jint y, jint w, jint h) { - if (handle != 0) { - NativeCode *code = (NativeCode *)handle; - - if (code->callbacks.onContentRectChanged != nullptr) { - ARect rect; - rect.left = x; - rect.top = y; - rect.right = x+w; - rect.bottom = y+h; - code->callbacks.onContentRectChanged(code, &rect); - } + if (handle != 0) { + NativeCode *code = (NativeCode *)handle; + + if (code->callbacks.onContentRectChanged != nullptr) { + ARect rect; + rect.left = x; + rect.top = y; + rect.right = x + w; + rect.bottom = y + h; + code->callbacks.onContentRectChanged(code, &rect); } + } +} + +static void onSoftwareKeyboardVisibilityChangedNative_native(JNIEnv *env, + jobject activity, + jlong handle, + bool visible) { + if (handle != 0) { + NativeCode *code = (NativeCode *)handle; + code->softwareKeyboardVisible = visible; + + if (code->callbacks.onSoftwareKeyboardVisibilityChanged != nullptr) { + code->callbacks.onSoftwareKeyboardVisibilityChanged(code, visible); + } + } +} + +static void onEditorActionNative_native(JNIEnv *env, jobject activity, + jlong handle, int action) { + if (handle != 0) { + NativeCode *code = (NativeCode *)handle; + + if (code->callbacks.onEditorAction != nullptr) { + code->callbacks.onEditorAction(code, action); + } + } } static const JNINativeMethod g_methods[] = { @@ -971,7 +1197,7 @@ static const JNINativeMethod g_methods[] = { {"onSurfaceRedrawNeededNative", "(JLandroid/view/Surface;)V", (void *)onSurfaceRedrawNeeded_native}, {"onSurfaceDestroyedNative", "(J)V", (void *)onSurfaceDestroyed_native}, - {"onTouchEventNative", "(JLandroid/view/MotionEvent;)Z", + {"onTouchEventNative", "(JLandroid/view/MotionEvent;IIIIIJJIIIIIIFF)Z", (void *)onTouchEvent_native}, {"onKeyDownNative", "(JLandroid/view/KeyEvent;)Z", (void *)onKeyDown_native}, @@ -986,6 +1212,9 @@ static const JNINativeMethod g_methods[] = { (void *)setInputConnection_native}, {"onContentRectChangedNative", "(JIIII)V", (void *)onContentRectChangedNative_native}, + {"onSoftwareKeyboardVisibilityChangedNative", "(JZ)V", + (void *)onSoftwareKeyboardVisibilityChangedNative_native}, + {"onEditorActionNative", "(JI)V", (void *)onEditorActionNative_native}, }; static const char *const kGameActivityPathName = @@ -996,138 +1225,161 @@ static const char *const kConfigurationPathName = "android/content/res/Configuration"; static const char *const kWindowInsetsCompatTypePathName = "androidx/core/view/WindowInsetsCompat$Type"; +static const char *const kLocaleListPathName = "android/os/LocaleList"; +static const char *const kLocalePathName = "java/util/Locale"; -#define FIND_CLASS(var, className) \ - var = env->FindClass(className); \ - LOG_FATAL_IF(!var, "Unable to find class %s", className); +#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_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); + 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); +#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); + 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 configuration_class; - FIND_CLASS(configuration_class, kConfigurationPathName); - - if (android_get_device_api_level() >= 26) { - GET_FIELD_ID(gConfigurationClassInfo.colorMode, configuration_class, - "colorMode", "I"); - } - - GET_FIELD_ID(gConfigurationClassInfo.densityDpi, configuration_class, - "densityDpi", "I"); - GET_FIELD_ID(gConfigurationClassInfo.fontScale, configuration_class, - "fontScale", "F"); - - if (android_get_device_api_level() >= 31) { - GET_FIELD_ID(gConfigurationClassInfo.fontWeightAdjustment, - configuration_class, "fontWeightAdjustment", "I"); - } - - GET_FIELD_ID(gConfigurationClassInfo.hardKeyboardHidden, - configuration_class, "hardKeyboardHidden", "I"); - GET_FIELD_ID(gConfigurationClassInfo.keyboard, configuration_class, - "keyboard", "I"); - GET_FIELD_ID(gConfigurationClassInfo.keyboardHidden, configuration_class, - "keyboardHidden", "I"); - GET_FIELD_ID(gConfigurationClassInfo.mcc, configuration_class, "mcc", "I"); - GET_FIELD_ID(gConfigurationClassInfo.mnc, configuration_class, "mnc", "I"); - GET_FIELD_ID(gConfigurationClassInfo.navigation, configuration_class, - "navigation", "I"); - GET_FIELD_ID(gConfigurationClassInfo.navigationHidden, configuration_class, - "navigationHidden", "I"); - GET_FIELD_ID(gConfigurationClassInfo.orientation, configuration_class, - "orientation", "I"); - GET_FIELD_ID(gConfigurationClassInfo.screenHeightDp, configuration_class, - "screenHeightDp", "I"); - GET_FIELD_ID(gConfigurationClassInfo.screenLayout, configuration_class, - "screenLayout", "I"); - GET_FIELD_ID(gConfigurationClassInfo.screenWidthDp, configuration_class, - "screenWidthDp", "I"); - GET_FIELD_ID(gConfigurationClassInfo.smallestScreenWidthDp, - configuration_class, "smallestScreenWidthDp", "I"); - GET_FIELD_ID(gConfigurationClassInfo.touchscreen, configuration_class, - "touchscreen", "I"); - GET_FIELD_ID(gConfigurationClassInfo.uiMode, configuration_class, "uiMode", - "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)); + 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 configuration_class; + FIND_CLASS(configuration_class, kConfigurationPathName); + + if (android_get_device_api_level() >= 26) { + GET_FIELD_ID(gConfigurationClassInfo.colorMode, configuration_class, + "colorMode", "I"); + } + + GET_FIELD_ID(gConfigurationClassInfo.densityDpi, configuration_class, + "densityDpi", "I"); + GET_FIELD_ID(gConfigurationClassInfo.fontScale, configuration_class, + "fontScale", "F"); + + if (android_get_device_api_level() >= 31) { + GET_FIELD_ID(gConfigurationClassInfo.fontWeightAdjustment, + configuration_class, "fontWeightAdjustment", "I"); + } + + GET_FIELD_ID(gConfigurationClassInfo.hardKeyboardHidden, configuration_class, + "hardKeyboardHidden", "I"); + GET_FIELD_ID(gConfigurationClassInfo.keyboard, configuration_class, + "keyboard", "I"); + GET_FIELD_ID(gConfigurationClassInfo.keyboardHidden, configuration_class, + "keyboardHidden", "I"); + GET_FIELD_ID(gConfigurationClassInfo.mcc, configuration_class, "mcc", "I"); + GET_FIELD_ID(gConfigurationClassInfo.mnc, configuration_class, "mnc", "I"); + GET_FIELD_ID(gConfigurationClassInfo.navigation, configuration_class, + "navigation", "I"); + GET_FIELD_ID(gConfigurationClassInfo.navigationHidden, configuration_class, + "navigationHidden", "I"); + GET_FIELD_ID(gConfigurationClassInfo.orientation, configuration_class, + "orientation", "I"); + GET_FIELD_ID(gConfigurationClassInfo.screenHeightDp, configuration_class, + "screenHeightDp", "I"); + GET_FIELD_ID(gConfigurationClassInfo.screenLayout, configuration_class, + "screenLayout", "I"); + GET_FIELD_ID(gConfigurationClassInfo.screenWidthDp, configuration_class, + "screenWidthDp", "I"); + GET_FIELD_ID(gConfigurationClassInfo.smallestScreenWidthDp, + configuration_class, "smallestScreenWidthDp", "I"); + GET_FIELD_ID(gConfigurationClassInfo.touchscreen, configuration_class, + "touchscreen", "I"); + GET_FIELD_ID(gConfigurationClassInfo.uiMode, configuration_class, "uiMode", + "I"); + + GET_METHOD_ID(gConfigurationClassInfo.getLocales, configuration_class, + "getLocales", "()Landroid/os/LocaleList;"); + + jclass localeListClass; + FIND_CLASS(localeListClass, kLocaleListPathName); + GET_METHOD_ID(gLocaleListClassInfo.size, localeListClass, "size", "()I"); + GET_METHOD_ID(gLocaleListClassInfo.get, localeListClass, "get", + "(I)Ljava/util/Locale;"); + + jclass localeClass; + FIND_CLASS(localeClass, kLocalePathName); + GET_METHOD_ID(gLocaleClassInfo.getLanguage, localeClass, "getLanguage", + "()Ljava/lang/String;"); + GET_METHOD_ID(gLocaleClassInfo.getScript, localeClass, "getScript", + "()Ljava/lang/String;"); + GET_METHOD_ID(gLocaleClassInfo.getCountry, localeClass, "getCountry", + "()Ljava/lang/String;"); + GET_METHOD_ID(gLocaleClassInfo.getVariant, localeClass, "getVariant", + "()Ljava/lang/String;"); + + 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"); + } + + GameActivityEventsInit(env); + + return jniRegisterNativeMethods(env, kGameActivityPathName, g_methods, + NELEM(g_methods)); } // XXX: This symbol is renamed with a _C suffix and then re-exported from @@ -1139,9 +1391,9 @@ Java_com_google_androidgamesdk_GameActivity_initializeNativeCode_C( JNIEnv *env, jobject javaGameActivity, jstring internalDataDir, jstring obbDir, jstring externalDataDir, jobject jAssetMgr, jbyteArray savedState, jobject javaConfig) { - GameActivity_register(env); - jlong nativeCode = initializeNativeCode_native( - env, javaGameActivity, internalDataDir, obbDir, externalDataDir, - jAssetMgr, savedState, javaConfig); - return nativeCode; + GameActivity_register(env); + jlong nativeCode = initializeNativeCode_native( + env, javaGameActivity, internalDataDir, obbDir, externalDataDir, + jAssetMgr, savedState, javaConfig); + return nativeCode; } diff --git a/android-activity/game-activity-csrc/game-activity/GameActivity.h b/android-activity/game-activity-csrc/game-activity/GameActivity.h index b945069..b6ff8b4 100644 --- a/android-activity/game-activity-csrc/game-activity/GameActivity.h +++ b/android-activity/game-activity-csrc/game-activity/GameActivity.h @@ -31,26 +31,43 @@ #include #include #include +#include +#include +#include #include #include #include #include -#include "common/gamesdk_common.h" -#include "game-activity/GameActivityEvents.h" -#include "game-text-input/gametextinput.h" - #ifdef __cplusplus extern "C" { #endif -#define GAMEACTIVITY_MAJOR_VERSION 2 +#define GAMEACTIVITY_MAJOR_VERSION 4 #define GAMEACTIVITY_MINOR_VERSION 0 -#define GAMEACTIVITY_BUGFIX_VERSION 2 -#define GAMEACTIVITY_PACKED_VERSION \ - ANDROID_GAMESDK_PACKED_VERSION(GAMEACTIVITY_MAJOR_VERSION, \ - GAMEACTIVITY_MINOR_VERSION, \ - GAMEACTIVITY_BUGFIX_VERSION) +#define GAMEACTIVITY_BUGFIX_VERSION 0 +#define GAMEACTIVITY_PACKED_VERSION \ + ANDROID_GAMESDK_PACKED_VERSION(GAMEACTIVITY_MAJOR_VERSION, \ + GAMEACTIVITY_MINOR_VERSION, \ + GAMEACTIVITY_BUGFIX_VERSION) + +/** + * The type of a component for which to retrieve insets. See + * https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type + */ +typedef enum GameCommonInsetsType : uint8_t { + GAMECOMMON_INSETS_TYPE_CAPTION_BAR = 0, + GAMECOMMON_INSETS_TYPE_DISPLAY_CUTOUT, + GAMECOMMON_INSETS_TYPE_IME, + GAMECOMMON_INSETS_TYPE_MANDATORY_SYSTEM_GESTURES, + GAMECOMMON_INSETS_TYPE_NAVIGATION_BARS, + GAMECOMMON_INSETS_TYPE_STATUS_BARS, + GAMECOMMON_INSETS_TYPE_SYSTEM_BARS, + GAMECOMMON_INSETS_TYPE_SYSTEM_GESTURES, + GAMECOMMON_INSETS_TYPE_TAPABLE_ELEMENT, + GAMECOMMON_INSETS_TYPE_WATERFALL, + GAMECOMMON_INSETS_TYPE_COUNT +} GameCommonInsetsType; /** * {@link GameActivityCallbacks} @@ -63,66 +80,66 @@ struct GameActivityCallbacks; * code as it is being launched. */ typedef struct GameActivity { - /** - * Pointer to the callback function table of the native application. - * You can set the functions here to your own callbacks. The callbacks - * pointer itself here should not be changed; it is allocated and managed - * for you by the framework. - */ - struct GameActivityCallbacks* callbacks; - - /** - * The global handle on the process's Java VM. - */ - JavaVM* vm; - - /** - * JNI context for the main thread of the app. Note that this field - * can ONLY be used from the main thread of the process; that is, the - * thread that calls into the GameActivityCallbacks. - */ - JNIEnv* env; - - /** - * The GameActivity object handle. - */ - jobject javaGameActivity; - - /** - * Path to this application's internal data directory. - */ - const char* internalDataPath; - - /** - * Path to this application's external (removable/mountable) data directory. - */ - const char* externalDataPath; - - /** - * The platform's SDK version code. - */ - int32_t sdkVersion; - - /** - * This is the native instance of the application. It is not used by - * the framework, but can be set by the application to its own instance - * state. - */ - void* instance; - - /** - * Pointer to the Asset Manager instance for the application. The - * application uses this to access binary assets bundled inside its own .apk - * file. - */ - AAssetManager* assetManager; - - /** - * Available starting with Honeycomb: path to the directory containing - * the application's OBB files (if any). If the app doesn't have any - * OBB files, this directory may not exist. - */ - const char* obbPath; + /** + * Pointer to the callback function table of the native application. + * You can set the functions here to your own callbacks. The callbacks + * pointer itself here should not be changed; it is allocated and managed + * for you by the framework. + */ + struct GameActivityCallbacks* callbacks; + + /** + * The global handle on the process's Java VM. + */ + JavaVM* vm; + + /** + * JNI context for the main thread of the app. Note that this field + * can ONLY be used from the main thread of the process; that is, the + * thread that calls into the GameActivityCallbacks. + */ + JNIEnv* env; + + /** + * The GameActivity object handle. + */ + jobject javaGameActivity; + + /** + * Path to this application's internal data directory. + */ + const char* internalDataPath; + + /** + * Path to this application's external (removable/mountable) data directory. + */ + const char* externalDataPath; + + /** + * The platform's SDK version code. + */ + int32_t sdkVersion; + + /** + * This is the native instance of the application. It is not used by + * the framework, but can be set by the application to its own instance + * state. + */ + void* instance; + + /** + * Pointer to the Asset Manager instance for the application. The + * application uses this to access binary assets bundled inside its own .apk + * file. + */ + AAssetManager* assetManager; + + /** + * Available starting with Honeycomb: path to the directory containing + * the application's OBB files (if any). If the app doesn't have any + * OBB files, this directory may not exist. + */ + const char* obbPath; } GameActivity; /** @@ -139,144 +156,153 @@ typedef void (*SaveInstanceStateRecallback)(const char* bytes, int len, * to have it called. */ typedef struct GameActivityCallbacks { - /** - * GameActivity has started. See Java documentation for Activity.onStart() - * for more information. - */ - void (*onStart)(GameActivity* activity); - - /** - * GameActivity has resumed. See Java documentation for Activity.onResume() - * for more information. - */ - void (*onResume)(GameActivity* activity); - - /** - * The framework is asking GameActivity to save its current instance state. - * See the Java documentation for Activity.onSaveInstanceState() for more - * information. The user should call the recallback with their data, its - * length and the provided context; they retain ownership of the data. Note - * that the saved state will be persisted, so it can not contain any active - * entities (pointers to memory, file descriptors, etc). - */ - void (*onSaveInstanceState)(GameActivity* activity, - SaveInstanceStateRecallback recallback, - void* context); - - /** - * GameActivity has paused. See Java documentation for Activity.onPause() - * for more information. - */ - void (*onPause)(GameActivity* activity); - - /** - * GameActivity has stopped. See Java documentation for Activity.onStop() - * for more information. - */ - void (*onStop)(GameActivity* activity); - - /** - * GameActivity is being destroyed. See Java documentation for - * Activity.onDestroy() for more information. - */ - void (*onDestroy)(GameActivity* activity); - - /** - * Focus has changed in this GameActivity's window. This is often used, - * for example, to pause a game when it loses input focus. - */ - void (*onWindowFocusChanged)(GameActivity* activity, bool hasFocus); - - /** - * The drawing window for this native activity has been created. You - * can use the given native window object to start drawing. - */ - void (*onNativeWindowCreated)(GameActivity* activity, + /** + * GameActivity has started. See Java documentation for Activity.onStart() + * for more information. + */ + void (*onStart)(GameActivity* activity); + + /** + * GameActivity has resumed. See Java documentation for Activity.onResume() + * for more information. + */ + void (*onResume)(GameActivity* activity); + + /** + * The framework is asking GameActivity to save its current instance state. + * See the Java documentation for Activity.onSaveInstanceState() for more + * information. The user should call the recallback with their data, its + * length and the provided context; they retain ownership of the data. Note + * that the saved state will be persisted, so it can not contain any active + * entities (pointers to memory, file descriptors, etc). + */ + void (*onSaveInstanceState)(GameActivity* activity, + SaveInstanceStateRecallback recallback, + void* context); + + /** + * GameActivity has paused. See Java documentation for Activity.onPause() + * for more information. + */ + void (*onPause)(GameActivity* activity); + + /** + * GameActivity has stopped. See Java documentation for Activity.onStop() + * for more information. + */ + void (*onStop)(GameActivity* activity); + + /** + * GameActivity is being destroyed. See Java documentation for + * Activity.onDestroy() for more information. + */ + void (*onDestroy)(GameActivity* activity); + + /** + * Focus has changed in this GameActivity's window. This is often used, + * for example, to pause a game when it loses input focus. + */ + void (*onWindowFocusChanged)(GameActivity* activity, bool hasFocus); + + /** + * The drawing window for this native activity has been created. You + * can use the given native window object to start drawing. + */ + void (*onNativeWindowCreated)(GameActivity* activity, ANativeWindow* window); + + /** + * The drawing window for this native activity has been resized. You should + * retrieve the new size from the window and ensure that your rendering in + * it now matches. + */ + void (*onNativeWindowResized)(GameActivity* activity, ANativeWindow* window, + int32_t newWidth, int32_t newHeight); + + /** + * The drawing window for this native activity needs to be redrawn. To + * avoid transient artifacts during screen changes (such resizing after + * rotation), applications should not return from this function until they + * have finished drawing their window in its current state. + */ + void (*onNativeWindowRedrawNeeded)(GameActivity* activity, + ANativeWindow* window); + + /** + * The drawing window for this native activity is going to be destroyed. + * You MUST ensure that you do not touch the window object after returning + * from this function: in the common case of drawing to the window from + * another thread, that means the implementation of this callback must + * properly synchronize with the other thread to stop its drawing before + * returning from here. + */ + void (*onNativeWindowDestroyed)(GameActivity* activity, ANativeWindow* window); - /** - * The drawing window for this native activity has been resized. You should - * retrieve the new size from the window and ensure that your rendering in - * it now matches. - */ - void (*onNativeWindowResized)(GameActivity* activity, ANativeWindow* window, - int32_t newWidth, int32_t newHeight); - - /** - * The drawing window for this native activity needs to be redrawn. To - * avoid transient artifacts during screen changes (such resizing after - * rotation), applications should not return from this function until they - * have finished drawing their window in its current state. - */ - void (*onNativeWindowRedrawNeeded)(GameActivity* activity, - ANativeWindow* window); - - /** - * The drawing window for this native activity is going to be destroyed. - * You MUST ensure that you do not touch the window object after returning - * from this function: in the common case of drawing to the window from - * another thread, that means the implementation of this callback must - * properly synchronize with the other thread to stop its drawing before - * returning from here. - */ - void (*onNativeWindowDestroyed)(GameActivity* activity, - ANativeWindow* window); - - /** - * The current device AConfiguration has changed. The new configuration can - * be retrieved from assetManager. - */ - void (*onConfigurationChanged)(GameActivity* activity); - - /** - * The system is running low on memory. Use this callback to release - * resources you do not need, to help the system avoid killing more - * important processes. - */ - void (*onTrimMemory)(GameActivity* activity, int level); - - /** - * Callback called for every MotionEvent done on the GameActivity - * SurfaceView. Ownership of `event` is maintained by the library and it is - * only valid during the callback. - */ - bool (*onTouchEvent)(GameActivity* activity, - const GameActivityMotionEvent* event); - - /** - * Callback called for every key down event on the GameActivity SurfaceView. - * Ownership of `event` is maintained by the library and it is only valid - * during the callback. - */ - bool (*onKeyDown)(GameActivity* activity, - const GameActivityKeyEvent* event); - - /** - * Callback called for every key up event on the GameActivity SurfaceView. - * Ownership of `event` is maintained by the library and it is only valid - * during the callback. - */ - bool (*onKeyUp)(GameActivity* activity, const GameActivityKeyEvent* event); - - /** - * Callback called for every soft-keyboard text input event. - * Ownership of `state` is maintained by the library and it is only valid - * during the callback. - */ - void (*onTextInputEvent)(GameActivity* activity, - const GameTextInputState* state); - - /** - * Callback called when WindowInsets of the main app window have changed. - * Call GameActivity_getWindowInsets to retrieve the insets themselves. - */ - void (*onWindowInsetsChanged)(GameActivity* activity); - - /** - * Callback called when the rectangle in the window where the content - * should be placed has changed. - */ - void (*onContentRectChanged)(GameActivity *activity, const ARect *rect); + /** + * The current device AConfiguration has changed. The new configuration can + * be retrieved from assetManager. + */ + void (*onConfigurationChanged)(GameActivity* activity); + + /** + * The system is running low on memory. Use this callback to release + * resources you do not need, to help the system avoid killing more + * important processes. + */ + void (*onTrimMemory)(GameActivity* activity, int level); + + /** + * Callback called for every MotionEvent done on the GameActivity + * SurfaceView. Ownership of `event` is maintained by the library and it is + * only valid during the callback. + */ + bool (*onTouchEvent)(GameActivity* activity, + const GameActivityMotionEvent* event); + + /** + * Callback called for every key down event on the GameActivity SurfaceView. + * Ownership of `event` is maintained by the library and it is only valid + * during the callback. + */ + bool (*onKeyDown)(GameActivity* activity, const GameActivityKeyEvent* event); + + /** + * Callback called for every key up event on the GameActivity SurfaceView. + * Ownership of `event` is maintained by the library and it is only valid + * during the callback. + */ + bool (*onKeyUp)(GameActivity* activity, const GameActivityKeyEvent* event); + + /** + * Callback called for every soft-keyboard text input event. + * Ownership of `state` is maintained by the library and it is only valid + * during the callback. + */ + void (*onTextInputEvent)(GameActivity* activity, + const GameTextInputState* state); + + /** + * Callback called when WindowInsets of the main app window have changed. + * Call GameActivity_getWindowInsets to retrieve the insets themselves. + */ + void (*onWindowInsetsChanged)(GameActivity* activity); + + /** + * Callback called when the rectangle in the window where the content + * should be placed has changed. + */ + void (*onContentRectChanged)(GameActivity* activity, const ARect* rect); + + /** + * Callback called when the software keyboard is shown or hidden. + */ + void (*onSoftwareKeyboardVisibilityChanged)(GameActivity* activity, + bool visible); + + /** + * Callback called when the software keyboard is shown or hidden. + */ + bool (*onEditorAction)(GameActivity* activity, int action); } GameActivityCallbacks; /** @@ -310,187 +336,187 @@ void GameActivity_finish(GameActivity* activity); * Flags for GameActivity_setWindowFlags, * as per the Java API at android.view.WindowManager.LayoutParams. */ -enum GameActivitySetWindowFlags { - /** - * As long as this window is visible to the user, allow the lock - * screen to activate while the screen is on. This can be used - * independently, or in combination with {@link - * GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} and/or {@link - * GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} - */ - GAMEACTIVITY_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001, - /** Everything behind this window will be dimmed. */ - GAMEACTIVITY_FLAG_DIM_BEHIND = 0x00000002, - /** - * Blur everything behind this window. - * @deprecated Blurring is no longer supported. - */ - GAMEACTIVITY_FLAG_BLUR_BEHIND = 0x00000004, - /** - * This window won't ever get key input focus, so the - * user can not send key or other button events to it. Those will - * instead go to whatever focusable window is behind it. This flag - * will also enable {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL} whether or not - * that is explicitly set. - * - * Setting this flag also implies that the window will not need to - * interact with - * a soft input method, so it will be Z-ordered and positioned - * independently of any active input method (typically this means it - * gets Z-ordered on top of the input method, so it can use the full - * screen for its content and cover the input method if needed. You - * can use {@link GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM} to modify this - * behavior. - */ - GAMEACTIVITY_FLAG_NOT_FOCUSABLE = 0x00000008, - /** This window can never receive touch events. */ - GAMEACTIVITY_FLAG_NOT_TOUCHABLE = 0x00000010, - /** - * Even when this window is focusable (its - * {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set), allow any pointer - * events outside of the window to be sent to the windows behind it. - * Otherwise it will consume all pointer events itself, regardless of - * whether they are inside of the window. - */ - GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL = 0x00000020, - /** - * When set, if the device is asleep when the touch - * screen is pressed, you will receive this first touch event. Usually - * the first touch event is consumed by the system since the user can - * not see what they are pressing on. - * - * @deprecated This flag has no effect. - */ - GAMEACTIVITY_FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040, - /** - * As long as this window is visible to the user, keep - * the device's screen turned on and bright. - */ - GAMEACTIVITY_FLAG_KEEP_SCREEN_ON = 0x00000080, - /** - * Place the window within the entire screen, ignoring - * decorations around the border (such as the status bar). The - * window must correctly position its contents to take the screen - * decoration into account. - */ - GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN = 0x00000100, - /** Allows the window to extend outside of the screen. */ - GAMEACTIVITY_FLAG_LAYOUT_NO_LIMITS = 0x00000200, - /** - * Hide all screen decorations (such as the status - * bar) while this window is displayed. This allows the window to - * use the entire display space for itself -- the status bar will - * be hidden when an app window with this flag set is on the top - * layer. A fullscreen window will ignore a value of {@link - * GAMEACTIVITY_SOFT_INPUT_ADJUST_RESIZE}; the window will stay - * fullscreen and will not resize. - */ - GAMEACTIVITY_FLAG_FULLSCREEN = 0x00000400, - /** - * Override {@link GAMEACTIVITY_FLAG_FULLSCREEN} and force the - * screen decorations (such as the status bar) to be shown. - */ - GAMEACTIVITY_FLAG_FORCE_NOT_FULLSCREEN = 0x00000800, - /** - * Turn on dithering when compositing this window to - * the screen. - * @deprecated This flag is no longer used. - */ - GAMEACTIVITY_FLAG_DITHER = 0x00001000, - /** - * Treat the content of the window as secure, preventing - * it from appearing in screenshots or from being viewed on non-secure - * displays. - */ - GAMEACTIVITY_FLAG_SECURE = 0x00002000, - /** - * A special mode where the layout parameters are used - * to perform scaling of the surface when it is composited to the - * screen. - */ - GAMEACTIVITY_FLAG_SCALED = 0x00004000, - /** - * Intended for windows that will often be used when the user is - * holding the screen against their face, it will aggressively - * filter the event stream to prevent unintended presses in this - * situation that may not be desired for a particular window, when - * such an event stream is detected, the application will receive - * a {@link AMOTION_EVENT_ACTION_CANCEL} to indicate this so - * applications can handle this accordingly by taking no action on - * the event until the finger is released. - */ - GAMEACTIVITY_FLAG_IGNORE_CHEEK_PRESSES = 0x00008000, - /** - * A special option only for use in combination with - * {@link GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN}. When requesting layout in - * the screen your window may appear on top of or behind screen decorations - * such as the status bar. By also including this flag, the window - * manager will report the inset rectangle needed to ensure your - * content is not covered by screen decorations. - */ - GAMEACTIVITY_FLAG_LAYOUT_INSET_DECOR = 0x00010000, - /** - * Invert the state of {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} with - * respect to how this window interacts with the current method. - * That is, if FLAG_NOT_FOCUSABLE is set and this flag is set, - * then the window will behave as if it needs to interact with the - * input method and thus be placed behind/away from it; if {@link - * GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set and this flag is set, - * then the window will behave as if it doesn't need to interact - * with the input method and can be placed to use more space and - * cover the input method. - */ - GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM = 0x00020000, - /** - * If you have set {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL}, you - * can set this flag to receive a single special MotionEvent with - * the action - * {@link AMOTION_EVENT_ACTION_OUTSIDE} for - * touches that occur outside of your window. Note that you will not - * receive the full down/move/up gesture, only the location of the - * first down as an {@link AMOTION_EVENT_ACTION_OUTSIDE}. - */ - GAMEACTIVITY_FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000, - /** - * Special flag to let windows be shown when the screen - * is locked. This will let application windows take precedence over - * key guard or any other lock screens. Can be used with - * {@link GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} to turn screen on and display - * windows directly before showing the key guard window. Can be used with - * {@link GAMEACTIVITY_FLAG_DISMISS_KEYGUARD} to automatically fully - * dismisss non-secure keyguards. This flag only applies to the top-most - * full-screen window. - */ - GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED = 0x00080000, - /** - * Ask that the system wallpaper be shown behind - * your window. The window surface must be translucent to be able - * to actually see the wallpaper behind it; this flag just ensures - * that the wallpaper surface will be there if this window actually - * has translucent regions. - */ - GAMEACTIVITY_FLAG_SHOW_WALLPAPER = 0x00100000, - /** - * When set as a window is being added or made - * visible, once the window has been shown then the system will - * poke the power manager's user activity (as if the user had woken - * up the device) to turn the screen on. - */ - GAMEACTIVITY_FLAG_TURN_SCREEN_ON = 0x00200000, - /** - * When set the window will cause the keyguard to - * be dismissed, only if it is not a secure lock keyguard. Because such - * a keyguard is not needed for security, it will never re-appear if - * the user navigates to another window (in contrast to - * {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}, which will only temporarily - * hide both secure and non-secure keyguards but ensure they reappear - * when the user moves to another UI that doesn't hide them). - * If the keyguard is currently active and is secure (requires an - * unlock pattern) than the user will still need to confirm it before - * seeing this window, unless {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} has - * also been set. - */ - GAMEACTIVITY_FLAG_DISMISS_KEYGUARD = 0x00400000, +enum GameActivitySetWindowFlags : uint32_t { + /** + * As long as this window is visible to the user, allow the lock + * screen to activate while the screen is on. This can be used + * independently, or in combination with {@link + * GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} and/or {@link + * GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} + */ + GAMEACTIVITY_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001, + /** Everything behind this window will be dimmed. */ + GAMEACTIVITY_FLAG_DIM_BEHIND = 0x00000002, + /** + * Blur everything behind this window. + * @deprecated Blurring is no longer supported. + */ + GAMEACTIVITY_FLAG_BLUR_BEHIND = 0x00000004, + /** + * This window won't ever get key input focus, so the + * user can not send key or other button events to it. Those will + * instead go to whatever focusable window is behind it. This flag + * will also enable {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL} whether or not + * that is explicitly set. + * + * Setting this flag also implies that the window will not need to + * interact with + * a soft input method, so it will be Z-ordered and positioned + * independently of any active input method (typically this means it + * gets Z-ordered on top of the input method, so it can use the full + * screen for its content and cover the input method if needed. You + * can use {@link GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM} to modify this + * behavior. + */ + GAMEACTIVITY_FLAG_NOT_FOCUSABLE = 0x00000008, + /** This window can never receive touch events. */ + GAMEACTIVITY_FLAG_NOT_TOUCHABLE = 0x00000010, + /** + * Even when this window is focusable (its + * {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set), allow any pointer + * events outside of the window to be sent to the windows behind it. + * Otherwise it will consume all pointer events itself, regardless of + * whether they are inside of the window. + */ + GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL = 0x00000020, + /** + * When set, if the device is asleep when the touch + * screen is pressed, you will receive this first touch event. Usually + * the first touch event is consumed by the system since the user can + * not see what they are pressing on. + * + * @deprecated This flag has no effect. + */ + GAMEACTIVITY_FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040, + /** + * As long as this window is visible to the user, keep + * the device's screen turned on and bright. + */ + GAMEACTIVITY_FLAG_KEEP_SCREEN_ON = 0x00000080, + /** + * Place the window within the entire screen, ignoring + * decorations around the border (such as the status bar). The + * window must correctly position its contents to take the screen + * decoration into account. + */ + GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN = 0x00000100, + /** Allows the window to extend outside of the screen. */ + GAMEACTIVITY_FLAG_LAYOUT_NO_LIMITS = 0x00000200, + /** + * Hide all screen decorations (such as the status + * bar) while this window is displayed. This allows the window to + * use the entire display space for itself -- the status bar will + * be hidden when an app window with this flag set is on the top + * layer. A fullscreen window will ignore a value of {@link + * GAMEACTIVITY_SOFT_INPUT_ADJUST_RESIZE}; the window will stay + * fullscreen and will not resize. + */ + GAMEACTIVITY_FLAG_FULLSCREEN = 0x00000400, + /** + * Override {@link GAMEACTIVITY_FLAG_FULLSCREEN} and force the + * screen decorations (such as the status bar) to be shown. + */ + GAMEACTIVITY_FLAG_FORCE_NOT_FULLSCREEN = 0x00000800, + /** + * Turn on dithering when compositing this window to + * the screen. + * @deprecated This flag is no longer used. + */ + GAMEACTIVITY_FLAG_DITHER = 0x00001000, + /** + * Treat the content of the window as secure, preventing + * it from appearing in screenshots or from being viewed on non-secure + * displays. + */ + GAMEACTIVITY_FLAG_SECURE = 0x00002000, + /** + * A special mode where the layout parameters are used + * to perform scaling of the surface when it is composited to the + * screen. + */ + GAMEACTIVITY_FLAG_SCALED = 0x00004000, + /** + * Intended for windows that will often be used when the user is + * holding the screen against their face, it will aggressively + * filter the event stream to prevent unintended presses in this + * situation that may not be desired for a particular window, when + * such an event stream is detected, the application will receive + * a {@link AMOTION_EVENT_ACTION_CANCEL} to indicate this so + * applications can handle this accordingly by taking no action on + * the event until the finger is released. + */ + GAMEACTIVITY_FLAG_IGNORE_CHEEK_PRESSES = 0x00008000, + /** + * A special option only for use in combination with + * {@link GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN}. When requesting layout in + * the screen your window may appear on top of or behind screen decorations + * such as the status bar. By also including this flag, the window + * manager will report the inset rectangle needed to ensure your + * content is not covered by screen decorations. + */ + GAMEACTIVITY_FLAG_LAYOUT_INSET_DECOR = 0x00010000, + /** + * Invert the state of {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} with + * respect to how this window interacts with the current method. + * That is, if FLAG_NOT_FOCUSABLE is set and this flag is set, + * then the window will behave as if it needs to interact with the + * input method and thus be placed behind/away from it; if {@link + * GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set and this flag is set, + * then the window will behave as if it doesn't need to interact + * with the input method and can be placed to use more space and + * cover the input method. + */ + GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM = 0x00020000, + /** + * If you have set {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL}, you + * can set this flag to receive a single special MotionEvent with + * the action + * {@link AMOTION_EVENT_ACTION_OUTSIDE} for + * touches that occur outside of your window. Note that you will not + * receive the full down/move/up gesture, only the location of the + * first down as an {@link AMOTION_EVENT_ACTION_OUTSIDE}. + */ + GAMEACTIVITY_FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000, + /** + * Special flag to let windows be shown when the screen + * is locked. This will let application windows take precedence over + * key guard or any other lock screens. Can be used with + * {@link GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} to turn screen on and display + * windows directly before showing the key guard window. Can be used with + * {@link GAMEACTIVITY_FLAG_DISMISS_KEYGUARD} to automatically fully + * dismisss non-secure keyguards. This flag only applies to the top-most + * full-screen window. + */ + GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED = 0x00080000, + /** + * Ask that the system wallpaper be shown behind + * your window. The window surface must be translucent to be able + * to actually see the wallpaper behind it; this flag just ensures + * that the wallpaper surface will be there if this window actually + * has translucent regions. + */ + GAMEACTIVITY_FLAG_SHOW_WALLPAPER = 0x00100000, + /** + * When set as a window is being added or made + * visible, once the window has been shown then the system will + * poke the power manager's user activity (as if the user had woken + * up the device) to turn the screen on. + */ + GAMEACTIVITY_FLAG_TURN_SCREEN_ON = 0x00200000, + /** + * When set the window will cause the keyguard to + * be dismissed, only if it is not a secure lock keyguard. Because such + * a keyguard is not needed for security, it will never re-appear if + * the user navigates to another window (in contrast to + * {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}, which will only temporarily + * hide both secure and non-secure keyguards but ensure they reappear + * when the user moves to another UI that doesn't hide them). + * If the keyguard is currently active and is secure (requires an + * unlock pattern) than the user will still need to confirm it before + * seeing this window, unless {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} has + * also been set. + */ + GAMEACTIVITY_FLAG_DISMISS_KEYGUARD = 0x00400000, }; /** @@ -510,19 +536,19 @@ void GameActivity_setWindowFlags(GameActivity* activity, uint32_t addFlags, * Flags for GameActivity_showSoftInput; see the Java InputMethodManager * API for documentation. */ -enum GameActivityShowSoftInputFlags { - /** - * Implicit request to show the input window, not as the result - * of a direct request by the user. - */ - GAMEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001, - - /** - * The user has forced the input method open (such as by - * long-pressing menu) so it should not be closed until they - * explicitly do so. - */ - GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002, +enum GameActivityShowSoftInputFlags : uint8_t { + /** + * Implicit request to show the input window, not as the result + * of a direct request by the user. + */ + GAMEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001, + + /** + * The user has forced the input method open (such as by + * long-pressing menu) so it should not be closed until they + * explicitly do so. + */ + GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002, }; /** @@ -533,6 +559,13 @@ enum GameActivityShowSoftInputFlags { */ void GameActivity_showSoftInput(GameActivity* activity, uint32_t flags); +/** + * Restarts the input method. Calls InputMethodManager.restartInput(). + * Note that this method can be called from *any* thread; it will send a message + * to the main thread of the process where the Java call will take place. + */ +void GameActivity_restartInput(GameActivity* activity); + /** * Set the text entry state (see documentation of the GameTextInputState struct * in the Game Text Input library reference). @@ -560,17 +593,17 @@ GameTextInput* GameActivity_getTextInput(const GameActivity* activity); * Flags for GameActivity_hideSoftInput; see the Java InputMethodManager * API for documentation. */ -enum GameActivityHideSoftInputFlags { - /** - * The soft input window should only be hidden if it was not - * explicitly shown by the user. - */ - GAMEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001, - /** - * The soft input window should normally be hidden, unless it was - * originally shown with {@link GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED}. - */ - GAMEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002, +enum GameActivityHideSoftInputFlags : uint16_t { + /** + * The soft input window should only be hidden if it was not + * explicitly shown by the user. + */ + GAMEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001, + /** + * The soft input window should normally be hidden, unless it was + * originally shown with {@link GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED}. + */ + GAMEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002, }; /** @@ -590,18 +623,23 @@ void GameActivity_hideSoftInput(GameActivity* activity, uint32_t flags); void GameActivity_getWindowInsets(GameActivity* activity, GameCommonInsetsType type, ARect* insets); +/** + * Tells whether the software keyboard is visible or not. + */ +bool GameActivity_isSoftwareKeyboardVisible(GameActivity* activity); + /** * Set options on how the IME behaves when it is requested for text input. * See * https://developer.android.com/reference/android/view/inputmethod/EditorInfo * for the meaning of inputType, actionId and imeOptions. * - * Note that this function will attach the current thread to the JVM if it is - * not already attached, so the caller must detach the thread from the JVM - * before the thread is destroyed using DetachCurrentThread. + * Note: currently only TYPE_NULL AND TYPE_CLASS_NUMBER are supported. */ -void GameActivity_setImeEditorInfo(GameActivity* activity, int inputType, - int actionId, int imeOptions); +void GameActivity_setImeEditorInfo(GameActivity* activity, + enum GameTextInputType inputType, + enum GameTextInputActionType actionId, + enum GameTextInputImeOptions imeOptions); /** * These are getters for Configuration class members. They may be called from @@ -615,6 +653,7 @@ int GameActivity_getFontWeightAdjustment(GameActivity* activity); int GameActivity_getHardKeyboardHidden(GameActivity* activity); int GameActivity_getKeyboard(GameActivity* activity); int GameActivity_getKeyboardHidden(GameActivity* activity); +int GameActivity_getLocalesCount(GameActivity* activity); int GameActivity_getMcc(GameActivity* activity); int GameActivity_getMnc(GameActivity* activity); int GameActivity_getNavigation(GameActivity* activity); @@ -627,6 +666,42 @@ int GameActivity_getSmallestScreenWidthDp(GameActivity* activity); int GameActivity_getTouchscreen(GameActivity* activity); int GameActivity_getUIMode(GameActivity* activity); +/** + * The functions below return Java locale information. + * + * In simple cases there will be just one locale, but it's possible tha + * there are more than one locale objects. Users are encouraged to write code + * that handles all locales and not just the first one. + * + * The functions in the block below return string values in the provided buffer. + * Return value is zero if there were no errors, otherwise it's non-zero. + * If the return value is zero, `dst` will contain a null-terminated string: + * strlen(dst) <= dst_size - 1. + * If the return value is non-zero, the content of dst is undefined. + * + * Parameters: + * + * dst, dst_size: define a receiver buffer. Locale string can be something + * short like "EN/EN", but it may be longer. You should be safe with a buffer + * size of 256 bytes. + * + * If the buffer is too small, ENOBUFS is returned. Try allocating a larger + * buffer in this case. + * + * localeIdx must be between 0 and the value of GameActivity_getLocalesCount(). + * If localeIdx is out of range, EINVAL is returned. + * + * Refer to Java documentation of locales for more information. + */ +int GameActivity_getLocaleLanguage(char* dst, size_t dst_size, + GameActivity* activity, size_t localeIdx); +int GameActivity_getLocaleScript(char* dst, size_t dst_size, + GameActivity* activity, size_t localeIdx); +int GameActivity_getLocaleCountry(char* dst, size_t dst_size, + GameActivity* activity, size_t localeIdx); +int GameActivity_getLocaleVariant(char* dst, size_t dst_size, + GameActivity* activity, size_t localeIdx); + #ifdef __cplusplus } #endif diff --git a/android-activity/game-activity-csrc/game-activity/GameActivityEvents.cpp b/android-activity/game-activity-csrc/game-activity/GameActivityEvents.cpp index 028b55e..5e238c6 100644 --- a/android-activity/game-activity-csrc/game-activity/GameActivityEvents.cpp +++ b/android-activity/game-activity-csrc/game-activity/GameActivityEvents.cpp @@ -14,43 +14,14 @@ * limitations under the License. */ -#include "GameActivityEvents.h" - +#include +#include #include #include -#include "GameActivityLog.h" - -// 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 { - -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 ""; -} - -std::string GetSystemProp(const char *key, const char *default_value = "") { - return getSystemPropViaGet(key, default_value); -} - -int GetSystemPropAsInt(const char *key, int default_value = 0) { - std::string prop = GetSystemProp(key); - return prop == "" ? default_value : strtoll(prop.c_str(), nullptr, 10); -} - -} // anonymous namespace - -#ifndef NELEM -#define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0]))) -#endif +#include "GameActivityEvents_internal.h" +#include "system_utils.h" static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = { /* AMOTION_EVENT_AXIS_X */ true, @@ -60,355 +31,305 @@ static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = { false}; extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) { - if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { - return; - } + if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { + return; + } - enabledAxes[axis] = true; + enabledAxes[axis] = true; } float GameActivityPointerAxes_getAxisValue( const GameActivityPointerAxes *pointerInfo, int32_t axis) { - if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { - return 0; - } + if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { + return 0; + } - if (!enabledAxes[axis]) { - ALOGW("Axis %d must be enabled before it can be accessed.", axis); - return 0; - } + if (!enabledAxes[axis]) { + ALOGW("Axis %d must be enabled before it can be accessed.", axis); + return 0; + } - return pointerInfo->axisValues[axis]; + return pointerInfo->axisValues[axis]; } extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) { - if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { - return; - } + if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { + return; + } - enabledAxes[axis] = false; + enabledAxes[axis] = false; } float GameActivityMotionEvent_getHistoricalAxisValue( const GameActivityMotionEvent *event, int axis, int pointerIndex, int historyPos) { - if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { - ALOGE("Invalid axis %d", axis); - return -1; - } - if (pointerIndex < 0 || pointerIndex >= event->pointerCount) { - ALOGE("Invalid pointer index %d", pointerIndex); - return -1; - } - if (historyPos < 0 || historyPos >= event->historySize) { - ALOGE("Invalid history index %d", historyPos); - return -1; - } - if (!enabledAxes[axis]) { - ALOGW("Axis %d must be enabled before it can be accessed.", axis); - return 0; - } - - int pointerOffset = pointerIndex * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; - int historyValuesOffset = historyPos * event->pointerCount * - GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; - return event - ->historicalAxisValues[historyValuesOffset + pointerOffset + axis]; + if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { + ALOGE("Invalid axis %d", axis); + return -1; + } + if (pointerIndex < 0 || pointerIndex >= event->pointerCount) { + ALOGE("Invalid pointer index %d", pointerIndex); + return -1; + } + if (historyPos < 0 || historyPos >= event->historySize) { + ALOGE("Invalid history index %d", historyPos); + return -1; + } + if (!enabledAxes[axis]) { + ALOGW("Axis %d must be enabled before it can be accessed.", axis); + return 0; + } + + int pointerOffset = pointerIndex * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; + int historyValuesOffset = + historyPos * event->pointerCount * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; + return event + ->historicalAxisValues[historyValuesOffset + pointerOffset + axis]; } static struct { - jmethodID getDeviceId; - jmethodID getSource; - jmethodID getAction; + jmethodID getDeviceId; + jmethodID getSource; + jmethodID getAction; - jmethodID getEventTime; - jmethodID getDownTime; + jmethodID getEventTime; + jmethodID getDownTime; - jmethodID getFlags; - jmethodID getMetaState; + jmethodID getFlags; + jmethodID getMetaState; - jmethodID getActionButton; - jmethodID getButtonState; - jmethodID getClassification; - jmethodID getEdgeFlags; + jmethodID getActionButton; + jmethodID getButtonState; + jmethodID getClassification; + jmethodID getEdgeFlags; - jmethodID getHistorySize; - jmethodID getHistoricalEventTime; + jmethodID getHistorySize; + jmethodID getHistoricalEventTime; - jmethodID getPointerCount; - jmethodID getPointerId; + jmethodID getPointerCount; + jmethodID getPointerId; - jmethodID getToolType; + jmethodID getToolType; - jmethodID getRawX; - jmethodID getRawY; - jmethodID getXPrecision; - jmethodID getYPrecision; - jmethodID getAxisValue; + jmethodID getRawX; + jmethodID getRawY; + jmethodID getXPrecision; + jmethodID getYPrecision; + jmethodID getAxisValue; - jmethodID getHistoricalAxisValue; + jmethodID getHistoricalAxisValue; } gMotionEventClassInfo; extern "C" void GameActivityMotionEvent_destroy( GameActivityMotionEvent *c_event) { - delete c_event->historicalAxisValues; - delete c_event->historicalEventTimesMillis; - delete c_event->historicalEventTimesNanos; + delete c_event->historicalAxisValues; + delete c_event->historicalEventTimesMillis; + delete c_event->historicalEventTimesNanos; +} + +static void initMotionEvents(JNIEnv *env) { + int sdkVersion = gamesdk::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.getHistorySize = + env->GetMethodID(motionEventClass, "getHistorySize", "()I"); + gMotionEventClassInfo.getHistoricalEventTime = + env->GetMethodID(motionEventClass, "getHistoricalEventTime", "(I)J"); + + gMotionEventClassInfo.getPointerCount = + env->GetMethodID(motionEventClass, "getPointerCount", "()I"); + gMotionEventClassInfo.getPointerId = + env->GetMethodID(motionEventClass, "getPointerId", "(I)I"); + gMotionEventClassInfo.getToolType = + env->GetMethodID(motionEventClass, "getToolType", "(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"); + + gMotionEventClassInfo.getHistoricalAxisValue = + env->GetMethodID(motionEventClass, "getHistoricalAxisValue", "(III)F"); } 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.getHistorySize = - env->GetMethodID(motionEventClass, "getHistorySize", "()I"); - gMotionEventClassInfo.getHistoricalEventTime = env->GetMethodID( - motionEventClass, "getHistoricalEventTime", "(I)J"); - - gMotionEventClassInfo.getPointerCount = - env->GetMethodID(motionEventClass, "getPointerCount", "()I"); - gMotionEventClassInfo.getPointerId = - env->GetMethodID(motionEventClass, "getPointerId", "(I)I"); - gMotionEventClassInfo.getToolType = - env->GetMethodID(motionEventClass, "getToolType", "(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"); - - gMotionEventClassInfo.getHistoricalAxisValue = env->GetMethodID( - motionEventClass, "getHistoricalAxisValue", "(III)F"); - gMotionEventClassInfoInitialized = true; - } + JNIEnv *env, jobject motionEvent, GameActivityMotionEvent *out_event, + int pointerCount, int historySize) { + 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), + /*toolType=*/ + env->CallIntMethod(motionEvent, gMotionEventClassInfo.getToolType, i), + /*axisValues=*/{0}, + /*rawX=*/gMotionEventClassInfo.getRawX + ? env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getRawX, + i) + : 0, + /*rawY=*/gMotionEventClassInfo.getRawY + ? env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getRawY, + i) + : 0, + }; - 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), - /*toolType=*/ - env->CallIntMethod(motionEvent, gMotionEventClassInfo.getToolType, - 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); - } - } + 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); + } } - - int historySize = - env->CallIntMethod(motionEvent, gMotionEventClassInfo.getHistorySize); - out_event->historySize = historySize; - out_event->historicalAxisValues = - new float[historySize * pointerCount * - GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT]; - out_event->historicalEventTimesMillis = new int64_t[historySize]; - out_event->historicalEventTimesNanos = new int64_t[historySize]; - - for (int historyIndex = 0; historyIndex < historySize; historyIndex++) { - out_event->historicalEventTimesMillis[historyIndex] = - env->CallLongMethod(motionEvent, - gMotionEventClassInfo.getHistoricalEventTime, - historyIndex); - out_event->historicalEventTimesNanos[historyIndex] = - out_event->historicalEventTimesMillis[historyIndex] * 1000000; - for (int i = 0; i < pointerCount; ++i) { - int pointerOffset = i * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; - int historyAxisOffset = historyIndex * pointerCount * - GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; - float *axisValues = - &out_event - ->historicalAxisValues[historyAxisOffset + pointerOffset]; - for (int axisIndex = 0; - axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; - ++axisIndex) { - if (enabledAxes[axisIndex]) { - axisValues[axisIndex] = env->CallFloatMethod( - motionEvent, - gMotionEventClassInfo.getHistoricalAxisValue, axisIndex, - i, historyIndex); - } - } + } + + out_event->historySize = historySize; + out_event->historicalAxisValues = + new float[historySize * pointerCount * + GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT]; + out_event->historicalEventTimesMillis = new int64_t[historySize]; + out_event->historicalEventTimesNanos = new int64_t[historySize]; + + for (int historyIndex = 0; historyIndex < historySize; historyIndex++) { + out_event->historicalEventTimesMillis[historyIndex] = env->CallLongMethod( + motionEvent, gMotionEventClassInfo.getHistoricalEventTime, + historyIndex); + out_event->historicalEventTimesNanos[historyIndex] = + out_event->historicalEventTimesMillis[historyIndex] * 1000000; + for (int i = 0; i < pointerCount; ++i) { + int pointerOffset = i * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; + int historyAxisOffset = + historyIndex * pointerCount * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; + float *axisValues = + &out_event->historicalAxisValues[historyAxisOffset + pointerOffset]; + for (int axisIndex = 0; axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; + ++axisIndex) { + if (enabledAxes[axisIndex]) { + axisValues[axisIndex] = env->CallFloatMethod( + motionEvent, gMotionEventClassInfo.getHistoricalAxisValue, + axisIndex, i, historyIndex); } + } } - - 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 getDeviceId; + jmethodID getSource; + jmethodID getAction; - jmethodID getEventTime; - jmethodID getDownTime; + jmethodID getEventTime; + jmethodID getDownTime; - jmethodID getFlags; - jmethodID getMetaState; + jmethodID getFlags; + jmethodID getMetaState; - jmethodID getModifiers; - jmethodID getRepeatCount; - jmethodID getKeyCode; - jmethodID getScanCode; - //jmethodID getUnicodeChar; + jmethodID getModifiers; + jmethodID getRepeatCount; + jmethodID getKeyCode; + jmethodID getScanCode; + // jmethodID getUnicodeChar; } gKeyEventClassInfo; +static void initKeyEvents(JNIEnv *env) { + int sdkVersion = gamesdk::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"); + gKeyEventClassInfo.getScanCode = + env->GetMethodID(keyEventClass, "getScanCode", "()I"); + //gKeyEventClassInfo.getUnicodeChar = + //env->GetMethodID(keyEventClass, "getUnicodeChar", "()I"); +} + 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"); - gKeyEventClassInfo.getScanCode = - env->GetMethodID(keyEventClass, "getScanCode", "()I"); - //gKeyEventClassInfo.getUnicodeChar = - // env->GetMethodID(keyEventClass, "getUnicodeChar", "()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), + /*scanCode=*/ + env->CallIntMethod(keyEvent, gKeyEventClassInfo.getScanCode) + /*unicodeChar=*/ + // env->CallIntMethod(keyEvent, gKeyEventClassInfo.getUnicodeChar) + }; +} - *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), - /*scanCode=*/ - env->CallIntMethod(keyEvent, gKeyEventClassInfo.getScanCode) - /*unicodeChar=*/ - //env->CallIntMethod(keyEvent, gKeyEventClassInfo.getUnicodeChar) - }; +extern "C" void GameActivityEventsInit(JNIEnv *env) { + initMotionEvents(env); + initKeyEvents(env); } diff --git a/android-activity/game-activity-csrc/game-activity/GameActivityEvents.h b/android-activity/game-activity-csrc/game-activity/GameActivityEvents.h index 015ffbe..2a7982a 100644 --- a/android-activity/game-activity-csrc/game-activity/GameActivityEvents.h +++ b/android-activity/game-activity-csrc/game-activity/GameActivityEvents.h @@ -57,29 +57,29 @@ extern "C" { * \see GameActivityMotionEvent */ typedef struct GameActivityPointerAxes { - int32_t id; - int32_t toolType; - float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT]; - float rawX; - float rawY; + int32_t id; + int32_t toolType; + float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT]; + float rawX; + float rawY; } GameActivityPointerAxes; /** \brief Get the toolType of the pointer. */ inline int32_t GameActivityPointerAxes_getToolType( const GameActivityPointerAxes* pointerInfo) { - return pointerInfo->toolType; + return pointerInfo->toolType; } /** \brief Get the current X coordinate of the pointer. */ inline float GameActivityPointerAxes_getX( const GameActivityPointerAxes* pointerInfo) { - return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X]; + return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X]; } /** \brief Get the current Y coordinate of the pointer. */ inline float GameActivityPointerAxes_getY( const GameActivityPointerAxes* pointerInfo) { - return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y]; + return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y]; } /** @@ -126,44 +126,44 @@ float GameActivityPointerAxes_getAxisValue( inline float GameActivityPointerAxes_getPressure( const GameActivityPointerAxes* pointerInfo) { - return GameActivityPointerAxes_getAxisValue(pointerInfo, - AMOTION_EVENT_AXIS_PRESSURE); + return GameActivityPointerAxes_getAxisValue(pointerInfo, + AMOTION_EVENT_AXIS_PRESSURE); } inline float GameActivityPointerAxes_getSize( const GameActivityPointerAxes* pointerInfo) { - return GameActivityPointerAxes_getAxisValue(pointerInfo, - AMOTION_EVENT_AXIS_SIZE); + return GameActivityPointerAxes_getAxisValue(pointerInfo, + AMOTION_EVENT_AXIS_SIZE); } inline float GameActivityPointerAxes_getTouchMajor( const GameActivityPointerAxes* pointerInfo) { - return GameActivityPointerAxes_getAxisValue(pointerInfo, - AMOTION_EVENT_AXIS_TOUCH_MAJOR); + return GameActivityPointerAxes_getAxisValue(pointerInfo, + AMOTION_EVENT_AXIS_TOUCH_MAJOR); } inline float GameActivityPointerAxes_getTouchMinor( const GameActivityPointerAxes* pointerInfo) { - return GameActivityPointerAxes_getAxisValue(pointerInfo, - AMOTION_EVENT_AXIS_TOUCH_MINOR); + return GameActivityPointerAxes_getAxisValue(pointerInfo, + AMOTION_EVENT_AXIS_TOUCH_MINOR); } inline float GameActivityPointerAxes_getToolMajor( const GameActivityPointerAxes* pointerInfo) { - return GameActivityPointerAxes_getAxisValue(pointerInfo, - AMOTION_EVENT_AXIS_TOOL_MAJOR); + return GameActivityPointerAxes_getAxisValue(pointerInfo, + AMOTION_EVENT_AXIS_TOOL_MAJOR); } inline float GameActivityPointerAxes_getToolMinor( const GameActivityPointerAxes* pointerInfo) { - return GameActivityPointerAxes_getAxisValue(pointerInfo, - AMOTION_EVENT_AXIS_TOOL_MINOR); + return GameActivityPointerAxes_getAxisValue(pointerInfo, + AMOTION_EVENT_AXIS_TOOL_MINOR); } inline float GameActivityPointerAxes_getOrientation( const GameActivityPointerAxes* pointerInfo) { - return GameActivityPointerAxes_getAxisValue(pointerInfo, - AMOTION_EVENT_AXIS_ORIENTATION); + return GameActivityPointerAxes_getAxisValue(pointerInfo, + AMOTION_EVENT_AXIS_ORIENTATION); } /** @@ -171,7 +171,7 @@ inline float GameActivityPointerAxes_getOrientation( */ #if (defined GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE) #define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT \ - GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE + GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE #else #define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8 #endif @@ -183,32 +183,32 @@ inline float GameActivityPointerAxes_getOrientation( * (see https://developer.android.com/reference/android/view/MotionEvent). */ typedef struct GameActivityMotionEvent { - int32_t deviceId; - int32_t source; - int32_t action; + int32_t deviceId; + int32_t source; + int32_t action; - int64_t eventTime; - int64_t downTime; + int64_t eventTime; + int64_t downTime; - int32_t flags; - int32_t metaState; + int32_t flags; + int32_t metaState; - int32_t actionButton; - int32_t buttonState; - int32_t classification; - int32_t edgeFlags; + int32_t actionButton; + int32_t buttonState; + int32_t classification; + int32_t edgeFlags; - uint32_t pointerCount; - GameActivityPointerAxes - pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT]; + uint32_t pointerCount; + GameActivityPointerAxes + pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT]; - int historySize; - int64_t* historicalEventTimesMillis; - int64_t* historicalEventTimesNanos; - float* historicalAxisValues; + int historySize; + int64_t* historicalEventTimesMillis; + int64_t* historicalEventTimesNanos; + float* historicalAxisValues; - float precisionX; - float precisionY; + float precisionX; + float precisionY; } GameActivityMotionEvent; float GameActivityMotionEvent_getHistoricalAxisValue( @@ -217,78 +217,66 @@ float GameActivityMotionEvent_getHistoricalAxisValue( inline int GameActivityMotionEvent_getHistorySize( const GameActivityMotionEvent* event) { - return event->historySize; + return event->historySize; } inline float GameActivityMotionEvent_getHistoricalX( const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { - return GameActivityMotionEvent_getHistoricalAxisValue( - event, AMOTION_EVENT_AXIS_X, pointerIndex, historyPos); + return GameActivityMotionEvent_getHistoricalAxisValue( + event, AMOTION_EVENT_AXIS_X, pointerIndex, historyPos); } inline float GameActivityMotionEvent_getHistoricalY( const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { - return GameActivityMotionEvent_getHistoricalAxisValue( - event, AMOTION_EVENT_AXIS_Y, pointerIndex, historyPos); + return GameActivityMotionEvent_getHistoricalAxisValue( + event, AMOTION_EVENT_AXIS_Y, pointerIndex, historyPos); } inline float GameActivityMotionEvent_getHistoricalPressure( const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { - return GameActivityMotionEvent_getHistoricalAxisValue( - event, AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historyPos); + return GameActivityMotionEvent_getHistoricalAxisValue( + event, AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historyPos); } inline float GameActivityMotionEvent_getHistoricalSize( const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { - return GameActivityMotionEvent_getHistoricalAxisValue( - event, AMOTION_EVENT_AXIS_SIZE, pointerIndex, historyPos); + return GameActivityMotionEvent_getHistoricalAxisValue( + event, AMOTION_EVENT_AXIS_SIZE, pointerIndex, historyPos); } inline float GameActivityMotionEvent_getHistoricalTouchMajor( const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { - return GameActivityMotionEvent_getHistoricalAxisValue( - event, AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historyPos); + return GameActivityMotionEvent_getHistoricalAxisValue( + event, AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historyPos); } inline float GameActivityMotionEvent_getHistoricalTouchMinor( const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { - return GameActivityMotionEvent_getHistoricalAxisValue( - event, AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historyPos); + return GameActivityMotionEvent_getHistoricalAxisValue( + event, AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historyPos); } inline float GameActivityMotionEvent_getHistoricalToolMajor( const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { - return GameActivityMotionEvent_getHistoricalAxisValue( - event, AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historyPos); + return GameActivityMotionEvent_getHistoricalAxisValue( + event, AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historyPos); } inline float GameActivityMotionEvent_getHistoricalToolMinor( const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { - return GameActivityMotionEvent_getHistoricalAxisValue( - event, AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historyPos); + return GameActivityMotionEvent_getHistoricalAxisValue( + event, AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historyPos); } inline float GameActivityMotionEvent_getHistoricalOrientation( const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { - return GameActivityMotionEvent_getHistoricalAxisValue( - event, AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historyPos); + return GameActivityMotionEvent_getHistoricalAxisValue( + event, AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historyPos); } /** \brief Handle the freeing of the GameActivityMotionEvent struct. */ void GameActivityMotionEvent_destroy(GameActivityMotionEvent* c_event); -/** - * \brief Convert a Java `MotionEvent` to a `GameActivityMotionEvent`. - * - * This is done automatically by the GameActivity: see `onTouchEvent` to set - * a callback to consume the received events. - * This function can be used if you re-implement events handling in your own - * activity. - * Ownership of out_event is maintained by the caller. - */ -void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent, - GameActivityMotionEvent* out_event); - /** * \brief Describe a key event that happened on the GameActivity SurfaceView. * @@ -298,35 +286,23 @@ void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent, * nanoseconds in this struct. */ typedef struct GameActivityKeyEvent { - int32_t deviceId; - int32_t source; - int32_t action; + int32_t deviceId; + int32_t source; + int32_t action; - int64_t eventTime; - int64_t downTime; + int64_t eventTime; + int64_t downTime; - int32_t flags; - int32_t metaState; + int32_t flags; + int32_t metaState; - int32_t modifiers; - int32_t repeatCount; - int32_t keyCode; - int32_t scanCode; - //int32_t unicodeChar; + int32_t modifiers; + int32_t repeatCount; + int32_t keyCode; + int32_t scanCode; + // int32_t unicodeChar; } GameActivityKeyEvent; -/** - * \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`. - * - * This is done automatically by the GameActivity: see `onKeyUp` and `onKeyDown` - * to set a callback to consume the received events. - * This function can be used if you re-implement events handling in your own - * activity. - * Ownership of out_event is maintained by the caller. - */ -void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent, - GameActivityKeyEvent* out_event); - #ifdef __cplusplus } #endif diff --git a/android-activity/game-activity-csrc/game-activity/GameActivityLog.h b/android-activity/game-activity-csrc/game-activity/GameActivityLog.h index ba9a9e9..311b3c4 100644 --- a/android-activity/game-activity-csrc/game-activity/GameActivityLog.h +++ b/android-activity/game-activity-csrc/game-activity/GameActivityLog.h @@ -26,7 +26,7 @@ #define ALOGV(...) #else #define ALOGV(...) \ - __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__); + __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__); #endif /* Returns 2nd arg. Used to substitute default value if caller's vararg list @@ -40,21 +40,21 @@ #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)) + __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) +#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__))) + (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__))) #endif /* @@ -62,14 +62,14 @@ */ #ifndef SLOGW #define SLOGW(...) \ - ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) + ((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) +#define SLOGW_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0) #endif /* diff --git a/android-activity/game-activity-csrc/game-activity/native_app_glue/android_native_app_glue.c b/android-activity/game-activity-csrc/game-activity/native_app_glue/android_native_app_glue.c index a7bcb85..48069d9 100644 --- a/android-activity/game-activity-csrc/game-activity/native_app_glue/android_native_app_glue.c +++ b/android-activity/game-activity-csrc/game-activity/native_app_glue/android_native_app_glue.c @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "android_native_app_glue.h" +#include "game-activity/native_app_glue/android_native_app_glue.h" #include #include @@ -29,214 +29,215 @@ #define NATIVE_APP_GLUE_KEY_EVENTS_DEFAULT_BUF_SIZE 4 #define LOGI(...) \ - ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__)) + ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__)) #define LOGE(...) \ - ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__)) + ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__)) #define LOGW(...) \ - ((void)__android_log_print(ANDROID_LOG_WARN, "threaded_app", __VA_ARGS__)) -#define LOGW_ONCE(...) \ - do { \ - static bool alogw_once##__FILE__##__LINE__##__ = true; \ - if (alogw_once##__FILE__##__LINE__##__) { \ - alogw_once##__FILE__##__LINE__##__ = false; \ - LOGW(__VA_ARGS__); \ - } \ - } while (0) + ((void)__android_log_print(ANDROID_LOG_WARN, "threaded_app", __VA_ARGS__)) +#define LOGW_ONCE(...) \ + do { \ + static bool alogw_once##__FILE__##__LINE__##__ = true; \ + if (alogw_once##__FILE__##__LINE__##__) { \ + alogw_once##__FILE__##__LINE__##__ = false; \ + LOGW(__VA_ARGS__); \ + } \ + } while (0) /* 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__)) +#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); + 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)) { - LOGE("No data on command pipe!"); - return -1; - } - if (cmd == APP_CMD_SAVE_STATE) free_saved_state(android_app); - return cmd; + int8_t cmd; + if (read(android_app->msgread, &cmd, sizeof(cmd)) != sizeof(cmd)) { + LOGE("No data on command pipe!"); + return -1; + } + if (cmd == APP_CMD_SAVE_STATE) free_saved_state(android_app); + return cmd; } 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)); + 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_pre_exec_cmd(struct android_app* android_app, int8_t cmd) { - switch (cmd) { - case UNUSED_APP_CMD_INPUT_CHANGED: - LOGV("UNUSED_APP_CMD_INPUT_CHANGED"); - // Do nothing. This can be used in the future to handle AInputQueue - // natively, like done in NativeActivity. - break; - - case APP_CMD_INIT_WINDOW: - LOGV("APP_CMD_INIT_WINDOW"); - 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"); - 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", 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"); - AConfiguration_fromAssetManager( - android_app->config, android_app->activity->assetManager); - print_cur_config(android_app); - break; - - case APP_CMD_DESTROY: - LOGV("APP_CMD_DESTROY"); - android_app->destroyRequested = 1; - break; - } + switch (cmd) { + case UNUSED_APP_CMD_INPUT_CHANGED: + LOGV("UNUSED_APP_CMD_INPUT_CHANGED"); + // Do nothing. This can be used in the future to handle AInputQueue + // natively, like done in NativeActivity. + break; + + case APP_CMD_INIT_WINDOW: + LOGV("APP_CMD_INIT_WINDOW"); + 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"); + 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", 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"); + AConfiguration_fromAssetManager(android_app->config, + android_app->activity->assetManager); + print_cur_config(android_app); + break; + + case APP_CMD_DESTROY: + LOGV("APP_CMD_DESTROY"); + 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"); - 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"); - 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; - } + switch (cmd) { + case APP_CMD_TERM_WINDOW: + LOGV("APP_CMD_TERM_WINDOW"); + 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"); + 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); + LOGV("android_app_destroy!"); + free_saved_state(android_app); + pthread_mutex_lock(&android_app->mutex); - 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. + 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_cmd(struct android_app* app, 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); + 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); } // This is run on a separate thread (i.e: not the main thread). static void* android_app_entry(void* param) { - struct android_app* android_app = (struct android_app*)param; - int input_buf_idx = 0; - - LOGV("android_app_entry called"); - android_app->config = AConfiguration_new(); - LOGV("android_app = %p", android_app); - LOGV("config = %p", android_app->config); - LOGV("activity = %p", android_app->activity); - LOGV("assetmanager = %p", android_app->activity->assetManager); - AConfiguration_fromAssetManager(android_app->config, - android_app->activity->assetManager); - - print_cur_config(android_app); - - /* initialize event buffers */ - for (input_buf_idx = 0; input_buf_idx < NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; input_buf_idx++) { - struct android_input_buffer *buf = &android_app->inputBuffers[input_buf_idx]; - - buf->motionEventsBufferSize = NATIVE_APP_GLUE_MOTION_EVENTS_DEFAULT_BUF_SIZE; - buf->motionEvents = (GameActivityMotionEvent *) malloc(sizeof(GameActivityMotionEvent) * - buf->motionEventsBufferSize); - - buf->keyEventsBufferSize = NATIVE_APP_GLUE_KEY_EVENTS_DEFAULT_BUF_SIZE; - buf->keyEvents = (GameActivityKeyEvent *) malloc(sizeof(GameActivityKeyEvent) * - buf->keyEventsBufferSize); - } + struct android_app* android_app = (struct android_app*)param; + int input_buf_idx = 0; - android_app->cmdPollSource.id = LOOPER_ID_MAIN; - android_app->cmdPollSource.app = android_app; - android_app->cmdPollSource.process = process_cmd; + LOGV("android_app_entry called"); + android_app->config = AConfiguration_new(); + LOGV("android_app = %p", android_app); + LOGV("config = %p", android_app->config); + LOGV("activity = %p", android_app->activity); + LOGV("assetmanager = %p", android_app->activity->assetManager); + AConfiguration_fromAssetManager(android_app->config, + android_app->activity->assetManager); - 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; + print_cur_config(android_app); - pthread_mutex_lock(&android_app->mutex); - android_app->running = 1; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); + /* initialize event buffers */ + for (input_buf_idx = 0; input_buf_idx < NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; + input_buf_idx++) { + struct android_input_buffer* buf = + &android_app->inputBuffers[input_buf_idx]; - _rust_glue_entry(android_app); + buf->motionEventsBufferSize = + NATIVE_APP_GLUE_MOTION_EVENTS_DEFAULT_BUF_SIZE; + buf->motionEvents = (GameActivityMotionEvent*)malloc( + sizeof(GameActivityMotionEvent) * buf->motionEventsBufferSize); - android_app_destroy(android_app); - return NULL; + buf->keyEventsBufferSize = NATIVE_APP_GLUE_KEY_EVENTS_DEFAULT_BUF_SIZE; + buf->keyEvents = (GameActivityKeyEvent*)malloc( + sizeof(GameActivityKeyEvent) * buf->keyEventsBufferSize); + } + + android_app->cmdPollSource.id = LOOPER_ID_MAIN; + android_app->cmdPollSource.app = android_app; + android_app->cmdPollSource.process = process_cmd; + + 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; } // Codes from https://developer.android.com/reference/android/view/KeyEvent @@ -249,13 +250,13 @@ static void* android_app_entry(void* param) { // Double-buffer the key event filter to avoid race condition. static bool default_key_filter(const GameActivityKeyEvent* event) { - // Ignore camera, volume, etc. buttons - return !(event->keyCode == KEY_EVENT_KEYCODE_VOLUME_DOWN || - event->keyCode == KEY_EVENT_KEYCODE_VOLUME_MUTE || - event->keyCode == KEY_EVENT_KEYCODE_VOLUME_UP || - event->keyCode == KEY_EVENT_KEYCODE_CAMERA || - event->keyCode == KEY_EVENT_KEYCODE_ZOOM_IN || - event->keyCode == KEY_EVENT_KEYCODE_ZOOM_OUT); + // Ignore camera, volume, etc. buttons + return !(event->keyCode == KEY_EVENT_KEYCODE_VOLUME_DOWN || + event->keyCode == KEY_EVENT_KEYCODE_VOLUME_MUTE || + event->keyCode == KEY_EVENT_KEYCODE_VOLUME_UP || + event->keyCode == KEY_EVENT_KEYCODE_CAMERA || + event->keyCode == KEY_EVENT_KEYCODE_ZOOM_IN || + event->keyCode == KEY_EVENT_KEYCODE_ZOOM_OUT); } // See @@ -263,8 +264,8 @@ static bool default_key_filter(const GameActivityKeyEvent* event) { #define SOURCE_TOUCHSCREEN 0x00001002 static bool default_motion_filter(const GameActivityMotionEvent* event) { - // Ignore any non-touch events. - return event->source == SOURCE_TOUCHSCREEN; + // Ignore any non-touch events. + return (event->source & SOURCE_TOUCHSCREEN) != 0; } // -------------------------------------------------------------------- @@ -274,427 +275,458 @@ static bool default_motion_filter(const GameActivityMotionEvent* event) { static struct android_app* android_app_create(GameActivity* activity, void* savedState, size_t savedStateSize) { - // struct android_app* android_app = calloc(1, sizeof(struct android_app)); - 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); - } + // struct android_app* android_app = calloc(1, sizeof(struct android_app)); + 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]; - 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]; - - android_app->keyEventFilter = default_key_filter; - android_app->motionEventFilter = default_motion_filter; - - LOGV("Launching android_app_entry in a thread"); - 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); + android_app->keyEventFilter = default_key_filter; + android_app->motionEventFilter = default_motion_filter; - return android_app; + LOGV("Launching android_app_entry in a thread"); + 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", strerror(errno)); - } +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", strerror(errno)); + } } static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) { - LOGV("android_app_set_window called"); - pthread_mutex_lock(&android_app->mutex); - - // NB: we have to consider that the native thread could have already - // (gracefully) exit (setting android_app->destroyed) and so we need - // to be careful to avoid a deadlock waiting for a thread that's - // already exit. - if (android_app->destroyed) { - pthread_mutex_unlock(&android_app->mutex); - return; - } - 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); - } + LOGV("android_app_set_window called"); + pthread_mutex_lock(&android_app->mutex); + + // NB: we have to consider that the native thread could have already + // (gracefully) exit (setting android_app->destroyed) and so we need + // to be careful to avoid a deadlock waiting for a thread that's + // already exit. + if (android_app->destroyed) { pthread_mutex_unlock(&android_app->mutex); + return; + } + 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); - - // NB: we have to consider that the native thread could have already - // (gracefully) exit (setting android_app->destroyed) and so we need - // to be careful to avoid a deadlock waiting for a thread that's - // already exit. - if (!android_app->destroyed) { - android_app_write_cmd(android_app, cmd); - while (android_app->activityState != cmd) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } + pthread_mutex_lock(&android_app->mutex); + + // NB: we have to consider that the native thread could have already + // (gracefully) exit (setting android_app->destroyed) and so we need + // to be careful to avoid a deadlock waiting for a thread that's + // already exit. + if (!android_app->destroyed) { + 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); + } + pthread_mutex_unlock(&android_app->mutex); } static void android_app_free(struct android_app* android_app) { - int input_buf_idx = 0; + int input_buf_idx = 0; - pthread_mutex_lock(&android_app->mutex); + pthread_mutex_lock(&android_app->mutex); - // It's possible that onDestroy is called after we have already 'destroyed' - // the app (via `android_app_destroy` due to `android_main` returning. - // - // In this case `->destroyed` will already be set (so we won't deadlock in - // the loop below) but we still need to close the messaging fds and finish - // freeing the android_app + // It's possible that onDestroy is called after we have already 'destroyed' + // the app (via `android_app_destroy` due to `android_main` returning. + // + // In this case `->destroyed` will already be set (so we won't deadlock in + // the loop below) but we still need to close the messaging fds and finish + // freeing the android_app - 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); + 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); - for (input_buf_idx = 0; input_buf_idx < NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; input_buf_idx++) { - struct android_input_buffer *buf = &android_app->inputBuffers[input_buf_idx]; + for (input_buf_idx = 0; input_buf_idx < NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; + input_buf_idx++) { + struct android_input_buffer* buf = + &android_app->inputBuffers[input_buf_idx]; - android_app_clear_motion_events(buf); - free(buf->motionEvents); - free(buf->keyEvents); - } + android_app_clear_motion_events(buf); + free(buf->motionEvents); + free(buf->keyEvents); + } - close(android_app->msgread); - close(android_app->msgwrite); - pthread_cond_destroy(&android_app->cond); - pthread_mutex_destroy(&android_app->mutex); - free(android_app); + close(android_app->msgread); + close(android_app->msgwrite); + pthread_cond_destroy(&android_app->cond); + pthread_mutex_destroy(&android_app->mutex); + free(android_app); } static inline struct android_app* ToApp(GameActivity* activity) { - return (struct android_app*)activity->instance; + return (struct android_app*)activity->instance; } static void onDestroy(GameActivity* activity) { - LOGV("Destroy: %p", activity); - android_app_free(ToApp(activity)); + LOGV("Destroy: %p", activity); + android_app_free(ToApp(activity)); } static void onStart(GameActivity* activity) { - LOGV("Start: %p", activity); - android_app_set_activity_state(ToApp(activity), APP_CMD_START); + LOGV("Start: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_START); } static void onResume(GameActivity* activity) { - LOGV("Resume: %p", activity); - android_app_set_activity_state(ToApp(activity), APP_CMD_RESUME); + LOGV("Resume: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_RESUME); } static void onSaveInstanceState(GameActivity* activity, SaveInstanceStateRecallback recallback, void* context) { - LOGV("SaveInstanceState: %p", activity); - - struct android_app* android_app = ToApp(activity); - pthread_mutex_lock(&android_app->mutex); - - // NB: we have to consider that the native thread could have already - // (gracefully) exit (setting android_app->destroyed) and so we need - // to be careful to avoid a deadlock waiting for a thread that's - // already exit. - if (android_app->destroyed) { - pthread_mutex_unlock(&android_app->mutex); - return; - } + LOGV("SaveInstanceState: %p", activity); - 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) { - // Tell the Java side about our state. - recallback((const char*)android_app->savedState, - android_app->savedStateSize, context); - // Now we can free it. - free(android_app->savedState); - android_app->savedState = NULL; - android_app->savedStateSize = 0; - } + struct android_app* android_app = ToApp(activity); + pthread_mutex_lock(&android_app->mutex); + // NB: we have to consider that the native thread could have already + // (gracefully) exit (setting android_app->destroyed) and so we need + // to be careful to avoid a deadlock waiting for a thread that's + // already exit. + if (android_app->destroyed) { pthread_mutex_unlock(&android_app->mutex); + return; + } + + 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) { + // Tell the Java side about our state. + recallback((const char*)android_app->savedState, + android_app->savedStateSize, context); + // Now we can free it. + free(android_app->savedState); + android_app->savedState = NULL; + android_app->savedStateSize = 0; + } + + pthread_mutex_unlock(&android_app->mutex); } static void onPause(GameActivity* activity) { - LOGV("Pause: %p", activity); - android_app_set_activity_state(ToApp(activity), APP_CMD_PAUSE); + LOGV("Pause: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_PAUSE); } static void onStop(GameActivity* activity) { - LOGV("Stop: %p", activity); - android_app_set_activity_state(ToApp(activity), APP_CMD_STOP); + LOGV("Stop: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_STOP); } static void onConfigurationChanged(GameActivity* activity) { - LOGV("ConfigurationChanged: %p", activity); - android_app_write_cmd(ToApp(activity), APP_CMD_CONFIG_CHANGED); + LOGV("ConfigurationChanged: %p", activity); + android_app_write_cmd(ToApp(activity), APP_CMD_CONFIG_CHANGED); } static void onTrimMemory(GameActivity* activity, int level) { - LOGV("TrimMemory: %p %d", activity, level); - android_app_write_cmd(ToApp(activity), APP_CMD_LOW_MEMORY); + LOGV("TrimMemory: %p %d", activity, level); + android_app_write_cmd(ToApp(activity), APP_CMD_LOW_MEMORY); } static void onWindowFocusChanged(GameActivity* activity, bool focused) { - LOGV("WindowFocusChanged: %p -- %d", activity, focused); - android_app_write_cmd(ToApp(activity), - focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); + LOGV("WindowFocusChanged: %p -- %d", activity, focused); + android_app_write_cmd(ToApp(activity), + focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); } static void onNativeWindowCreated(GameActivity* activity, ANativeWindow* window) { - LOGV("NativeWindowCreated: %p -- %p", activity, window); - android_app_set_window(ToApp(activity), window); + LOGV("NativeWindowCreated: %p -- %p", activity, window); + android_app_set_window(ToApp(activity), window); } static void onNativeWindowDestroyed(GameActivity* activity, ANativeWindow* window) { - LOGV("NativeWindowDestroyed: %p -- %p", activity, window); - android_app_set_window(ToApp(activity), NULL); + LOGV("NativeWindowDestroyed: %p -- %p", activity, window); + android_app_set_window(ToApp(activity), NULL); } static void onNativeWindowRedrawNeeded(GameActivity* activity, ANativeWindow* window) { - LOGV("NativeWindowRedrawNeeded: %p -- %p", activity, window); - android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_REDRAW_NEEDED); + LOGV("NativeWindowRedrawNeeded: %p -- %p", activity, window); + android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_REDRAW_NEEDED); } static void onNativeWindowResized(GameActivity* activity, ANativeWindow* window, int32_t width, int32_t height) { - LOGV("NativeWindowResized: %p -- %p ( %d x %d )", activity, window, width, - height); - android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_RESIZED); + LOGV("NativeWindowResized: %p -- %p ( %d x %d )", activity, window, width, + height); + android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_RESIZED); } void android_app_set_motion_event_filter(struct android_app* app, android_motion_event_filter filter) { - pthread_mutex_lock(&app->mutex); - app->motionEventFilter = filter; - pthread_mutex_unlock(&app->mutex); + pthread_mutex_lock(&app->mutex); + app->motionEventFilter = filter; + pthread_mutex_unlock(&app->mutex); } bool android_app_input_available_wake_up(struct android_app* app) { - pthread_mutex_lock(&app->mutex); - bool available = app->inputAvailableWakeUp; - app->inputAvailableWakeUp = false; - pthread_mutex_unlock(&app->mutex); - return available; + pthread_mutex_lock(&app->mutex); + bool available = app->inputAvailableWakeUp; + app->inputAvailableWakeUp = false; + pthread_mutex_unlock(&app->mutex); + return available; } // NB: should be called with the android_app->mutex held already static void notifyInput(struct android_app* android_app) { - // Don't spam the mainloop with wake ups if we've already sent one - if (android_app->inputSwapPending) { - return; - } + // Don't spam the mainloop with wake ups if we've already sent one + if (android_app->inputSwapPending) { + return; + } - if (android_app->looper != NULL) { - // for the app thread to know why it received the wake() up - android_app->inputAvailableWakeUp = true; - android_app->inputSwapPending = true; - ALooper_wake(android_app->looper); - } + if (android_app->looper != NULL) { + // for the app thread to know why it received the wake() up + android_app->inputAvailableWakeUp = true; + android_app->inputSwapPending = true; + ALooper_wake(android_app->looper); + } } static bool onTouchEvent(GameActivity* activity, const GameActivityMotionEvent* event) { - struct android_app* android_app = ToApp(activity); - pthread_mutex_lock(&android_app->mutex); - - // NB: we have to consider that the native thread could have already - // (gracefully) exit (setting android_app->destroyed) and so we need - // to be careful to avoid a deadlock waiting for a thread that's - // already exit. - if (android_app->destroyed) { - pthread_mutex_unlock(&android_app->mutex); - return false; - } - - if (android_app->motionEventFilter != NULL && - !android_app->motionEventFilter(event)) { - pthread_mutex_unlock(&android_app->mutex); - return false; - } - - struct android_input_buffer* inputBuffer = - &android_app->inputBuffers[android_app->currentInputBuffer]; - - // Add to the list of active motion events - if (inputBuffer->motionEventsCount >= inputBuffer->motionEventsBufferSize) { - inputBuffer->motionEventsBufferSize *= 2; - inputBuffer->motionEvents = (GameActivityMotionEvent *) realloc(inputBuffer->motionEvents, - sizeof(GameActivityMotionEvent) * inputBuffer->motionEventsBufferSize); + struct android_app* android_app = ToApp(activity); + pthread_mutex_lock(&android_app->mutex); + + // NB: we have to consider that the native thread could have already + // (gracefully) exit (setting android_app->destroyed) and so we need + // to be careful to avoid a deadlock waiting for a thread that's + // already exit. + if (android_app->destroyed) { + pthread_mutex_unlock(&android_app->mutex); + return false; + } - if (inputBuffer->motionEvents == NULL) { - LOGE("onTouchEvent: out of memory"); - abort(); - } + if (android_app->motionEventFilter != NULL && + !android_app->motionEventFilter(event)) { + pthread_mutex_unlock(&android_app->mutex); + return false; + } + + struct android_input_buffer* inputBuffer = + &android_app->inputBuffers[android_app->currentInputBuffer]; + + // Add to the list of active motion events + if (inputBuffer->motionEventsCount >= inputBuffer->motionEventsBufferSize) { + inputBuffer->motionEventsBufferSize *= 2; + inputBuffer->motionEvents = (GameActivityMotionEvent*)realloc( + inputBuffer->motionEvents, + sizeof(GameActivityMotionEvent) * inputBuffer->motionEventsBufferSize); + + if (inputBuffer->motionEvents == NULL) { + LOGE("onTouchEvent: out of memory"); + abort(); } + } - int new_ix = inputBuffer->motionEventsCount; - memcpy(&inputBuffer->motionEvents[new_ix], event, sizeof(GameActivityMotionEvent)); - ++inputBuffer->motionEventsCount; - notifyInput(android_app); + int new_ix = inputBuffer->motionEventsCount; + memcpy(&inputBuffer->motionEvents[new_ix], event, + sizeof(GameActivityMotionEvent)); + ++inputBuffer->motionEventsCount; + notifyInput(android_app); - pthread_mutex_unlock(&android_app->mutex); - return true; + android_app_write_cmd(android_app, APP_CMD_TOUCH_EVENT); + pthread_mutex_unlock(&android_app->mutex); + return true; } struct android_input_buffer* android_app_swap_input_buffers( struct android_app* android_app) { - pthread_mutex_lock(&android_app->mutex); - - struct android_input_buffer* inputBuffer = - &android_app->inputBuffers[android_app->currentInputBuffer]; - - if (inputBuffer->motionEventsCount == 0 && - inputBuffer->keyEventsCount == 0) { - inputBuffer = NULL; - } else { - android_app->currentInputBuffer = - (android_app->currentInputBuffer + 1) % - NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; - } + pthread_mutex_lock(&android_app->mutex); - android_app->inputSwapPending = false; - android_app->inputAvailableWakeUp = false; + struct android_input_buffer* inputBuffer = + &android_app->inputBuffers[android_app->currentInputBuffer]; - pthread_mutex_unlock(&android_app->mutex); + if (inputBuffer->motionEventsCount == 0 && inputBuffer->keyEventsCount == 0) { + inputBuffer = NULL; + } else { + android_app->currentInputBuffer = (android_app->currentInputBuffer + 1) % + NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; + } + + android_app->inputSwapPending = false; + android_app->inputAvailableWakeUp = false; - return inputBuffer; + pthread_mutex_unlock(&android_app->mutex); + + return inputBuffer; } void android_app_clear_motion_events(struct android_input_buffer* inputBuffer) { - // We do not need to lock here if the inputBuffer has already been swapped - // as is handled by the game loop thread - while (inputBuffer->motionEventsCount > 0) { - GameActivityMotionEvent_destroy( - &inputBuffer->motionEvents[inputBuffer->motionEventsCount - 1]); + // We do not need to lock here if the inputBuffer has already been swapped + // as is handled by the game loop thread + while (inputBuffer->motionEventsCount > 0) { + GameActivityMotionEvent_destroy( + &inputBuffer->motionEvents[inputBuffer->motionEventsCount - 1]); - inputBuffer->motionEventsCount--; - } - assert(inputBuffer->motionEventsCount == 0); + inputBuffer->motionEventsCount--; + } + assert(inputBuffer->motionEventsCount == 0); } void android_app_set_key_event_filter(struct android_app* app, android_key_event_filter filter) { - pthread_mutex_lock(&app->mutex); - app->keyEventFilter = filter; - pthread_mutex_unlock(&app->mutex); + pthread_mutex_lock(&app->mutex); + app->keyEventFilter = filter; + pthread_mutex_unlock(&app->mutex); } static bool onKey(GameActivity* activity, const GameActivityKeyEvent* event) { - struct android_app* android_app = ToApp(activity); - pthread_mutex_lock(&android_app->mutex); - - // NB: we have to consider that the native thread could have already - // (gracefully) exit (setting android_app->destroyed) and so we need - // to be careful to avoid a deadlock waiting for a thread that's - // already exit. - if (android_app->destroyed) { - pthread_mutex_unlock(&android_app->mutex); - return false; - } - - if (android_app->keyEventFilter != NULL && - !android_app->keyEventFilter(event)) { - pthread_mutex_unlock(&android_app->mutex); - return false; - } - - struct android_input_buffer* inputBuffer = - &android_app->inputBuffers[android_app->currentInputBuffer]; - - // Add to the list of active key down events - if (inputBuffer->keyEventsCount >= inputBuffer->keyEventsBufferSize) { - inputBuffer->keyEventsBufferSize = inputBuffer->keyEventsBufferSize * 2; - inputBuffer->keyEvents = (GameActivityKeyEvent *) realloc(inputBuffer->keyEvents, - sizeof(GameActivityKeyEvent) * inputBuffer->keyEventsBufferSize); + struct android_app* android_app = ToApp(activity); + pthread_mutex_lock(&android_app->mutex); + + // NB: we have to consider that the native thread could have already + // (gracefully) exit (setting android_app->destroyed) and so we need + // to be careful to avoid a deadlock waiting for a thread that's + // already exit. + if (android_app->destroyed) { + pthread_mutex_unlock(&android_app->mutex); + return false; + } - if (inputBuffer->keyEvents == NULL) { - LOGE("onKey: out of memory"); - abort(); - } + if (android_app->keyEventFilter != NULL && + !android_app->keyEventFilter(event)) { + pthread_mutex_unlock(&android_app->mutex); + return false; + } + + struct android_input_buffer* inputBuffer = + &android_app->inputBuffers[android_app->currentInputBuffer]; + + // Add to the list of active key down events + if (inputBuffer->keyEventsCount >= inputBuffer->keyEventsBufferSize) { + inputBuffer->keyEventsBufferSize = inputBuffer->keyEventsBufferSize * 2; + inputBuffer->keyEvents = (GameActivityKeyEvent*)realloc( + inputBuffer->keyEvents, + sizeof(GameActivityKeyEvent) * inputBuffer->keyEventsBufferSize); + + if (inputBuffer->keyEvents == NULL) { + LOGE("onKey: out of memory"); + abort(); } + } - int new_ix = inputBuffer->keyEventsCount; - memcpy(&inputBuffer->keyEvents[new_ix], event, sizeof(GameActivityKeyEvent)); - ++inputBuffer->keyEventsCount; - notifyInput(android_app); + int new_ix = inputBuffer->keyEventsCount; + memcpy(&inputBuffer->keyEvents[new_ix], event, sizeof(GameActivityKeyEvent)); + ++inputBuffer->keyEventsCount; + notifyInput(android_app); - pthread_mutex_unlock(&android_app->mutex); - return true; + android_app_write_cmd(android_app, APP_CMD_KEY_EVENT); + pthread_mutex_unlock(&android_app->mutex); + return true; } void android_app_clear_key_events(struct android_input_buffer* inputBuffer) { - inputBuffer->keyEventsCount = 0; + inputBuffer->keyEventsCount = 0; } static void onTextInputEvent(GameActivity* activity, const GameTextInputState* state) { - struct android_app* android_app = ToApp(activity); - pthread_mutex_lock(&android_app->mutex); - if (!android_app->destroyed) { - android_app->textInputState = 1; - notifyInput(android_app); - } - pthread_mutex_unlock(&android_app->mutex); + struct android_app* android_app = ToApp(activity); + pthread_mutex_lock(&android_app->mutex); + if (!android_app->destroyed) { + android_app->textInputState = 1; + notifyInput(android_app); + } + pthread_mutex_unlock(&android_app->mutex); } static void onWindowInsetsChanged(GameActivity* activity) { - LOGV("WindowInsetsChanged: %p", activity); - android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_INSETS_CHANGED); + LOGV("WindowInsetsChanged: %p", activity); + android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_INSETS_CHANGED); } -static void onContentRectChanged(GameActivity* activity, const ARect *rect) { - LOGV("ContentRectChanged: %p -- (%d %d) (%d %d)", activity, rect->left, rect->top, - rect->right, rect->bottom); +static void onContentRectChanged(GameActivity* activity, const ARect* rect) { + LOGV("ContentRectChanged: %p -- (%d %d) (%d %d)", activity, rect->left, + rect->top, rect->right, rect->bottom); - struct android_app* android_app = ToApp(activity); + struct android_app* android_app = ToApp(activity); - pthread_mutex_lock(&android_app->mutex); - android_app->contentRect = *rect; + pthread_mutex_lock(&android_app->mutex); + android_app->contentRect = *rect; - android_app_write_cmd(android_app, APP_CMD_CONTENT_RECT_CHANGED); - pthread_mutex_unlock(&android_app->mutex); + android_app_write_cmd(android_app, APP_CMD_CONTENT_RECT_CHANGED); + pthread_mutex_unlock(&android_app->mutex); +} + +static void onSoftwareKeyboardVisibilityChanged(GameActivity* activity, + bool visible) { + LOGV("SoftwareKeyboardVisibilityChanged: %p -- %d", activity, (int)visible); + + struct android_app* android_app = ToApp(activity); + + pthread_mutex_lock(&android_app->mutex); + android_app->softwareKeyboardVisible = visible; + + android_app_write_cmd(android_app, APP_CMD_SOFTWARE_KB_VIS_CHANGED); + pthread_mutex_unlock(&android_app->mutex); +} + +static bool onEditorAction(GameActivity* activity, int action) { + LOGV("EditorAction: %p -- %d", activity, action); + + struct android_app* android_app = ToApp(activity); + + pthread_mutex_lock(&android_app->mutex); + android_app->editorAction = action; + + android_app_write_cmd(android_app, APP_CMD_EDITOR_ACTION); + pthread_mutex_unlock(&android_app->mutex); + return true; } // XXX: This symbol is renamed with a _C suffix and then re-exported from @@ -703,30 +735,31 @@ static void onContentRectChanged(GameActivity* activity, const ARect *rect) { // JNIEXPORT void GameActivity_onCreate_C(GameActivity* activity, void* savedState, - size_t savedStateSize) { - LOGV("Creating: %p", 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->onTouchEvent = onTouchEvent; - activity->callbacks->onKeyDown = onKey; - activity->callbacks->onKeyUp = onKey; - activity->callbacks->onTextInputEvent = onTextInputEvent; - activity->callbacks->onConfigurationChanged = onConfigurationChanged; - activity->callbacks->onTrimMemory = onTrimMemory; - activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; - activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; - activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; - activity->callbacks->onNativeWindowRedrawNeeded = - onNativeWindowRedrawNeeded; - activity->callbacks->onNativeWindowResized = onNativeWindowResized; - activity->callbacks->onWindowInsetsChanged = onWindowInsetsChanged; - activity->callbacks->onContentRectChanged = onContentRectChanged; - LOGV("Callbacks set: %p", activity->callbacks); - - activity->instance = - android_app_create(activity, savedState, savedStateSize); + size_t savedStateSize) { + LOGV("Creating: %p", 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->onTouchEvent = onTouchEvent; + activity->callbacks->onKeyDown = onKey; + activity->callbacks->onKeyUp = onKey; + activity->callbacks->onTextInputEvent = onTextInputEvent; + activity->callbacks->onConfigurationChanged = onConfigurationChanged; + activity->callbacks->onTrimMemory = onTrimMemory; + activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; + activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; + activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; + activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded; + activity->callbacks->onNativeWindowResized = onNativeWindowResized; + activity->callbacks->onWindowInsetsChanged = onWindowInsetsChanged; + activity->callbacks->onContentRectChanged = onContentRectChanged; + activity->callbacks->onSoftwareKeyboardVisibilityChanged = + onSoftwareKeyboardVisibilityChanged; + activity->callbacks->onEditorAction = onEditorAction; + LOGV("Callbacks set: %p", activity->callbacks); + + activity->instance = android_app_create(activity, savedState, savedStateSize); } diff --git a/android-activity/game-activity-csrc/game-activity/native_app_glue/android_native_app_glue.h b/android-activity/game-activity-csrc/game-activity/native_app_glue/android_native_app_glue.h index 6fa5721..cdb1ac8 100644 --- a/android-activity/game-activity-csrc/game-activity/native_app_glue/android_native_app_glue.h +++ b/android-activity/game-activity-csrc/game-activity/native_app_glue/android_native_app_glue.h @@ -27,6 +27,7 @@ #include #include #include +#include #include "game-activity/GameActivity.h" @@ -86,55 +87,54 @@ struct android_app; * 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); + /** + * 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); }; struct android_input_buffer { - /** - * Pointer to a read-only array of GameActivityMotionEvent. - * Only the first motionEventsCount events are valid. - */ - GameActivityMotionEvent *motionEvents; - - /** - * The number of valid motion events in `motionEvents`. - */ - uint64_t motionEventsCount; - - /** - * The size of the `motionEvents` buffer. - */ - uint64_t motionEventsBufferSize; - - /** - * Pointer to a read-only array of GameActivityKeyEvent. - * Only the first keyEventsCount events are valid. - */ - GameActivityKeyEvent *keyEvents; - - /** - * The number of valid "Key" events in `keyEvents`. - */ - uint64_t keyEventsCount; - - /** - * The size of the `keyEvents` buffer. - */ - uint64_t keyEventsBufferSize; + /** + * Pointer to a read-only array of GameActivityMotionEvent. + * Only the first motionEventsCount events are valid. + */ + GameActivityMotionEvent* motionEvents; + + /** + * The number of valid motion events in `motionEvents`. + */ + uint64_t motionEventsCount; + + /** + * The size of the `motionEvents` buffer. + */ + uint64_t motionEventsBufferSize; + + /** + * Pointer to a read-only array of GameActivityKeyEvent. + * Only the first keyEventsCount events are valid. + */ + GameActivityKeyEvent* keyEvents; + + /** + * The number of valid "Key" events in `keyEvents`. + */ + uint64_t keyEventsCount; + + /** + * The size of the `keyEvents` buffer. + */ + uint64_t keyEventsBufferSize; }; /** @@ -164,261 +164,294 @@ typedef bool (*android_motion_event_filter)(const GameActivityMotionEvent*); * Java objects. */ struct android_app { - /** - * An optional pointer to application-defined state. - */ - void* userData; - - /** - * A required callback for processing main app commands (`APP_CMD_*`). - * This is called each frame if there are app commands that need processing. - */ - void (*onAppCmd)(struct android_app* app, int32_t cmd); - - /** The GameActivity object instance that this app is running in. */ - GameActivity* activity; - - /** The current configuration the app is running in. */ - AConfiguration* config; - - /** - * The last activity 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; - - /** - * The size of the activity saved state. It is 0 if `savedState` is NULL. - */ - size_t savedStateSize; - - /** The ALooper associated with the app's thread. */ - ALooper* looper; - - /** 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. - */ - int activityState; - - /** - * This is non-zero when the application's GameActivity is being - * destroyed and waiting for the app thread to complete. - */ - int destroyRequested; + /** + * An optional pointer to application-defined state. + */ + void* userData; + + /** + * A required callback for processing main app commands (`APP_CMD_*`). + * This is called each frame if there are app commands that need processing. + */ + void (*onAppCmd)(struct android_app* app, int32_t cmd); + + /** The GameActivity object instance that this app is running in. */ + GameActivity* activity; + + /** The current configuration the app is running in. */ + AConfiguration* config; + + /** + * The last activity 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; + + /** + * The size of the activity saved state. It is 0 if `savedState` is NULL. + */ + size_t savedStateSize; + + /** The ALooper associated with the app's thread. */ + ALooper* looper; + + /** 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; + + /** + * Whether the software keyboard is visible or not. + */ + bool softwareKeyboardVisible; + + /** + * Last editor action. Valid within APP_CMD_SOFTWARE_KB_VIS_CHANGED handler. + */ + int editorAction; + + /** + * Current state of the app's activity. May be either APP_CMD_START, + * APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP. + */ + int activityState; + + /** + * This is non-zero when the application's GameActivity is being + * destroyed and waiting for the app thread to complete. + */ + int destroyRequested; #define NATIVE_APP_GLUE_MAX_INPUT_BUFFERS 2 - /** - * This is used for buffering input from GameActivity. Once ready, the - * application thread switches the buffers and processes what was - * accumulated. - */ - struct android_input_buffer inputBuffers[NATIVE_APP_GLUE_MAX_INPUT_BUFFERS]; - - int currentInputBuffer; - - /** - * 0 if no text input event is outstanding, 1 if it is. - * Use `GameActivity_getTextInputState` to get information - * about the text entered by the user. - */ - int textInputState; - - // Below are "private" implementation of the glue code. - /** @cond INTERNAL */ - - pthread_mutex_t mutex; - pthread_cond_t cond; - - int msgread; - int msgwrite; - - pthread_t thread; - - struct android_poll_source cmdPollSource; - - int running; - int stateSaved; - int destroyed; - int redrawNeeded; - ANativeWindow* pendingWindow; - ARect pendingContentRect; - - android_key_event_filter keyEventFilter; - android_motion_event_filter motionEventFilter; - - // When new input is received we set both of these flags and use the looper to - // wake up the application mainloop. - // - // To avoid spamming the mainloop with wake ups from lots of input though we - // don't sent a wake up if the inputSwapPending flag is already set. (i.e. - // we already expect input to be processed in a finite amount of time due to - // our previous wake up) - // - // When a wake up is received then we will check this flag (clearing it - // at the same time). If it was set then an InputAvailable event is sent to - // the application - which should lead to all input being processed within - // a finite amount of time. - // - // The next time android_app_swap_input_buffers is called, both flags will be - // cleared. - // - // NB: both of these should only be read with the app mutex held - bool inputAvailableWakeUp; - bool inputSwapPending; - - /** @endcond */ + /** + * This is used for buffering input from GameActivity. Once ready, the + * application thread switches the buffers and processes what was + * accumulated. + */ + struct android_input_buffer inputBuffers[NATIVE_APP_GLUE_MAX_INPUT_BUFFERS]; + + int currentInputBuffer; + + /** + * 0 if no text input event is outstanding, 1 if it is. + * Use `GameActivity_getTextInputState` to get information + * about the text entered by the user. + */ + int textInputState; + + // Below are "private" implementation of the glue code. + /** @cond INTERNAL */ + + pthread_mutex_t mutex; + pthread_cond_t cond; + + int msgread; + int msgwrite; + + pthread_t thread; + + struct android_poll_source cmdPollSource; + + int running; + int stateSaved; + int destroyed; + int redrawNeeded; + ANativeWindow* pendingWindow; + ARect pendingContentRect; + + android_key_event_filter keyEventFilter; + android_motion_event_filter motionEventFilter; + + // When new input is received we set both of these flags and use the looper to + // wake up the application mainloop. + // + // To avoid spamming the mainloop with wake ups from lots of input though we + // don't sent a wake up if the inputSwapPending flag is already set. (i.e. + // we already expect input to be processed in a finite amount of time due to + // our previous wake up) + // + // When a wake up is received then we will check this flag (clearing it + // at the same time). If it was set then an InputAvailable event is sent to + // the application - which should lead to all input being processed within + // a finite amount of time. + // + // The next time android_app_swap_input_buffers is called, both flags will be + // cleared. + // + // NB: both of these should only be read with the app mutex held + bool inputAvailableWakeUp; + bool inputSwapPending; + + /** @endcond */ }; /** * Looper ID of commands coming from the app's main thread, an AInputQueue or * user-defined sources. */ -enum NativeAppGlueLooperId { - /** - * 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, - - /** - * Unused. Reserved for future use when usage of AInputQueue will be - * supported. - */ - LOOPER_ID_INPUT = 2, - - /** - * Start of user-defined ALooper identifiers. - */ - LOOPER_ID_USER = 3, +enum NativeAppGlueLooperId : int8_t { + /** + * 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, + + /** + * Unused. Reserved for future use when usage of AInputQueue will be + * supported. + */ + LOOPER_ID_INPUT = 2, + + /** + * Start of user-defined ALooper identifiers. + */ + LOOPER_ID_USER = 3, }; /** * Commands passed from the application's main Java thread to the game's thread. + * + * Values from 0 to 127 are reserved for this library; values from -128 to -1 + * can be used for custom user's events. */ -enum NativeAppGlueAppCmd { - /** - * Unused. Reserved for future use when usage of AInputQueue will be - * supported. - */ - UNUSED_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, - - /** - * Command from main thread: the app's insets have changed. - */ - APP_CMD_WINDOW_INSETS_CHANGED, +enum NativeAppGlueAppCmd : int8_t { + /** + * Unused. Reserved for future use when usage of AInputQueue will be + * supported. + */ + UNUSED_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 software keyboard was shown or hidden. + */ + APP_CMD_SOFTWARE_KB_VIS_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, + + /** + * Command from main thread: the app's insets have changed. + */ + APP_CMD_WINDOW_INSETS_CHANGED, + + /** + * Command from main thread: an editor action has been triggered. + */ + APP_CMD_EDITOR_ACTION, + + /** + * Command from main thread: a keyboard event has been received. + */ + APP_CMD_KEY_EVENT, + + /** + * Command from main thread: a touch event has been received. + */ + APP_CMD_TOUCH_EVENT, }; @@ -495,6 +528,16 @@ void android_app_set_key_event_filter(struct android_app* app, void android_app_set_motion_event_filter(struct android_app* app, android_motion_event_filter filter); +/** + * You can send your custom events using the function below. + * + * Make sure your custom codes do not overlap with this library's ones. + * + * Values from 0 to 127 are reserved for this library; values from -128 to -1 + * can be used for custom user's events. + */ +void android_app_write_cmd(struct android_app* android_app, int8_t cmd); + /** * Determines if a looper wake up was due to new input becoming available */ diff --git a/android-activity/game-activity-csrc/game-text-input/gamecommon.h b/android-activity/game-activity-csrc/game-text-input/gamecommon.h old mode 100755 new mode 100644 index 38bffff..28f5f52 --- a/android-activity/game-activity-csrc/game-text-input/gamecommon.h +++ b/android-activity/game-activity-csrc/game-text-input/gamecommon.h @@ -27,15 +27,15 @@ * https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type */ typedef enum GameCommonInsetsType { - GAMECOMMON_INSETS_TYPE_CAPTION_BAR = 0, - GAMECOMMON_INSETS_TYPE_DISPLAY_CUTOUT, - GAMECOMMON_INSETS_TYPE_IME, - GAMECOMMON_INSETS_TYPE_MANDATORY_SYSTEM_GESTURES, - GAMECOMMON_INSETS_TYPE_NAVIGATION_BARS, - GAMECOMMON_INSETS_TYPE_STATUS_BARS, - GAMECOMMON_INSETS_TYPE_SYSTEM_BARS, - GAMECOMMON_INSETS_TYPE_SYSTEM_GESTURES, - GAMECOMMON_INSETS_TYPE_TAPABLE_ELEMENT, - GAMECOMMON_INSETS_TYPE_WATERFALL, - GAMECOMMON_INSETS_TYPE_COUNT + GAMECOMMON_INSETS_TYPE_CAPTION_BAR = 0, + GAMECOMMON_INSETS_TYPE_DISPLAY_CUTOUT, + GAMECOMMON_INSETS_TYPE_IME, + GAMECOMMON_INSETS_TYPE_MANDATORY_SYSTEM_GESTURES, + GAMECOMMON_INSETS_TYPE_NAVIGATION_BARS, + GAMECOMMON_INSETS_TYPE_STATUS_BARS, + GAMECOMMON_INSETS_TYPE_SYSTEM_BARS, + GAMECOMMON_INSETS_TYPE_SYSTEM_GESTURES, + GAMECOMMON_INSETS_TYPE_TAPABLE_ELEMENT, + GAMECOMMON_INSETS_TYPE_WATERFALL, + GAMECOMMON_INSETS_TYPE_COUNT } GameCommonInsetsType; diff --git a/android-activity/game-activity-csrc/game-text-input/gametextinput.cpp b/android-activity/game-activity-csrc/game-text-input/gametextinput.cpp old mode 100755 new mode 100644 index b8244fb..97c9eb0 --- a/android-activity/game-activity-csrc/game-text-input/gametextinput.cpp +++ b/android-activity/game-activity-csrc/game-text-input/gametextinput.cpp @@ -30,61 +30,60 @@ static constexpr int32_t DEFAULT_MAX_STRING_SIZE = 1 << 16; // Cache of field ids in the Java GameTextInputState class struct StateClassInfo { - jfieldID text; - jfieldID selectionStart; - jfieldID selectionEnd; - jfieldID composingRegionStart; - jfieldID composingRegionEnd; + jfieldID text; + jfieldID selectionStart; + jfieldID selectionEnd; + jfieldID composingRegionStart; + jfieldID composingRegionEnd; }; // Main GameTextInput object. struct GameTextInput { - public: - GameTextInput(JNIEnv *env, uint32_t max_string_size); - ~GameTextInput(); - void setState(const GameTextInputState &state); - const GameTextInputState &getState() const { return currentState_; } - void setInputConnection(jobject inputConnection); - void processEvent(jobject textInputEvent); - void showIme(uint32_t flags); - void hideIme(uint32_t flags); - void restartInput(); - void setEventCallback(GameTextInputEventCallback callback, void *context); - jobject stateToJava(const GameTextInputState &state) const; - void stateFromJava(jobject textInputEvent, - GameTextInputGetStateCallback callback, - void *context) const; - void setImeInsetsCallback(GameTextInputImeInsetsCallback callback, - void *context); - void processImeInsets(const ARect *insets); - const ARect &getImeInsets() const { return currentInsets_; } - - private: - // Copy string and set other fields - void setStateInner(const GameTextInputState &state); - static void processCallback(void *context, const GameTextInputState *state); - JNIEnv *env_ = nullptr; - // Cached at initialization from - // com/google/androidgamesdk/gametextinput/State. - jclass stateJavaClass_ = nullptr; - // The latest text input update. - GameTextInputState currentState_ = {}; - // An instance of gametextinput.InputConnection. - jclass inputConnectionClass_ = nullptr; - jobject inputConnection_ = nullptr; - jmethodID inputConnectionSetStateMethod_; - jmethodID setSoftKeyboardActiveMethod_; - jmethodID restartInputMethod_; - void (*eventCallback_)(void *context, - const struct GameTextInputState *state) = nullptr; - void *eventCallbackContext_ = nullptr; - void (*insetsCallback_)(void *context, - const struct ARect *insets) = nullptr; - ARect currentInsets_ = {}; - void *insetsCallbackContext_ = nullptr; - StateClassInfo stateClassInfo_ = {}; - // Constant-sized buffer used to store state text. - std::vector stateStringBuffer_; + public: + GameTextInput(JNIEnv *env, uint32_t max_string_size); + ~GameTextInput(); + void setState(const GameTextInputState &state); + const GameTextInputState &getState() const { return currentState_; } + void setInputConnection(jobject inputConnection); + void processEvent(jobject textInputEvent); + void showIme(uint32_t flags); + void hideIme(uint32_t flags); + void restartInput(); + void setEventCallback(GameTextInputEventCallback callback, void *context); + jobject stateToJava(const GameTextInputState &state) const; + void stateFromJava(jobject textInputEvent, + GameTextInputGetStateCallback callback, + void *context) const; + void setImeInsetsCallback(GameTextInputImeInsetsCallback callback, + void *context); + void processImeInsets(const ARect *insets); + const ARect &getImeInsets() const { return currentInsets_; } + + private: + // Copy string and set other fields + void setStateInner(const GameTextInputState &state); + static void processCallback(void *context, const GameTextInputState *state); + JNIEnv *env_ = nullptr; + // Cached at initialization from + // com/google/androidgamesdk/gametextinput/State. + jclass stateJavaClass_ = nullptr; + // The latest text input update. + GameTextInputState currentState_ = {}; + // An instance of gametextinput.InputConnection. + jclass inputConnectionClass_ = nullptr; + jobject inputConnection_ = nullptr; + jmethodID inputConnectionSetStateMethod_; + jmethodID setSoftKeyboardActiveMethod_; + jmethodID restartInputMethod_; + void (*eventCallback_)(void *context, + const struct GameTextInputState *state) = nullptr; + void *eventCallbackContext_ = nullptr; + void (*insetsCallback_)(void *context, const struct ARect *insets) = nullptr; + ARect currentInsets_ = {}; + void *insetsCallbackContext_ = nullptr; + StateClassInfo stateClassInfo_ = {}; + // Constant-sized buffer used to store state text. + std::vector stateStringBuffer_; }; std::unique_ptr s_gameTextInput; @@ -98,8 +97,8 @@ extern "C" { // Convert to a Java structure. jobject currentState_toJava(const GameTextInput *gameTextInput, const GameTextInputState *state) { - if (state == nullptr) return NULL; - return gameTextInput->stateToJava(*state); + if (state == nullptr) return NULL; + return gameTextInput->stateToJava(*state); } // Convert from Java structure. @@ -107,7 +106,7 @@ void currentState_fromJava(const GameTextInput *gameTextInput, jobject textInputEvent, GameTextInputGetStateCallback callback, void *context) { - gameTextInput->stateFromJava(textInputEvent, callback, context); + gameTextInput->stateFromJava(textInputEvent, callback, context); } /////////////////////////////////////////////////////////// @@ -116,74 +115,74 @@ void currentState_fromJava(const GameTextInput *gameTextInput, struct GameTextInput *GameTextInput_init(JNIEnv *env, uint32_t max_string_size) { - if (s_gameTextInput.get() != nullptr) { - __android_log_print(ANDROID_LOG_WARN, LOG_TAG, - "Warning: called GameTextInput_init twice without " - "calling GameTextInput_destroy"); - return s_gameTextInput.get(); - } - // Don't use make_unique, for C++11 compatibility - s_gameTextInput = - std::unique_ptr(new GameTextInput(env, max_string_size)); + if (s_gameTextInput.get() != nullptr) { + __android_log_print(ANDROID_LOG_WARN, LOG_TAG, + "Warning: called GameTextInput_init twice without " + "calling GameTextInput_destroy"); return s_gameTextInput.get(); + } + // Don't use make_unique, for C++11 compatibility + s_gameTextInput = + std::unique_ptr(new GameTextInput(env, max_string_size)); + return s_gameTextInput.get(); } void GameTextInput_destroy(GameTextInput *input) { - if (input == nullptr || s_gameTextInput.get() == nullptr) return; - s_gameTextInput.reset(); + if (input == nullptr || s_gameTextInput.get() == nullptr) return; + s_gameTextInput.reset(); } void GameTextInput_setState(GameTextInput *input, const GameTextInputState *state) { - if (state == nullptr) return; - input->setState(*state); + if (state == nullptr) return; + input->setState(*state); } void GameTextInput_getState(GameTextInput *input, GameTextInputGetStateCallback callback, void *context) { - callback(context, &input->getState()); + callback(context, &input->getState()); } void GameTextInput_setInputConnection(GameTextInput *input, jobject inputConnection) { - input->setInputConnection(inputConnection); + input->setInputConnection(inputConnection); } void GameTextInput_processEvent(GameTextInput *input, jobject textInputEvent) { - input->processEvent(textInputEvent); + input->processEvent(textInputEvent); } void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets) { - input->processImeInsets(insets); + input->processImeInsets(insets); } void GameTextInput_showIme(struct GameTextInput *input, uint32_t flags) { - input->showIme(flags); + input->showIme(flags); } void GameTextInput_hideIme(struct GameTextInput *input, uint32_t flags) { - input->hideIme(flags); + input->hideIme(flags); } void GameTextInput_restartInput(struct GameTextInput *input) { - input->restartInput(); + input->restartInput(); } void GameTextInput_setEventCallback(struct GameTextInput *input, GameTextInputEventCallback callback, void *context) { - input->setEventCallback(callback, context); + input->setEventCallback(callback, context); } void GameTextInput_setImeInsetsCallback(struct GameTextInput *input, GameTextInputImeInsetsCallback callback, void *context) { - input->setImeInsetsCallback(callback, context); + input->setImeInsetsCallback(callback, context); } void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets) { - *insets = input->getImeInsets(); + *insets = input->getImeInsets(); } } // extern "C" @@ -196,182 +195,182 @@ GameTextInput::GameTextInput(JNIEnv *env, uint32_t max_string_size) : env_(env), stateStringBuffer_(max_string_size == 0 ? DEFAULT_MAX_STRING_SIZE : max_string_size) { - stateJavaClass_ = (jclass)env_->NewGlobalRef( - env_->FindClass("com/google/androidgamesdk/gametextinput/State")); - inputConnectionClass_ = (jclass)env_->NewGlobalRef(env_->FindClass( - "com/google/androidgamesdk/gametextinput/InputConnection")); - inputConnectionSetStateMethod_ = - env_->GetMethodID(inputConnectionClass_, "setState", - "(Lcom/google/androidgamesdk/gametextinput/State;)V"); - setSoftKeyboardActiveMethod_ = env_->GetMethodID( - inputConnectionClass_, "setSoftKeyboardActive", "(ZI)V"); - restartInputMethod_ = - env_->GetMethodID(inputConnectionClass_, "restartInput", "()V"); - - stateClassInfo_.text = - env_->GetFieldID(stateJavaClass_, "text", "Ljava/lang/String;"); - stateClassInfo_.selectionStart = - env_->GetFieldID(stateJavaClass_, "selectionStart", "I"); - stateClassInfo_.selectionEnd = - env_->GetFieldID(stateJavaClass_, "selectionEnd", "I"); - stateClassInfo_.composingRegionStart = - env_->GetFieldID(stateJavaClass_, "composingRegionStart", "I"); - stateClassInfo_.composingRegionEnd = - env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "I"); + stateJavaClass_ = (jclass)env_->NewGlobalRef( + env_->FindClass("com/google/androidgamesdk/gametextinput/State")); + inputConnectionClass_ = (jclass)env_->NewGlobalRef(env_->FindClass( + "com/google/androidgamesdk/gametextinput/InputConnection")); + inputConnectionSetStateMethod_ = + env_->GetMethodID(inputConnectionClass_, "setState", + "(Lcom/google/androidgamesdk/gametextinput/State;)V"); + setSoftKeyboardActiveMethod_ = env_->GetMethodID( + inputConnectionClass_, "setSoftKeyboardActive", "(ZI)V"); + restartInputMethod_ = + env_->GetMethodID(inputConnectionClass_, "restartInput", "()V"); + + stateClassInfo_.text = + env_->GetFieldID(stateJavaClass_, "text", "Ljava/lang/String;"); + stateClassInfo_.selectionStart = + env_->GetFieldID(stateJavaClass_, "selectionStart", "I"); + stateClassInfo_.selectionEnd = + env_->GetFieldID(stateJavaClass_, "selectionEnd", "I"); + stateClassInfo_.composingRegionStart = + env_->GetFieldID(stateJavaClass_, "composingRegionStart", "I"); + stateClassInfo_.composingRegionEnd = + env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "I"); } GameTextInput::~GameTextInput() { - if (stateJavaClass_ != NULL) { - env_->DeleteGlobalRef(stateJavaClass_); - stateJavaClass_ = NULL; - } - if (inputConnectionClass_ != NULL) { - env_->DeleteGlobalRef(inputConnectionClass_); - inputConnectionClass_ = NULL; - } - if (inputConnection_ != NULL) { - env_->DeleteGlobalRef(inputConnection_); - inputConnection_ = NULL; - } + if (stateJavaClass_ != NULL) { + env_->DeleteGlobalRef(stateJavaClass_); + stateJavaClass_ = NULL; + } + if (inputConnectionClass_ != NULL) { + env_->DeleteGlobalRef(inputConnectionClass_); + inputConnectionClass_ = NULL; + } + if (inputConnection_ != NULL) { + env_->DeleteGlobalRef(inputConnection_); + inputConnection_ = NULL; + } } void GameTextInput::setState(const GameTextInputState &state) { - if (inputConnection_ == nullptr) return; - jobject jstate = stateToJava(state); - env_->CallVoidMethod(inputConnection_, inputConnectionSetStateMethod_, - jstate); - env_->DeleteLocalRef(jstate); - setStateInner(state); + if (inputConnection_ == nullptr) return; + jobject jstate = stateToJava(state); + env_->CallVoidMethod(inputConnection_, inputConnectionSetStateMethod_, + jstate); + env_->DeleteLocalRef(jstate); + setStateInner(state); } void GameTextInput::setStateInner(const GameTextInputState &state) { - // Check if we're setting using our own string (other parts may be - // different) - if (state.text_UTF8 == currentState_.text_UTF8) { - currentState_ = state; - return; - } - // Otherwise, copy across the string. - auto bytes_needed = - std::min(static_cast(state.text_length + 1), - static_cast(stateStringBuffer_.size())); - currentState_.text_UTF8 = stateStringBuffer_.data(); - std::copy(state.text_UTF8, state.text_UTF8 + bytes_needed - 1, - stateStringBuffer_.data()); - currentState_.text_length = state.text_length; - currentState_.selection = state.selection; - currentState_.composingRegion = state.composingRegion; - stateStringBuffer_[bytes_needed - 1] = 0; + // Check if we're setting using our own string (other parts may be + // different) + if (state.text_UTF8 == currentState_.text_UTF8) { + currentState_ = state; + return; + } + // Otherwise, copy across the string. + auto bytes_needed = + std::min(static_cast(state.text_length + 1), + static_cast(stateStringBuffer_.size())); + currentState_.text_UTF8 = stateStringBuffer_.data(); + std::copy(state.text_UTF8, state.text_UTF8 + bytes_needed - 1, + stateStringBuffer_.data()); + currentState_.text_length = state.text_length; + currentState_.selection = state.selection; + currentState_.composingRegion = state.composingRegion; + stateStringBuffer_[bytes_needed - 1] = 0; } void GameTextInput::setInputConnection(jobject inputConnection) { - if (inputConnection_ != NULL) { - env_->DeleteGlobalRef(inputConnection_); - } - inputConnection_ = env_->NewGlobalRef(inputConnection); + if (inputConnection_ != NULL) { + env_->DeleteGlobalRef(inputConnection_); + } + inputConnection_ = env_->NewGlobalRef(inputConnection); } /*static*/ void GameTextInput::processCallback( void *context, const GameTextInputState *state) { - auto thiz = static_cast(context); - if (state != nullptr) thiz->setStateInner(*state); + auto thiz = static_cast(context); + if (state != nullptr) thiz->setStateInner(*state); } void GameTextInput::processEvent(jobject textInputEvent) { - stateFromJava(textInputEvent, processCallback, this); - if (eventCallback_) { - eventCallback_(eventCallbackContext_, ¤tState_); - } + stateFromJava(textInputEvent, processCallback, this); + if (eventCallback_) { + eventCallback_(eventCallbackContext_, ¤tState_); + } } void GameTextInput::showIme(uint32_t flags) { - if (inputConnection_ == nullptr) return; - env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, true, - flags); + if (inputConnection_ == nullptr) return; + env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, true, + flags); } void GameTextInput::setEventCallback(GameTextInputEventCallback callback, void *context) { - eventCallback_ = callback; - eventCallbackContext_ = context; + eventCallback_ = callback; + eventCallbackContext_ = context; } void GameTextInput::setImeInsetsCallback( GameTextInputImeInsetsCallback callback, void *context) { - insetsCallback_ = callback; - insetsCallbackContext_ = context; + insetsCallback_ = callback; + insetsCallbackContext_ = context; } void GameTextInput::processImeInsets(const ARect *insets) { - currentInsets_ = *insets; - if (insetsCallback_) { - insetsCallback_(insetsCallbackContext_, ¤tInsets_); - } + currentInsets_ = *insets; + if (insetsCallback_) { + insetsCallback_(insetsCallbackContext_, ¤tInsets_); + } } void GameTextInput::hideIme(uint32_t flags) { - if (inputConnection_ == nullptr) return; - env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, false, - flags); + if (inputConnection_ == nullptr) return; + env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, false, + flags); } void GameTextInput::restartInput() { - if (inputConnection_ == nullptr) return; - env_->CallVoidMethod(inputConnection_, restartInputMethod_, false); + if (inputConnection_ == nullptr) return; + env_->CallVoidMethod(inputConnection_, restartInputMethod_, false); } jobject GameTextInput::stateToJava(const GameTextInputState &state) const { - static jmethodID constructor = nullptr; + static jmethodID constructor = nullptr; + if (constructor == nullptr) { + constructor = env_->GetMethodID(stateJavaClass_, "", + "(Ljava/lang/String;IIII)V"); if (constructor == nullptr) { - constructor = env_->GetMethodID(stateJavaClass_, "", - "(Ljava/lang/String;IIII)V"); - if (constructor == nullptr) { - __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, - "Can't find gametextinput.State constructor"); - return nullptr; - } - } - const char *text = state.text_UTF8; - if (text == nullptr) { - static char empty_string[] = ""; - text = empty_string; + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, + "Can't find gametextinput.State constructor"); + return nullptr; } - // Note that this expects 'modified' UTF-8 which is not the same as UTF-8 - // https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8 - jstring jtext = env_->NewStringUTF(text); - jobject jobj = - env_->NewObject(stateJavaClass_, constructor, jtext, - state.selection.start, state.selection.end, - state.composingRegion.start, state.composingRegion.end); - env_->DeleteLocalRef(jtext); - return jobj; + } + const char *text = state.text_UTF8; + if (text == nullptr) { + static char empty_string[] = ""; + text = empty_string; + } + // Note that this expects 'modified' UTF-8 which is not the same as UTF-8 + // https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8 + jstring jtext = env_->NewStringUTF(text); + jobject jobj = + env_->NewObject(stateJavaClass_, constructor, jtext, + state.selection.start, state.selection.end, + state.composingRegion.start, state.composingRegion.end); + env_->DeleteLocalRef(jtext); + return jobj; } void GameTextInput::stateFromJava(jobject textInputEvent, GameTextInputGetStateCallback callback, void *context) const { - jstring text = - (jstring)env_->GetObjectField(textInputEvent, stateClassInfo_.text); - // Note this is 'modified' UTF-8, not true UTF-8. It has no NULLs in it, - // except at the end. It's actually not specified whether the value returned - // by GetStringUTFChars includes a null at the end, but it *seems to* on - // Android. - const char *text_chars = env_->GetStringUTFChars(text, NULL); - int text_len = env_->GetStringUTFLength( - text); // Length in bytes, *not* including the null. - int selectionStart = - env_->GetIntField(textInputEvent, stateClassInfo_.selectionStart); - int selectionEnd = - env_->GetIntField(textInputEvent, stateClassInfo_.selectionEnd); - int composingRegionStart = - env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionStart); - int composingRegionEnd = - env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionEnd); - GameTextInputState state{text_chars, - text_len, - {selectionStart, selectionEnd}, - {composingRegionStart, composingRegionEnd}}; - callback(context, &state); - env_->ReleaseStringUTFChars(text, text_chars); - env_->DeleteLocalRef(text); + jstring text = + (jstring)env_->GetObjectField(textInputEvent, stateClassInfo_.text); + // Note this is 'modified' UTF-8, not true UTF-8. It has no NULLs in it, + // except at the end. It's actually not specified whether the value returned + // by GetStringUTFChars includes a null at the end, but it *seems to* on + // Android. + const char *text_chars = env_->GetStringUTFChars(text, NULL); + int text_len = env_->GetStringUTFLength( + text); // Length in bytes, *not* including the null. + int selectionStart = + env_->GetIntField(textInputEvent, stateClassInfo_.selectionStart); + int selectionEnd = + env_->GetIntField(textInputEvent, stateClassInfo_.selectionEnd); + int composingRegionStart = + env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionStart); + int composingRegionEnd = + env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionEnd); + GameTextInputState state{text_chars, + text_len, + {selectionStart, selectionEnd}, + {composingRegionStart, composingRegionEnd}}; + callback(context, &state); + env_->ReleaseStringUTFChars(text, text_chars); + env_->DeleteLocalRef(text); } diff --git a/android-activity/game-activity-csrc/game-text-input/gametextinput.h b/android-activity/game-activity-csrc/game-text-input/gametextinput.h old mode 100755 new mode 100644 index 8c33da5..2483d11 --- a/android-activity/game-activity-csrc/game-text-input/gametextinput.h +++ b/android-activity/game-activity-csrc/game-text-input/gametextinput.h @@ -36,10 +36,10 @@ extern "C" { #define GAMETEXTINPUT_MAJOR_VERSION 2 #define GAMETEXTINPUT_MINOR_VERSION 0 #define GAMETEXTINPUT_BUGFIX_VERSION 0 -#define GAMETEXTINPUT_PACKED_VERSION \ - ANDROID_GAMESDK_PACKED_VERSION(GAMETEXTINPUT_MAJOR_VERSION, \ - GAMETEXTINPUT_MINOR_VERSION, \ - GAMETEXTINPUT_BUGFIX_VERSION) +#define GAMETEXTINPUT_PACKED_VERSION \ + ANDROID_GAMESDK_PACKED_VERSION(GAMETEXTINPUT_MAJOR_VERSION, \ + GAMETEXTINPUT_MINOR_VERSION, \ + GAMETEXTINPUT_BUGFIX_VERSION) /** * This struct holds a span within a region of text from start (inclusive) to @@ -47,10 +47,10 @@ extern "C" { * start==end. An undefined span is specified with start = end = SPAN_UNDEFINED. */ typedef struct GameTextInputSpan { - /** The start of the region (inclusive). */ - int32_t start; - /** The end of the region (exclusive). */ - int32_t end; + /** The start of the region (inclusive). */ + int32_t start; + /** The end of the region (exclusive). */ + int32_t end; } GameTextInputSpan; /** @@ -66,23 +66,23 @@ enum GameTextInputSpanFlag { SPAN_UNDEFINED = -1 }; * GameTextInput_setState to read and modify the state that an IME is editing. */ typedef struct GameTextInputState { - /** - * Text owned by the state, as a modified UTF-8 string. Null-terminated. - * https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8 - */ - const char *text_UTF8; - /** - * Length in bytes of text_UTF8, *not* including the null at end. - */ - int32_t text_length; - /** - * A selection defined on the text. - */ - GameTextInputSpan selection; - /** - * A composing region defined on the text. - */ - GameTextInputSpan composingRegion; + /** + * Text owned by the state, as a modified UTF-8 string. Null-terminated. + * https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8 + */ + const char *text_UTF8; + /** + * Length in bytes of text_UTF8, *not* including the null at end. + */ + int32_t text_length; + /** + * A selection defined on the text. + */ + GameTextInputSpan selection; + /** + * A composing region defined on the text. + */ + GameTextInputSpan composingRegion; } GameTextInputState; /** @@ -144,13 +144,13 @@ void GameTextInput_destroy(GameTextInput *input); * Flags to be passed to GameTextInput_showIme. */ enum ShowImeFlags { - SHOW_IME_UNDEFINED = 0, // Default value. - SHOW_IMPLICIT = - 1, // Indicates that the user has forced the input method open so it - // should not be closed until they explicitly do so. - SHOW_FORCED = 2 // Indicates that this is an implicit request to show the - // input window, not as the result of a direct request by - // the user. The window may not be shown in this case. + SHOW_IME_UNDEFINED = 0, // Default value. + SHOW_IMPLICIT = + 1, // Indicates that the user has forced the input method open so it + // should not be closed until they explicitly do so. + SHOW_FORCED = 2 // Indicates that this is an implicit request to show the + // input window, not as the result of a direct request by + // the user. The window may not be shown in this case. }; /** @@ -165,13 +165,13 @@ void GameTextInput_showIme(GameTextInput *input, uint32_t flags); * Flags to be passed to GameTextInput_hideIme. */ enum HideImeFlags { - HIDE_IME_UNDEFINED = 0, // Default value. - HIDE_IMPLICIT_ONLY = - 1, // Indicates that the soft input window should only be hidden if it - // was not explicitly shown by the user. - HIDE_NOT_ALWAYS = - 2, // Indicates that the soft input window should normally be hidden, - // unless it was originally shown with SHOW_FORCED. + HIDE_IME_UNDEFINED = 0, // Default value. + HIDE_IMPLICIT_ONLY = + 1, // Indicates that the soft input window should only be hidden if it + // was not explicitly shown by the user. + HIDE_NOT_ALWAYS = + 2, // Indicates that the soft input window should normally be hidden, + // unless it was originally shown with SHOW_FORCED. }; /**