Import unmodified GameActivity 2.0.2 Source

See: https://developer.android.com/jetpack/androidx/releases/games#games-activity_version_20_2
This commit is contained in:
Alexis
2023-06-25 22:28:45 -04:00
committed by Robert Bragg
parent 1a8a92b3fb
commit c471fdf903
11 changed files with 1347 additions and 962 deletions
@@ -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
File diff suppressed because it is too large Load Diff
@@ -36,12 +36,22 @@
#include <stdint.h>
#include <sys/types.h>
#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
@@ -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 <sys/system_properties.h>
#include <string>
#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)};
}
@@ -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 <android/input.h>
#include <jni.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#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
@@ -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 <android/log.h>
#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_
@@ -17,6 +17,7 @@
#include "android_native_app_glue.h"
#include <android/log.h>
#include <assert.h>
#include <errno.h>
#include <jni.h>
#include <stdlib.h>
@@ -24,6 +25,9 @@
#include <time.h>
#include <unistd.h>
#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 =
@@ -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
View File
+13
View File
@@ -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) {
+15
View File
@@ -26,12 +26,21 @@
#include <jni.h>
#include <stdint.h>
#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