diff --git a/android-activity/game-activity-csrc/common/gamesdk_common.h b/android-activity/game-activity-csrc/common/gamesdk_common.h new file mode 100644 index 0000000..d29ac01 --- /dev/null +++ b/android-activity/game-activity-csrc/common/gamesdk_common.h @@ -0,0 +1,41 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This is the main interface to the Android Performance Tuner library, also + * known as Tuning Fork. + * + * It is part of the Android Games SDK and produces best results when integrated + * with the Swappy Frame Pacing Library. + * + * See the documentation at + * https://developer.android.com/games/sdk/performance-tuner/custom-engine for + * more information on using this library in a native Android game. + * + */ + +#pragma once + +// 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)) +// 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 diff --git a/android-activity/game-activity-csrc/game-activity/GameActivity.cpp b/android-activity/game-activity-csrc/game-activity/GameActivity.cpp index a139696..3721b6d 100644 --- a/android-activity/game-activity-csrc/game-activity/GameActivity.cpp +++ b/android-activity/game-activity-csrc/game-activity/GameActivity.cpp @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#define LOG_TAG "GameActivity" #include "GameActivity.h" @@ -39,54 +38,10 @@ #include #include -// 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. +#include "GameActivityLog.h" + namespace { -#if __ANDROID_API__ >= 26 -std::string getSystemPropViaCallback(const char *key, - const char *default_value = "") { - const prop_info *prop = __system_property_find(key); - if (prop == nullptr) { - return default_value; - } - std::string return_value; - auto thunk = [](void *cookie, const char * /*name*/, const char *value, - uint32_t /*serial*/) { - if (value != nullptr) { - std::string *r = static_cast(cookie); - *r = value; - } - }; - __system_property_read_callback(prop, thunk, &return_value); - return return_value; -} -#else -std::string getSystemPropViaGet(const char *key, - const char *default_value = "") { - char buffer[PROP_VALUE_MAX + 1] = ""; // +1 for terminator - int bufferLen = __system_property_get(key, buffer); - if (bufferLen > 0) - return buffer; - else - return ""; -} -#endif - -std::string GetSystemProp(const char *key, const char *default_value = "") { -#if __ANDROID_API__ >= 26 - return getSystemPropViaCallback(key, default_value); -#else - return getSystemPropViaGet(key, default_value); -#endif -} - -int GetSystemPropAsInt(const char *key, int default_value = 0) { - std::string prop = GetSystemProp(key); - return prop == "" ? default_value : strtoll(prop.c_str(), nullptr, 10); -} - struct OwnedGameTextInputState { OwnedGameTextInputState &operator=(const GameTextInputState &rhs) { inner = rhs; @@ -100,93 +55,6 @@ struct OwnedGameTextInputState { } // anonymous namespace -#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); -#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); -#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__); -#ifdef NDEBUG -#define ALOGV(...) -#else -#define ALOGV(...) \ - __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__); -#endif - -/* Returns 2nd arg. Used to substitute default value if caller's vararg list - * is empty. - */ -#define __android_second(first, second, ...) second - -/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise - * returns nothing. - */ -#define __android_rest(first, ...) , ##__VA_ARGS__ - -#define android_printAssert(cond, tag, fmt...) \ - __android_log_assert(cond, tag, \ - __android_second(0, ##fmt, NULL) __android_rest(fmt)) - -#define CONDITION(cond) (__builtin_expect((cond) != 0, 0)) - -#ifndef LOG_ALWAYS_FATAL_IF -#define LOG_ALWAYS_FATAL_IF(cond, ...) \ - ((CONDITION(cond)) \ - ? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \ - : (void)0) -#endif - -#ifndef LOG_ALWAYS_FATAL -#define LOG_ALWAYS_FATAL(...) \ - (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__))) -#endif - -/* - * Simplified macro to send a warning system log message using current LOG_TAG. - */ -#ifndef SLOGW -#define SLOGW(...) \ - ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef SLOGW_IF -#define SLOGW_IF(cond, ...) \ - ((__predict_false(cond)) \ - ? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \ - : (void)0) -#endif - -/* - * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that - * are stripped out of release builds. - */ -#if LOG_NDEBUG - -#ifndef LOG_FATAL_IF -#define LOG_FATAL_IF(cond, ...) ((void)0) -#endif -#ifndef LOG_FATAL -#define LOG_FATAL(...) ((void)0) -#endif - -#else - -#ifndef LOG_FATAL_IF -#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__) -#endif -#ifndef LOG_FATAL -#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__) -#endif - -#endif - -/* - * Assertion that generates a log message when the assertion fails. - * Stripped out of release builds. Uses the current LOG_TAG. - */ -#ifndef ALOG_ASSERT -#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__) -#endif - -#define LOG_TRACE(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) - #ifndef NELEM #define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0]))) #endif @@ -212,6 +80,30 @@ static struct { 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; +} gConfigurationClassInfo; + /* * JNI methods of the WindowInsetsCompat.Type Java class. */ @@ -228,6 +120,7 @@ struct ActivityWork { int32_t cmd; int64_t arg1; int64_t arg2; + int64_t arg3; }; /* @@ -240,19 +133,46 @@ enum { CMD_SET_WINDOW_FLAGS, CMD_SHOW_SOFT_INPUT, CMD_HIDE_SOFT_INPUT, - CMD_SET_SOFT_INPUT_STATE + CMD_SET_SOFT_INPUT_STATE, + CMD_SET_IME_EDITOR_INFO }; +/* + * Last known Configuration values. They may be accessed from the different + * 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; +} gConfiguration; + /* * 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) { +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; LOG_TRACE("write_work: cmd=%d", cmd); restart: @@ -291,12 +211,10 @@ static bool read_work(int fd, ActivityWork *outWork) { * Native state for interacting with the GameActivity class. */ struct NativeCode : public GameActivity { - NativeCode(void *_dlhandle, GameActivity_createFunc *_createFunc) { + NativeCode() { memset((GameActivity *)this, 0, sizeof(GameActivity)); memset(&callbacks, 0, sizeof(callbacks)); memset(&insetsState, 0, sizeof(insetsState)); - dlhandle = _dlhandle; - createActivityFunc = _createFunc; nativeWindow = NULL; mainWorkRead = mainWorkWrite = -1; gameTextInput = NULL; @@ -324,12 +242,6 @@ struct NativeCode : public GameActivity { setSurface(NULL); if (mainWorkRead >= 0) close(mainWorkRead); if (mainWorkWrite >= 0) close(mainWorkWrite); - if (dlhandle != NULL) { - // for now don't unload... we probably should clean this - // up and only keep one open dlhandle per proc, since there - // is really no benefit to unloading the code. - // dlclose(dlhandle); - } } void setSurface(jobject _surface) { @@ -345,9 +257,6 @@ struct NativeCode : public GameActivity { GameActivityCallbacks callbacks; - void *dlhandle; - GameActivity_createFunc *createActivityFunc; - std::string internalDataPathObj; std::string externalDataPathObj; std::string obbPathObj; @@ -374,6 +283,8 @@ struct NativeCode : public GameActivity { ARect insetsState[GAMECOMMON_INSETS_TYPE_COUNT]; }; +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); @@ -476,6 +387,13 @@ static int mainWorkCallback(int fd, int events, void *data) { 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; @@ -485,40 +403,16 @@ static int mainWorkCallback(int fd, int events, void *data) { } // ------------------------------------------------------------------------ - static thread_local std::string g_error_msg; -static jlong loadNativeCode_native(JNIEnv *env, jobject javaGameActivity, - jstring path, jstring funcName, - jstring internalDataDir, jstring obbDir, - jstring externalDataDir, jobject jAssetMgr, - jbyteArray savedState) { - LOG_TRACE("loadNativeCode_native"); - const char *pathStr = env->GetStringUTFChars(path, NULL); +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; - void *handle = dlopen(pathStr, RTLD_LAZY); - - env->ReleaseStringUTFChars(path, pathStr); - - if (handle == nullptr) { - g_error_msg = dlerror(); - ALOGE("GameActivity dlopen(\"%s\") failed: %s", pathStr, - g_error_msg.c_str()); - return 0; - } - - const char *funcStr = env->GetStringUTFChars(funcName, NULL); - code = new NativeCode(handle, - (GameActivity_createFunc *)dlsym(handle, funcStr)); - env->ReleaseStringUTFChars(funcName, funcStr); - - if (code->createActivityFunc == nullptr) { - g_error_msg = dlerror(); - ALOGW("GameActivity_onCreate not found: %s", g_error_msg.c_str()); - delete code; - return 0; - } + code = new NativeCode(); code->looper = ALooper_forThread(); if (code->looper == nullptr) { @@ -588,7 +482,10 @@ static jlong loadNativeCode_native(JNIEnv *env, jobject javaGameActivity, rawSavedState = env->GetByteArrayElements(savedState, NULL); rawSavedSize = env->GetArrayLength(savedState); } - code->createActivityFunc(code, rawSavedState, rawSavedSize); + + readConfigurationValues(code, javaConfig); + + GameActivity_onCreate(code, rawSavedState, rawSavedSize); code->gameTextInput = GameTextInput_init(env, 0); GameTextInput_setEventCallback(code->gameTextInput, @@ -609,9 +506,9 @@ static jstring getDlError_native(JNIEnv *env, jobject javaGameActivity) { return result; } -static void unloadNativeCode_native(JNIEnv *env, jobject javaGameActivity, +static void terminateNativeCode_native(JNIEnv *env, jobject javaGameActivity, jlong handle) { - LOG_TRACE("unloadNativeCode_native"); + LOG_TRACE("terminateNativeCode_native"); if (handle != 0) { NativeCode *code = (NativeCode *)handle; delete code; @@ -695,11 +592,57 @@ static void onStop_native(JNIEnv *env, jobject javaGameActivity, jlong handle) { } } +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); + } + + 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 onConfigurationChanged_native(JNIEnv *env, jobject javaGameActivity, - jlong handle) { + jlong handle, jobject javaNewConfig) { LOG_TRACE("onConfigurationChanged_native"); if (handle != 0) { NativeCode *code = (NativeCode *)handle; + readConfigurationValues(code, javaNewConfig); + if (code->callbacks.onConfigurationChanged != NULL) { code->callbacks.onConfigurationChanged(code); } @@ -776,8 +719,12 @@ static void onSurfaceChanged_native(JNIEnv *env, jobject javaGameActivity, // 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); @@ -817,347 +764,84 @@ static void onSurfaceDestroyed_native(JNIEnv *env, jobject javaGameActivity, } } -static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = { - /* AMOTION_EVENT_AXIS_X */ true, - /* AMOTION_EVENT_AXIS_Y */ true, - // Disable all other axes by default (they can be enabled using - // `GameActivityPointerAxes_enableAxis`). - false}; - -extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) { - if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { - return; - } - - enabledAxes[axis] = true; -} - -extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) { - if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { - return; - } - - enabledAxes[axis] = false; -} - -static bool enabledHistoricalAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = { - // Disable all axes by default (they can be enabled using - // `GameActivityPointerAxes_enableHistoricalAxis`). - false}; - -extern "C" void GameActivityHistoricalPointerAxes_enableAxis(int32_t axis) { - if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { - return; - } - - enabledHistoricalAxes[axis] = true; -} - -extern "C" void GameActivityHistoricalPointerAxes_disableAxis(int32_t axis) { - if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { - return; - } - - enabledHistoricalAxes[axis] = false; -} - extern "C" void GameActivity_setImeEditorInfo(GameActivity *activity, int inputType, int actionId, int imeOptions) { - JNIEnv *env; - if (activity->vm->AttachCurrentThread(&env, NULL) == JNI_OK) { - env->CallVoidMethod(activity->javaGameActivity, - gGameActivityClassInfo.setImeEditorInfoFields, - inputType, actionId, imeOptions); - } + NativeCode *code = static_cast(activity); + write_work(code->mainWorkWrite, CMD_SET_IME_EDITOR_INFO, inputType, + actionId, imeOptions); } -static struct { - jmethodID getDeviceId; - jmethodID getSource; - jmethodID getAction; - - jmethodID getEventTime; - jmethodID getDownTime; - - jmethodID getFlags; - jmethodID getMetaState; - - jmethodID getActionButton; - jmethodID getButtonState; - jmethodID getClassification; - jmethodID getEdgeFlags; - - jmethodID getPointerCount; - jmethodID getPointerId; - jmethodID getToolType; - jmethodID getRawX; - jmethodID getRawY; - jmethodID getXPrecision; - jmethodID getYPrecision; - jmethodID getAxisValue; - - jmethodID getHistorySize; - jmethodID getHistoricalEventTime; - jmethodID getHistoricalAxisValue; -} gMotionEventClassInfo; - -extern "C" int GameActivityMotionEvent_fromJava( - JNIEnv *env, jobject motionEvent, GameActivityMotionEvent *out_event, - GameActivityHistoricalPointerAxes *out_historical) { - static bool gMotionEventClassInfoInitialized = false; - if (!gMotionEventClassInfoInitialized) { - int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk"); - gMotionEventClassInfo = {0}; - jclass motionEventClass = env->FindClass("android/view/MotionEvent"); - gMotionEventClassInfo.getDeviceId = - env->GetMethodID(motionEventClass, "getDeviceId", "()I"); - gMotionEventClassInfo.getSource = - env->GetMethodID(motionEventClass, "getSource", "()I"); - gMotionEventClassInfo.getAction = - env->GetMethodID(motionEventClass, "getAction", "()I"); - gMotionEventClassInfo.getEventTime = - env->GetMethodID(motionEventClass, "getEventTime", "()J"); - gMotionEventClassInfo.getDownTime = - env->GetMethodID(motionEventClass, "getDownTime", "()J"); - gMotionEventClassInfo.getFlags = - env->GetMethodID(motionEventClass, "getFlags", "()I"); - gMotionEventClassInfo.getMetaState = - env->GetMethodID(motionEventClass, "getMetaState", "()I"); - if (sdkVersion >= 23) { - gMotionEventClassInfo.getActionButton = - env->GetMethodID(motionEventClass, "getActionButton", "()I"); - } - if (sdkVersion >= 14) { - gMotionEventClassInfo.getButtonState = - env->GetMethodID(motionEventClass, "getButtonState", "()I"); - } - if (sdkVersion >= 29) { - gMotionEventClassInfo.getClassification = - env->GetMethodID(motionEventClass, "getClassification", "()I"); - } - gMotionEventClassInfo.getEdgeFlags = - env->GetMethodID(motionEventClass, "getEdgeFlags", "()I"); - gMotionEventClassInfo.getPointerCount = - env->GetMethodID(motionEventClass, "getPointerCount", "()I"); - gMotionEventClassInfo.getPointerId = - env->GetMethodID(motionEventClass, "getPointerId", "(I)I"); - 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.getHistorySize = - env->GetMethodID(motionEventClass, "getHistorySize", "()I"); - gMotionEventClassInfo.getHistoricalEventTime = - env->GetMethodID(motionEventClass, "getHistoricalEventTime", "(I)J"); - gMotionEventClassInfo.getHistoricalAxisValue = - env->GetMethodID(motionEventClass, "getHistoricalAxisValue", "(III)F"); - - gMotionEventClassInfoInitialized = true; - } - - int historySize = - env->CallIntMethod(motionEvent, gMotionEventClassInfo.getHistorySize); - historySize = - std::min(historySize, GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT); - - int localEnabledHistoricalAxis[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT]; - int enabledHistoricalAxisCount = 0; - - for (int axisIndex = 0; - axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; - ++axisIndex) { - if (enabledHistoricalAxes[axisIndex]) { - localEnabledHistoricalAxis[enabledHistoricalAxisCount++] = axisIndex; - } - } - out_event->historicalCount = enabledHistoricalAxisCount == 0 ? 0 : historySize; - out_event->historicalStart = 0; // Free for caller to use - - // The historical event times aren't unique per-pointer but for simplicity - // we output a per-pointer event time, copied from here... - int64_t historicalEventTimes[GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT]; - for (int histIndex = 0; histIndex < historySize; ++histIndex) { - historicalEventTimes[histIndex] = - env->CallLongMethod(motionEvent, - gMotionEventClassInfo.getHistoricalEventTime, histIndex) * - 1000000; - } - - 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); - } - } - - if (enabledHistoricalAxisCount > 0) { - for (int histIndex = 0; histIndex < historySize; ++histIndex) { - int pointerHistIndex = historySize * i; - out_historical[pointerHistIndex].eventTime = historicalEventTimes[histIndex]; - for (int c = 0; c < enabledHistoricalAxisCount; ++c) { - int axisIndex = localEnabledHistoricalAxis[c]; - out_historical[pointerHistIndex].axisValues[axisIndex] = - env->CallFloatMethod(motionEvent, - gMotionEventClassInfo.getHistoricalAxisValue, - axisIndex, i, histIndex); - } - } - } - } - - 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); - - return out_event->pointerCount * out_event->historicalCount; +extern "C" int GameActivity_getColorMode(GameActivity *) { + return gConfiguration.colorMode; } -static struct { - jmethodID getDeviceId; - jmethodID getSource; - jmethodID getAction; +extern "C" int GameActivity_getDensityDpi(GameActivity *) { + return gConfiguration.densityDpi; +} - jmethodID getEventTime; - jmethodID getDownTime; +extern "C" float GameActivity_getFontScale(GameActivity *) { + return gConfiguration.fontScale; +} - jmethodID getFlags; - jmethodID getMetaState; +extern "C" int GameActivity_getFontWeightAdjustment(GameActivity *) { + return gConfiguration.fontWeightAdjustment; +} - jmethodID getModifiers; - jmethodID getRepeatCount; - jmethodID getKeyCode; - jmethodID getScanCode; -} gKeyEventClassInfo; +extern "C" int GameActivity_getHardKeyboardHidden(GameActivity *) { + return gConfiguration.hardKeyboardHidden; +} -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"); +extern "C" int GameActivity_getKeyboard(GameActivity *) { + return gConfiguration.keyboard; +} - gKeyEventClassInfoInitialized = true; - } +extern "C" int GameActivity_getKeyboardHidden(GameActivity *) { + return gConfiguration.keyboardHidden; +} - *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)}; +extern "C" int GameActivity_getMcc(GameActivity *) { + return gConfiguration.mcc; +} + +extern "C" int GameActivity_getMnc(GameActivity *) { + return gConfiguration.mnc; +} + +extern "C" int GameActivity_getNavigation(GameActivity *) { + return gConfiguration.navigation; +} + +extern "C" int GameActivity_getNavigationHidden(GameActivity *) { + return gConfiguration.navigationHidden; +} + +extern "C" int GameActivity_getOrientation(GameActivity *) { + return gConfiguration.orientation; +} + +extern "C" int GameActivity_getScreenHeightDp(GameActivity *) { + return gConfiguration.screenHeightDp; +} + +extern "C" int GameActivity_getScreenLayout(GameActivity *) { + return gConfiguration.screenLayout; +} + +extern "C" int GameActivity_getScreenWidthDp(GameActivity *) { + return gConfiguration.screenWidthDp; +} + +extern "C" int GameActivity_getSmallestScreenWidthDp(GameActivity *) { + return gConfiguration.smallestScreenWidthDp; +} + +extern "C" int GameActivity_getTouchscreen(GameActivity *) { + return gConfiguration.touchscreen; +} + +extern "C" int GameActivity_getUIMode(GameActivity *) { + return gConfiguration.uiMode; } static bool onTouchEvent_native(JNIEnv *env, jobject javaGameActivity, @@ -1167,11 +851,9 @@ static bool onTouchEvent_native(JNIEnv *env, jobject javaGameActivity, if (code->callbacks.onTouchEvent == nullptr) return false; static GameActivityMotionEvent c_event; - // Note the actual data is written contiguously as numPointers x historySize - // entries. - static GameActivityHistoricalPointerAxes historical[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT * GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT]; - int historicalLen = GameActivityMotionEvent_fromJava(env, motionEvent, &c_event, historical); - return code->callbacks.onTouchEvent(code, &c_event, historical, historicalLen); + GameActivityMotionEvent_fromJava(env, motionEvent, &c_event); + return code->callbacks.onTouchEvent(code, &c_event); + } static bool onKeyUp_native(JNIEnv *env, jobject javaGameActivity, jlong handle, @@ -1247,19 +929,37 @@ static void setInputConnection_native(JNIEnv *env, jobject activity, 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); + } + } +} + static const JNINativeMethod g_methods[] = { - {"loadNativeCode", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/" - "String;Ljava/lang/String;Landroid/content/res/AssetManager;[B)J", - (void *)loadNativeCode_native}, + {"initializeNativeCode", + "(Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;Landroid/content/res/AssetManager;" + "[BLandroid/content/res/Configuration;)J", + (void *)initializeNativeCode_native}, {"getDlError", "()Ljava/lang/String;", (void *)getDlError_native}, - {"unloadNativeCode", "(J)V", (void *)unloadNativeCode_native}, + {"terminateNativeCode", "(J)V", (void *)terminateNativeCode_native}, {"onStartNative", "(J)V", (void *)onStart_native}, {"onResumeNative", "(J)V", (void *)onResume_native}, {"onSaveInstanceStateNative", "(J)[B", (void *)onSaveInstanceState_native}, {"onPauseNative", "(J)V", (void *)onPause_native}, {"onStopNative", "(J)V", (void *)onStop_native}, - {"onConfigurationChangedNative", "(J)V", + {"onConfigurationChangedNative", "(JLandroid/content/res/Configuration;)V", (void *)onConfigurationChanged_native}, {"onTrimMemoryNative", "(JI)V", (void *)onTrimMemory_native}, {"onWindowFocusChangedNative", "(JZ)V", @@ -1284,13 +984,16 @@ static const JNINativeMethod g_methods[] = { {"setInputConnectionNative", "(JLcom/google/androidgamesdk/gametextinput/InputConnection;)V", (void *)setInputConnection_native}, + {"onContentRectChangedNative", "(JIIII)V", + (void *)onContentRectChangedNative_native}, }; static const char *const kGameActivityPathName = "com/google/androidgamesdk/GameActivity"; static const char *const kInsetsPathName = "androidx/core/graphics/Insets"; - +static const char *const kConfigurationPathName = + "android/content/res/Configuration"; static const char *const kWindowInsetsCompatTypePathName = "androidx/core/view/WindowInsetsCompat$Type"; @@ -1348,12 +1051,59 @@ extern "C" int GameActivity_register(JNIEnv *env) { "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 = @@ -1380,19 +1130,14 @@ extern "C" int GameActivity_register(JNIEnv *env) { NELEM(g_methods)); } -// XXX: This symbol is renamed with a _C suffix and then re-exported from -// Rust because Rust/Cargo don't give us a way to directly export symbols -// from C/C++ code: https://github.com/rust-lang/rfcs/issues/2771 -// -// Register this method so that GameActiviy_register does not need to be called -// manually. -extern "C" jlong Java_com_google_androidgamesdk_GameActivity_loadNativeCode_C( - JNIEnv *env, jobject javaGameActivity, jstring path, jstring funcName, - jstring internalDataDir, jstring obbDir, jstring externalDataDir, - jobject jAssetMgr, jbyteArray savedState) { +extern "C" JNIEXPORT jlong JNICALL +Java_com_google_androidgamesdk_GameActivity_initializeNativeCode( + JNIEnv *env, jobject javaGameActivity, jstring internalDataDir, + jstring obbDir, jstring externalDataDir, jobject jAssetMgr, + jbyteArray savedState, jobject javaConfig) { GameActivity_register(env); - jlong nativeCode = loadNativeCode_native( - env, javaGameActivity, path, funcName, internalDataDir, obbDir, - externalDataDir, jAssetMgr, savedState); + 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 c5abfc0..702f754 100644 --- a/android-activity/game-activity-csrc/game-activity/GameActivity.h +++ b/android-activity/game-activity-csrc/game-activity/GameActivity.h @@ -36,12 +36,22 @@ #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_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) + /** * {@link GameActivityCallbacks} */ @@ -115,199 +125,6 @@ typedef struct GameActivity { const char* obbPath; } GameActivity; -/** - * The maximum number of axes supported in an Android MotionEvent. - * See https://developer.android.com/ndk/reference/group/input. - */ -#define GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48 - -/** - * \brief Describe information about a pointer, found in a - * GameActivityMotionEvent. - * - * You can read values directly from this structure, or use helper functions - * (`GameActivityPointerAxes_getX`, `GameActivityPointerAxes_getY` and - * `GameActivityPointerAxes_getAxisValue`). - * - * The X axis and Y axis are enabled by default but any other axis that you want - * to read **must** be enabled first, using - * `GameActivityPointerAxes_enableAxis`. - * - * \see GameActivityMotionEvent - */ -typedef struct GameActivityPointerAxes { - int32_t id; - int32_t toolType; - float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT]; - float rawX; - float rawY; -} GameActivityPointerAxes; - -typedef struct GameActivityHistoricalPointerAxes { - int64_t eventTime; - float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT]; -} GameActivityHistoricalPointerAxes; - -/** \brief Get the current X coordinate of the pointer. */ -inline float GameActivityPointerAxes_getX( - const GameActivityPointerAxes* pointerInfo) { - 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]; -} - -/** - * \brief Enable the specified axis, so that its value is reported in the - * GameActivityPointerAxes structures stored in a motion event. - * - * You must enable any axis that you want to read, apart from - * `AMOTION_EVENT_AXIS_X` and `AMOTION_EVENT_AXIS_Y` that are enabled by - * default. - * - * If the axis index is out of range, nothing is done. - */ -void GameActivityPointerAxes_enableAxis(int32_t axis); - -/** - * \brief Disable the specified axis. Its value won't be reported in the - * GameActivityPointerAxes structures stored in a motion event anymore. - * - * Apart from X and Y, any axis that you want to read **must** be enabled first, - * using `GameActivityPointerAxes_enableAxis`. - * - * If the axis index is out of range, nothing is done. - */ -void GameActivityPointerAxes_disableAxis(int32_t axis); - -/** - * \brief Enable the specified axis, so that its value is reported in the - * GameActivityHistoricalPointerAxes structures associated with a motion event. - * - * You must enable any axis that you want to read (no axes are enabled by - * default). - * - * If the axis index is out of range, nothing is done. - */ -void GameActivityHistoricalPointerAxes_enableAxis(int32_t axis); - -/** - * \brief Disable the specified axis. Its value won't be reported in the - * GameActivityHistoricalPointerAxes structures associated with motion events - * anymore. - * - * If the axis index is out of range, nothing is done. - */ -void GameActivityHistoricalPointerAxes_disableAxis(int32_t axis); - -/** - * \brief Get the value of the requested axis. - * - * Apart from X and Y, any axis that you want to read **must** be enabled first, - * using `GameActivityPointerAxes_enableAxis`. - * - * Find the valid enums for the axis (`AMOTION_EVENT_AXIS_X`, - * `AMOTION_EVENT_AXIS_Y`, `AMOTION_EVENT_AXIS_PRESSURE`...) - * in https://developer.android.com/ndk/reference/group/input. - * - * @param pointerInfo The structure containing information about the pointer, - * obtained from GameActivityMotionEvent. - * @param axis The axis to get the value from - * @return The value of the axis, or 0 if the axis is invalid or was not - * enabled. - */ -inline float GameActivityPointerAxes_getAxisValue( - GameActivityPointerAxes* pointerInfo, int32_t axis) { - if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { - return 0; - } - - return pointerInfo->axisValues[axis]; -} - -/** - * The maximum number of pointers returned inside a motion event. - */ -#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 -#else -#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8 -#endif - -/** - * The maximum number of historic samples associated with a single motion event. - */ -#if (defined GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT_OVERRIDE) -#define GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT \ - GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT_OVERRIDE -#else -#define GAMEACTIVITY_MAX_NUM_HISTORICAL_IN_MOTION_EVENT 8 -#endif - -/** - * \brief Describe a motion event that happened on the GameActivity SurfaceView. - * - * This is 1:1 mapping to the information contained in a Java `MotionEvent` - * (see https://developer.android.com/reference/android/view/MotionEvent). - */ -typedef struct GameActivityMotionEvent { - int32_t deviceId; - int32_t source; - int32_t action; - - int64_t eventTime; - int64_t downTime; - - int32_t flags; - int32_t metaState; - - int32_t actionButton; - int32_t buttonState; - int32_t classification; - int32_t edgeFlags; - - uint32_t pointerCount; - GameActivityPointerAxes - pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT]; - - float precisionX; - float precisionY; - - int16_t historicalStart; - - // Note the actual buffer of historical data has a length of - // pointerCount * historicalCount, since the historical axis - // data is per-pointer. - int16_t historicalCount; -} GameActivityMotionEvent; - -/** - * \brief Describe a key event that happened on the GameActivity SurfaceView. - * - * This is 1:1 mapping to the information contained in a Java `KeyEvent` - * (see https://developer.android.com/reference/android/view/KeyEvent). - */ -typedef struct GameActivityKeyEvent { - int32_t deviceId; - int32_t source; - int32_t action; - - int64_t eventTime; - int64_t downTime; - - int32_t flags; - int32_t metaState; - - int32_t modifiers; - int32_t repeatCount; - int32_t keyCode; - int32_t scanCode; -} GameActivityKeyEvent; - /** * A function the user should call from their callback with the data, its length * and the library- supplied context. @@ -424,9 +241,7 @@ typedef struct GameActivityCallbacks { * only valid during the callback. */ bool (*onTouchEvent)(GameActivity* activity, - const GameActivityMotionEvent* event, - const GameActivityHistoricalPointerAxes* historical, - int historicalLen); + const GameActivityMotionEvent* event); /** * Callback called for every key down event on the GameActivity SurfaceView. @@ -456,37 +271,14 @@ typedef struct GameActivityCallbacks { * 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); } GameActivityCallbacks; -/** - * \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. On return, the out_event->historicalStart will be zero, and should - * be updated to index into whatever buffer out_historical is copied. - * On return the length of out_historical is - * (out_event->pointerCount x out_event->historicalCount) and is in a - * pointer-major order (i.e. all axis for a pointer are contiguous) - * Ownership of out_event is maintained by the caller. - */ -int GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent, - GameActivityMotionEvent* out_event, - GameActivityHistoricalPointerAxes *out_historical); - -/** - * \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); - /** * This is the function that must be in the native code to instantiate the * application's native activity. It is called with the activity instance (see @@ -811,6 +603,30 @@ void GameActivity_getWindowInsets(GameActivity* activity, void GameActivity_setImeEditorInfo(GameActivity* activity, int inputType, int actionId, int imeOptions); +/** + * These are getters for Configuration class members. They may be called from + * any thread. + */ +int GameActivity_getOrientation(GameActivity* activity); +int GameActivity_getColorMode(GameActivity* activity); +int GameActivity_getDensityDpi(GameActivity* activity); +float GameActivity_getFontScale(GameActivity* activity); +int GameActivity_getFontWeightAdjustment(GameActivity* activity); +int GameActivity_getHardKeyboardHidden(GameActivity* activity); +int GameActivity_getKeyboard(GameActivity* activity); +int GameActivity_getKeyboardHidden(GameActivity* activity); +int GameActivity_getMcc(GameActivity* activity); +int GameActivity_getMnc(GameActivity* activity); +int GameActivity_getNavigation(GameActivity* activity); +int GameActivity_getNavigationHidden(GameActivity* activity); +int GameActivity_getOrientation(GameActivity* activity); +int GameActivity_getScreenHeightDp(GameActivity* activity); +int GameActivity_getScreenLayout(GameActivity* activity); +int GameActivity_getScreenWidthDp(GameActivity* activity); +int GameActivity_getSmallestScreenWidthDp(GameActivity* activity); +int GameActivity_getTouchscreen(GameActivity* activity); +int GameActivity_getUIMode(GameActivity* activity); + #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 new file mode 100644 index 0000000..4142601 --- /dev/null +++ b/android-activity/game-activity-csrc/game-activity/GameActivityEvents.cpp @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GameActivityEvents.h" + +#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 + +static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = { + /* AMOTION_EVENT_AXIS_X */ true, + /* AMOTION_EVENT_AXIS_Y */ true, + // Disable all other axes by default (they can be enabled using + // `GameActivityPointerAxes_enableAxis`). + false}; + +extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) { + if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { + return; + } + + enabledAxes[axis] = true; +} + +float GameActivityPointerAxes_getAxisValue( + const GameActivityPointerAxes *pointerInfo, int32_t axis) { + 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; + } + + return pointerInfo->axisValues[axis]; +} + +extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) { + if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { + return; + } + + 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]; +} + +static struct { + jmethodID getDeviceId; + jmethodID getSource; + jmethodID getAction; + + jmethodID getEventTime; + jmethodID getDownTime; + + jmethodID getFlags; + jmethodID getMetaState; + + jmethodID getActionButton; + jmethodID getButtonState; + jmethodID getClassification; + jmethodID getEdgeFlags; + + jmethodID getHistorySize; + jmethodID getHistoricalEventTime; + + jmethodID getPointerCount; + jmethodID getPointerId; + + jmethodID getToolType; + + jmethodID getRawX; + jmethodID getRawY; + jmethodID getXPrecision; + jmethodID getYPrecision; + jmethodID getAxisValue; + + jmethodID getHistoricalAxisValue; +} gMotionEventClassInfo; + +extern "C" void GameActivityMotionEvent_destroy( + GameActivityMotionEvent *c_event) { + delete c_event->historicalAxisValues; + delete c_event->historicalEventTimesMillis; + delete c_event->historicalEventTimesNanos; +} + +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; + } + + 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); + } + } + } + + 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->deviceId = + env->CallIntMethod(motionEvent, gMotionEventClassInfo.getDeviceId); + out_event->source = + env->CallIntMethod(motionEvent, gMotionEventClassInfo.getSource); + out_event->action = + env->CallIntMethod(motionEvent, gMotionEventClassInfo.getAction); + out_event->eventTime = + env->CallLongMethod(motionEvent, gMotionEventClassInfo.getEventTime) * + 1000000; + out_event->downTime = + env->CallLongMethod(motionEvent, gMotionEventClassInfo.getDownTime) * + 1000000; + out_event->flags = + env->CallIntMethod(motionEvent, gMotionEventClassInfo.getFlags); + out_event->metaState = + env->CallIntMethod(motionEvent, gMotionEventClassInfo.getMetaState); + out_event->actionButton = + gMotionEventClassInfo.getActionButton + ? env->CallIntMethod(motionEvent, + gMotionEventClassInfo.getActionButton) + : 0; + out_event->buttonState = + gMotionEventClassInfo.getButtonState + ? env->CallIntMethod(motionEvent, + gMotionEventClassInfo.getButtonState) + : 0; + out_event->classification = + gMotionEventClassInfo.getClassification + ? env->CallIntMethod(motionEvent, + gMotionEventClassInfo.getClassification) + : 0; + out_event->edgeFlags = + env->CallIntMethod(motionEvent, gMotionEventClassInfo.getEdgeFlags); + out_event->precisionX = + env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getXPrecision); + out_event->precisionY = + env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getYPrecision); +} + +static struct { + jmethodID getDeviceId; + jmethodID getSource; + jmethodID getAction; + + jmethodID getEventTime; + jmethodID getDownTime; + + jmethodID getFlags; + jmethodID getMetaState; + + jmethodID getModifiers; + jmethodID getRepeatCount; + jmethodID getKeyCode; + jmethodID getScanCode; + jmethodID getUnicodeChar; +} gKeyEventClassInfo; + +extern "C" void GameActivityKeyEvent_fromJava(JNIEnv *env, jobject keyEvent, + GameActivityKeyEvent *out_event) { + static bool gKeyEventClassInfoInitialized = false; + if (!gKeyEventClassInfoInitialized) { + int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk"); + gKeyEventClassInfo = {0}; + jclass keyEventClass = env->FindClass("android/view/KeyEvent"); + gKeyEventClassInfo.getDeviceId = + env->GetMethodID(keyEventClass, "getDeviceId", "()I"); + gKeyEventClassInfo.getSource = + env->GetMethodID(keyEventClass, "getSource", "()I"); + gKeyEventClassInfo.getAction = + env->GetMethodID(keyEventClass, "getAction", "()I"); + gKeyEventClassInfo.getEventTime = + env->GetMethodID(keyEventClass, "getEventTime", "()J"); + gKeyEventClassInfo.getDownTime = + env->GetMethodID(keyEventClass, "getDownTime", "()J"); + gKeyEventClassInfo.getFlags = + env->GetMethodID(keyEventClass, "getFlags", "()I"); + gKeyEventClassInfo.getMetaState = + env->GetMethodID(keyEventClass, "getMetaState", "()I"); + if (sdkVersion >= 13) { + gKeyEventClassInfo.getModifiers = + env->GetMethodID(keyEventClass, "getModifiers", "()I"); + } + gKeyEventClassInfo.getRepeatCount = + env->GetMethodID(keyEventClass, "getRepeatCount", "()I"); + gKeyEventClassInfo.getKeyCode = + env->GetMethodID(keyEventClass, "getKeyCode", "()I"); + 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)}; +} diff --git a/android-activity/game-activity-csrc/game-activity/GameActivityEvents.h b/android-activity/game-activity-csrc/game-activity/GameActivityEvents.h new file mode 100644 index 0000000..183c72f --- /dev/null +++ b/android-activity/game-activity-csrc/game-activity/GameActivityEvents.h @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup GameActivity Game Activity Events + * The interface to use Game Activity Events. + * @{ + */ + +/** + * @file GameActivityEvents.h + */ +#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H +#define ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The maximum number of axes supported in an Android MotionEvent. + * See https://developer.android.com/ndk/reference/group/input. + */ +#define GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48 + +/** + * \brief Describe information about a pointer, found in a + * GameActivityMotionEvent. + * + * You can read values directly from this structure, or use helper functions + * (`GameActivityPointerAxes_getX`, `GameActivityPointerAxes_getY` and + * `GameActivityPointerAxes_getAxisValue`). + * + * The X axis and Y axis are enabled by default but any other axis that you want + * to read **must** be enabled first, using + * `GameActivityPointerAxes_enableAxis`. + * + * \see GameActivityMotionEvent + */ +typedef struct GameActivityPointerAxes { + 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; +} + +/** \brief Get the current X coordinate of the pointer. */ +inline float GameActivityPointerAxes_getX( + const GameActivityPointerAxes* pointerInfo) { + 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]; +} + +/** + * \brief Enable the specified axis, so that its value is reported in the + * GameActivityPointerAxes structures stored in a motion event. + * + * You must enable any axis that you want to read, apart from + * `AMOTION_EVENT_AXIS_X` and `AMOTION_EVENT_AXIS_Y` that are enabled by + * default. + * + * If the axis index is out of range, nothing is done. + */ +void GameActivityPointerAxes_enableAxis(int32_t axis); + +/** + * \brief Disable the specified axis. Its value won't be reported in the + * GameActivityPointerAxes structures stored in a motion event anymore. + * + * Apart from X and Y, any axis that you want to read **must** be enabled first, + * using `GameActivityPointerAxes_enableAxis`. + * + * If the axis index is out of range, nothing is done. + */ +void GameActivityPointerAxes_disableAxis(int32_t axis); + +/** + * \brief Get the value of the requested axis. + * + * Apart from X and Y, any axis that you want to read **must** be enabled first, + * using `GameActivityPointerAxes_enableAxis`. + * + * Find the valid enums for the axis (`AMOTION_EVENT_AXIS_X`, + * `AMOTION_EVENT_AXIS_Y`, `AMOTION_EVENT_AXIS_PRESSURE`...) + * in https://developer.android.com/ndk/reference/group/input. + * + * @param pointerInfo The structure containing information about the pointer, + * obtained from GameActivityMotionEvent. + * @param axis The axis to get the value from + * @return The value of the axis, or 0 if the axis is invalid or was not + * enabled. + */ +float GameActivityPointerAxes_getAxisValue( + const GameActivityPointerAxes* pointerInfo, int32_t axis); + +inline float GameActivityPointerAxes_getPressure( + const GameActivityPointerAxes* pointerInfo) { + return GameActivityPointerAxes_getAxisValue(pointerInfo, + AMOTION_EVENT_AXIS_PRESSURE); +} + +inline float GameActivityPointerAxes_getSize( + const GameActivityPointerAxes* pointerInfo) { + return GameActivityPointerAxes_getAxisValue(pointerInfo, + AMOTION_EVENT_AXIS_SIZE); +} + +inline float GameActivityPointerAxes_getTouchMajor( + const GameActivityPointerAxes* pointerInfo) { + 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); +} + +inline float GameActivityPointerAxes_getToolMajor( + const GameActivityPointerAxes* pointerInfo) { + 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); +} + +inline float GameActivityPointerAxes_getOrientation( + const GameActivityPointerAxes* pointerInfo) { + return GameActivityPointerAxes_getAxisValue(pointerInfo, + AMOTION_EVENT_AXIS_ORIENTATION); +} + +/** + * The maximum number of pointers returned inside a motion event. + */ +#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 +#else +#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8 +#endif + +/** + * \brief Describe a motion event that happened on the GameActivity SurfaceView. + * + * This is 1:1 mapping to the information contained in a Java `MotionEvent` + * (see https://developer.android.com/reference/android/view/MotionEvent). + */ +typedef struct GameActivityMotionEvent { + int32_t deviceId; + int32_t source; + int32_t action; + + int64_t eventTime; + int64_t downTime; + + int32_t flags; + int32_t metaState; + + int32_t actionButton; + int32_t buttonState; + int32_t classification; + int32_t edgeFlags; + + uint32_t pointerCount; + GameActivityPointerAxes + pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT]; + + int historySize; + int64_t* historicalEventTimesMillis; + int64_t* historicalEventTimesNanos; + float* historicalAxisValues; + + float precisionX; + float precisionY; +} GameActivityMotionEvent; + +float GameActivityMotionEvent_getHistoricalAxisValue( + const GameActivityMotionEvent* event, int axis, int pointerIndex, + int historyPos); + +inline int GameActivityMotionEvent_getHistorySize( + const GameActivityMotionEvent* event) { + return event->historySize; +} + +inline float GameActivityMotionEvent_getHistoricalX( + const GameActivityMotionEvent* event, int pointerIndex, int 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); +} + +inline float GameActivityMotionEvent_getHistoricalPressure( + const GameActivityMotionEvent* event, int pointerIndex, int 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); +} + +inline float GameActivityMotionEvent_getHistoricalTouchMajor( + const GameActivityMotionEvent* event, int pointerIndex, int 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); +} + +inline float GameActivityMotionEvent_getHistoricalToolMajor( + const GameActivityMotionEvent* event, int pointerIndex, int 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); +} + +inline float GameActivityMotionEvent_getHistoricalOrientation( + const GameActivityMotionEvent* event, int pointerIndex, int 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. + * + * This is 1:1 mapping to the information contained in a Java `KeyEvent` + * (see https://developer.android.com/reference/android/view/KeyEvent). + * The only exception is the event times, which are reported as + * nanoseconds in this struct. + */ +typedef struct GameActivityKeyEvent { + int32_t deviceId; + int32_t source; + int32_t action; + + int64_t eventTime; + int64_t downTime; + + int32_t flags; + int32_t metaState; + + 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 + +/** @} */ + +#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H diff --git a/android-activity/game-activity-csrc/game-activity/GameActivityLog.h b/android-activity/game-activity-csrc/game-activity/GameActivityLog.h new file mode 100644 index 0000000..ba9a9e9 --- /dev/null +++ b/android-activity/game-activity-csrc/game-activity/GameActivityLog.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_ +#define ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_ + +#define LOG_TAG "GameActivity" +#include + +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); +#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); +#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__); +#ifdef NDEBUG +#define ALOGV(...) +#else +#define ALOGV(...) \ + __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__); +#endif + +/* Returns 2nd arg. Used to substitute default value if caller's vararg list + * is empty. + */ +#define __android_second(first, second, ...) second + +/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise + * returns nothing. + */ +#define __android_rest(first, ...) , ##__VA_ARGS__ + +#define android_printAssert(cond, tag, fmt...) \ + __android_log_assert(cond, tag, \ + __android_second(0, ##fmt, NULL) __android_rest(fmt)) + +#define CONDITION(cond) (__builtin_expect((cond) != 0, 0)) + +#ifndef LOG_ALWAYS_FATAL_IF +#define LOG_ALWAYS_FATAL_IF(cond, ...) \ + ((CONDITION(cond)) \ + ? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \ + : (void)0) +#endif + +#ifndef LOG_ALWAYS_FATAL +#define LOG_ALWAYS_FATAL(...) \ + (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__))) +#endif + +/* + * Simplified macro to send a warning system log message using current LOG_TAG. + */ +#ifndef SLOGW +#define SLOGW(...) \ + ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef SLOGW_IF +#define SLOGW_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0) +#endif + +/* + * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that + * are stripped out of release builds. + */ +#if LOG_NDEBUG + +#ifndef LOG_FATAL_IF +#define LOG_FATAL_IF(cond, ...) ((void)0) +#endif +#ifndef LOG_FATAL +#define LOG_FATAL(...) ((void)0) +#endif + +#else + +#ifndef LOG_FATAL_IF +#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__) +#endif +#ifndef LOG_FATAL +#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__) +#endif + +#endif + +/* + * Assertion that generates a log message when the assertion fails. + * Stripped out of release builds. Uses the current LOG_TAG. + */ +#ifndef ALOG_ASSERT +#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__) +#endif + +#define LOG_TRACE(...) + +#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_ 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 0a52bd0..223c762 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 @@ -17,6 +17,7 @@ #include "android_native_app_glue.h" #include +#include #include #include #include @@ -24,6 +25,9 @@ #include #include +#define NATIVE_APP_GLUE_MOTION_EVENTS_DEFAULT_BUF_SIZE 16 +#define NATIVE_APP_GLUE_KEY_EVENTS_DEFAULT_BUF_SIZE 4 + #define LOGI(...) \ ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__)) #define LOGE(...) \ @@ -189,6 +193,7 @@ static void process_cmd(struct android_app* app, // 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(); @@ -201,6 +206,19 @@ static void* android_app_entry(void* param) { 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); + } + android_app->cmdPollSource.id = LOOPER_ID_MAIN; android_app->cmdPollSource.app = android_app; android_app->cmdPollSource.process = process_cmd; @@ -215,7 +233,7 @@ static void* android_app_entry(void* param) { pthread_cond_broadcast(&android_app->cond); pthread_mutex_unlock(&android_app->mutex); - _rust_glue_entry(android_app); + android_main(android_app); android_app_destroy(android_app); return NULL; @@ -308,15 +326,6 @@ 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); } @@ -333,36 +342,31 @@ static void android_app_set_window(struct android_app* android_app, 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); - } + android_app_write_cmd(android_app, cmd); + while (android_app->activityState != cmd) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); } pthread_mutex_unlock(&android_app->mutex); } static void android_app_free(struct android_app* android_app) { + int input_buf_idx = 0; + 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 - 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]; + + 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); @@ -395,17 +399,8 @@ static void onSaveInstanceState(GameActivity* activity, LOGV("SaveInstanceState: %p", activity); struct android_app* android_app = ToApp(activity); + void* savedState = NULL; 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) { @@ -483,47 +478,11 @@ void android_app_set_motion_event_filter(struct android_app* app, pthread_mutex_unlock(&app->mutex); } -bool android_app_input_available_wake_up(struct android_app* app) { - pthread_mutex_lock(&app->mutex); - // TODO: use atomic ops for this - 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; - } - - if (android_app->looper != NULL) { - LOGV("Input Notify: %p", android_app); - // 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, - const GameActivityHistoricalPointerAxes* historical, - int historicalLen) { + 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); @@ -534,35 +493,21 @@ static bool onTouchEvent(GameActivity* activity, &android_app->inputBuffers[android_app->currentInputBuffer]; // Add to the list of active motion events - if (inputBuffer->motionEventsCount < - NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS) { - int new_ix = inputBuffer->motionEventsCount; - memcpy(&inputBuffer->motionEvents[new_ix], event, - sizeof(GameActivityMotionEvent)); - ++inputBuffer->motionEventsCount; + if (inputBuffer->motionEventsCount >= inputBuffer->motionEventsBufferSize) { + inputBuffer->motionEventsBufferSize *= 2; + inputBuffer->motionEvents = (GameActivityMotionEvent *) realloc(inputBuffer->motionEvents, + sizeof(GameActivityMotionEvent) * inputBuffer->motionEventsBufferSize); - if (inputBuffer->historicalSamplesCount + historicalLen <= - NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES) { - - int start_ix = inputBuffer->historicalSamplesCount; - memcpy(&inputBuffer->historicalAxisSamples[start_ix], historical, - sizeof(historical[0]) * historicalLen); - inputBuffer->historicalSamplesCount += event->historicalCount; - - inputBuffer->motionEvents[new_ix].historicalStart = start_ix; - inputBuffer->motionEvents[new_ix].historicalCount = historicalLen; - } else { - inputBuffer->motionEvents[new_ix].historicalCount = 0; + if (inputBuffer->motionEvents == NULL) { + LOGE("onTouchEvent: out of memory"); + abort(); } - - notifyInput(android_app); - } else { - LOGW_ONCE("Motion event will be dropped because the number of unconsumed motion" - " events exceeded NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS (%d). Consider setting" - " NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS_OVERRIDE to a larger value", - NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS); } + int new_ix = inputBuffer->motionEventsCount; + memcpy(&inputBuffer->motionEvents[new_ix], event, sizeof(GameActivityMotionEvent)); + ++inputBuffer->motionEventsCount; + pthread_mutex_unlock(&android_app->mutex); return true; } @@ -583,16 +528,21 @@ struct android_input_buffer* android_app_swap_input_buffers( NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; } - android_app->inputSwapPending = false; - android_app->inputAvailableWakeUp = false; - pthread_mutex_unlock(&android_app->mutex); return inputBuffer; } void android_app_clear_motion_events(struct android_input_buffer* inputBuffer) { - inputBuffer->motionEventsCount = 0; + // 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); } void android_app_set_key_event_filter(struct android_app* app, @@ -606,15 +556,6 @@ 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); @@ -625,20 +566,21 @@ static bool onKey(GameActivity* activity, const GameActivityKeyEvent* event) { &android_app->inputBuffers[android_app->currentInputBuffer]; // Add to the list of active key down events - if (inputBuffer->keyEventsCount < NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS) { - int new_ix = inputBuffer->keyEventsCount; - memcpy(&inputBuffer->keyEvents[new_ix], event, - sizeof(GameActivityKeyEvent)); - ++inputBuffer->keyEventsCount; + if (inputBuffer->keyEventsCount >= inputBuffer->keyEventsBufferSize) { + inputBuffer->keyEventsBufferSize = inputBuffer->keyEventsBufferSize * 2; + inputBuffer->keyEvents = (GameActivityKeyEvent *) realloc(inputBuffer->keyEvents, + sizeof(GameActivityKeyEvent) * inputBuffer->keyEventsBufferSize); - notifyInput(android_app); - } else { - LOGW_ONCE("Key event will be dropped because the number of unconsumed key events exceeded" - " NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS (%d). Consider setting" - " NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS_OVERRIDE to a larger value", - NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS); + 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; + pthread_mutex_unlock(&android_app->mutex); return true; } @@ -651,9 +593,8 @@ 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; - } + + android_app->textInputState = 1; pthread_mutex_unlock(&android_app->mutex); } @@ -662,8 +603,22 @@ static void onWindowInsetsChanged(GameActivity* 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); + + struct android_app* android_app = ToApp(activity); + + 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); +} + + JNIEXPORT -void GameActivity_onCreate_C(GameActivity* activity, void* savedState, +void GameActivity_onCreate(GameActivity* activity, void* savedState, size_t savedStateSize) { LOGV("Creating: %p", activity); activity->callbacks->onDestroy = onDestroy; @@ -685,6 +640,7 @@ void GameActivity_onCreate_C(GameActivity* activity, void* savedState, onNativeWindowRedrawNeeded; activity->callbacks->onNativeWindowResized = onNativeWindowResized; activity->callbacks->onWindowInsetsChanged = onWindowInsetsChanged; + activity->callbacks->onContentRectChanged = onContentRectChanged; LOGV("Callbacks set: %p", activity->callbacks); activity->instance = 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 96968e3..e63026f 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 @@ -30,27 +30,6 @@ #include "game-activity/GameActivity.h" -#if (defined NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS_OVERRIDE) -#define NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS \ - NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS_OVERRIDE -#else -#define NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS 16 -#endif - -#if (defined NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES_OVERRIDE) -#define NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES \ - NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES_OVERRIDE -#else -#define NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES 64 -#endif - -#if (defined NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS_OVERRIDE) -#define NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS \ - NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS_OVERRIDE -#else -#define NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS 4 -#endif - #ifdef __cplusplus extern "C" { #endif @@ -126,10 +105,10 @@ struct android_poll_source { struct android_input_buffer { /** - * Pointer to a read-only array of pointers to GameActivityMotionEvent. + * Pointer to a read-only array of GameActivityMotionEvent. * Only the first motionEventsCount events are valid. */ - GameActivityMotionEvent motionEvents[NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS]; + GameActivityMotionEvent *motionEvents; /** * The number of valid motion events in `motionEvents`. @@ -137,36 +116,25 @@ struct android_input_buffer { uint64_t motionEventsCount; /** - * Pointer to a read-only array of pointers to GameActivityHistoricalPointerAxes. - * - * Only the first historicalSamplesCount samples are valid. - * Refer to event->historicalStart, event->pointerCount and event->historicalCount - * to access the specific samples that relate to an event. - * - * Each slice of samples for one event has a length of - * (event->pointerCount and event->historicalCount) and is in pointer-major - * order so the historic samples for each pointer are contiguous. - * E.g. you would access historic sample index 3 for pointer 2 of an event with: - * - * historicalAxisSamples[event->historicalStart + (event->historicalCount * 2) + 3]; + * The size of the `motionEvents` buffer. */ - GameActivityHistoricalPointerAxes historicalAxisSamples[NATIVE_APP_GLUE_MAX_HISTORICAL_POINTER_SAMPLES]; + uint64_t motionEventsBufferSize; /** - * The number of valid historical samples in `historicalAxisSamples`. - */ - uint64_t historicalSamplesCount; - - /** - * Pointer to a read-only array of pointers to GameActivityKeyEvent. + * Pointer to a read-only array of GameActivityKeyEvent. * Only the first keyEventsCount events are valid. */ - GameActivityKeyEvent keyEvents[NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS]; + GameActivityKeyEvent *keyEvents; /** * The number of valid "Key" events in `keyEvents`. */ uint64_t keyEventsCount; + + /** + * The size of the `keyEvents` buffer. + */ + uint64_t keyEventsBufferSize; }; /** @@ -295,26 +263,6 @@ struct android_app { 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 */ }; @@ -500,10 +448,10 @@ void android_app_clear_motion_events(struct android_input_buffer* inputBuffer); void android_app_clear_key_events(struct android_input_buffer* inputBuffer); /** - * This is a springboard into the Rust glue layer that wraps calling the - * main entry for the app itself. + * This is the function that application code must implement, representing + * the main entry to the app. */ -extern void _rust_glue_entry(struct android_app* app); +extern void android_main(struct android_app* app); /** * Set the filter to use when processing key events. @@ -527,13 +475,6 @@ 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); -/** - * Determines if a looper wake up was due to new input becoming available - */ -bool android_app_input_available_wake_up(struct android_app* app); - -void GameActivity_onCreate_C(GameActivity* activity, void* savedState, - size_t savedStateSize); #ifdef __cplusplus } #endif 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 100644 new mode 100755 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 100644 new mode 100755 index 6a39943..b8244fb --- a/android-activity/game-activity-csrc/game-text-input/gametextinput.cpp +++ b/android-activity/game-activity-csrc/game-text-input/gametextinput.cpp @@ -48,6 +48,7 @@ struct GameTextInput { 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, @@ -73,6 +74,7 @@ struct GameTextInput { jobject inputConnection_ = nullptr; jmethodID inputConnectionSetStateMethod_; jmethodID setSoftKeyboardActiveMethod_; + jmethodID restartInputMethod_; void (*eventCallback_)(void *context, const struct GameTextInputState *state) = nullptr; void *eventCallbackContext_ = nullptr; @@ -164,6 +166,10 @@ void GameTextInput_hideIme(struct GameTextInput *input, uint32_t flags) { input->hideIme(flags); } +void GameTextInput_restartInput(struct GameTextInput *input) { + input->restartInput(); +} + void GameTextInput_setEventCallback(struct GameTextInput *input, GameTextInputEventCallback callback, void *context) { @@ -199,6 +205,8 @@ GameTextInput::GameTextInput(JNIEnv *env, uint32_t max_string_size) "(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;"); @@ -307,6 +315,11 @@ void GameTextInput::hideIme(uint32_t flags) { flags); } +void GameTextInput::restartInput() { + if (inputConnection_ == nullptr) return; + env_->CallVoidMethod(inputConnection_, restartInputMethod_, false); +} + jobject GameTextInput::stateToJava(const GameTextInputState &state) const { static jmethodID constructor = nullptr; if (constructor == nullptr) { 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 100644 new mode 100755 index a85265e..8c33da5 --- a/android-activity/game-activity-csrc/game-text-input/gametextinput.h +++ b/android-activity/game-activity-csrc/game-text-input/gametextinput.h @@ -26,12 +26,21 @@ #include #include +#include "common/gamesdk_common.h" #include "gamecommon.h" #ifdef __cplusplus extern "C" { #endif +#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) + /** * This struct holds a span within a region of text from start (inclusive) to * end (exclusive). An empty span or cursor position is specified with @@ -173,6 +182,12 @@ enum HideImeFlags { */ void GameTextInput_hideIme(GameTextInput *input, uint32_t flags); +/** + * Restarts the input method. Calls InputMethodManager.restartInput(). + * @param input A valid GameTextInput library handle. + */ +void GameTextInput_restartInput(GameTextInput *input); + /** * Call a callback with the current GameTextInput state, which may have been * modified by changes in the IME and calls to GameTextInput_setState. We use a