integrate gamesdk source code (#248)

* integrate gamesdk source code

* refine
This commit is contained in:
bofeng-song 2022-04-19 16:43:33 +08:00 committed by GitHub
parent 12edb5dd27
commit b0c2516d5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 9540 additions and 0 deletions

View File

@ -47,6 +47,21 @@ endif()
# add dependent boost libs
include(${CMAKE_CURRENT_LIST_DIR}/boost-source/boost.cmake)
if(ANDROID)
set(CC_GAME_ACTIVITY_SOURCES
${CMAKE_CURRENT_LIST_DIR}/android-gamesdk/GameActivity/game-activity/include/game-activity/GameActivity.cpp
${CMAKE_CURRENT_LIST_DIR}/android-gamesdk/GameActivity/game-activity/include/game-activity/native_app_glue/android_native_app_glue.c
${CMAKE_CURRENT_LIST_DIR}/android-gamesdk/GameActivity/game-activity/include/game-text-input/gametextinput.cpp
)
include(${CMAKE_CURRENT_LIST_DIR}/android-gamesdk/GameController/CMakeLists.txt)
list(APPEND CC_EXTERNAL_SOURCES
${CC_GAME_ACTIVITY_SOURCES}
)
include_directories(${CMAKE_CURRENT_LIST_DIR}/android-gamesdk/GameActivity/game-activity/include)
endif()
if(ANDROID OR OHOS)
include(${CMAKE_CURRENT_LIST_DIR}/pvmp3dec/CMakeLists.txt)

View File

@ -0,0 +1,769 @@
/*
* Copyright (C) 2021 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
* The interface to use GameActivity.
* @{
*/
/**
* @file GameActivity.h
*/
#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_H
#define ANDROID_GAME_SDK_GAME_ACTIVITY_H
#include <android/asset_manager.h>
#include <android/input.h>
#include <android/native_window.h>
#include <android/rect.h>
#include <jni.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include "game-text-input/gametextinput.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* {@link GameActivityCallbacks}
*/
struct GameActivityCallbacks;
/**
* This structure defines the native side of an android.app.GameActivity.
* It is created by the framework, and handed to the application's native
* code as it is being launched.
*/
typedef struct GameActivity {
/**
* Pointer to the callback function table of the native application.
* You can set the functions here to your own callbacks. The callbacks
* pointer itself here should not be changed; it is allocated and managed
* for you by the framework.
*/
struct GameActivityCallbacks* callbacks;
/**
* The global handle on the process's Java VM.
*/
JavaVM* vm;
/**
* JNI context for the main thread of the app. Note that this field
* can ONLY be used from the main thread of the process; that is, the
* thread that calls into the GameActivityCallbacks.
*/
JNIEnv* env;
/**
* The GameActivity object handle.
*/
jobject javaGameActivity;
/**
* Path to this application's internal data directory.
*/
const char* internalDataPath;
/**
* Path to this application's external (removable/mountable) data directory.
*/
const char* externalDataPath;
/**
* The platform's SDK version code.
*/
int32_t sdkVersion;
/**
* This is the native instance of the application. It is not used by
* the framework, but can be set by the application to its own instance
* state.
*/
void* instance;
/**
* Pointer to the Asset Manager instance for the application. The
* application uses this to access binary assets bundled inside its own .apk
* file.
*/
AAssetManager* assetManager;
/**
* Available starting with Honeycomb: path to the directory containing
* the application's OBB files (if any). If the app doesn't have any
* OBB files, this directory may not exist.
*/
const char* obbPath;
} 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;
float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
float rawX;
float rawY;
} GameActivityPointerAxes;
/** \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.
*/
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
/**
* \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;
} 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;
} GameActivityKeyEvent;
/**
* A function the user should call from their callback with the data, its length
* and the library- supplied context.
*/
typedef void (*SaveInstanceStateRecallback)(const char* bytes, int len,
void* context);
/**
* These are the callbacks the framework makes into a native application.
* All of these callbacks happen on the main thread of the application.
* By default, all callbacks are NULL; set to a pointer to your own function
* to have it called.
*/
typedef struct GameActivityCallbacks {
/**
* GameActivity has started. See Java documentation for Activity.onStart()
* for more information.
*/
void (*onStart)(GameActivity* activity);
/**
* GameActivity has resumed. See Java documentation for Activity.onResume()
* for more information.
*/
void (*onResume)(GameActivity* activity);
/**
* The framework is asking GameActivity to save its current instance state.
* See the Java documentation for Activity.onSaveInstanceState() for more
* information. The user should call the recallback with their data, its
* length and the provided context; they retain ownership of the data. Note
* that the saved state will be persisted, so it can not contain any active
* entities (pointers to memory, file descriptors, etc).
*/
void (*onSaveInstanceState)(GameActivity* activity,
SaveInstanceStateRecallback recallback,
void* context);
/**
* GameActivity has paused. See Java documentation for Activity.onPause()
* for more information.
*/
void (*onPause)(GameActivity* activity);
/**
* GameActivity has stopped. See Java documentation for Activity.onStop()
* for more information.
*/
void (*onStop)(GameActivity* activity);
/**
* GameActivity is being destroyed. See Java documentation for
* Activity.onDestroy() for more information.
*/
void (*onDestroy)(GameActivity* activity);
/**
* Focus has changed in this GameActivity's window. This is often used,
* for example, to pause a game when it loses input focus.
*/
void (*onWindowFocusChanged)(GameActivity* activity, bool hasFocus);
/**
* The drawing window for this native activity has been created. You
* can use the given native window object to start drawing.
*/
void (*onNativeWindowCreated)(GameActivity* activity,
ANativeWindow* window);
/**
* The drawing window for this native activity has been resized. You should
* retrieve the new size from the window and ensure that your rendering in
* it now matches.
*/
void (*onNativeWindowResized)(GameActivity* activity, ANativeWindow* window,
int32_t newWidth, int32_t newHeight);
/**
* The drawing window for this native activity needs to be redrawn. To
* avoid transient artifacts during screen changes (such resizing after
* rotation), applications should not return from this function until they
* have finished drawing their window in its current state.
*/
void (*onNativeWindowRedrawNeeded)(GameActivity* activity,
ANativeWindow* window);
/**
* The drawing window for this native activity is going to be destroyed.
* You MUST ensure that you do not touch the window object after returning
* from this function: in the common case of drawing to the window from
* another thread, that means the implementation of this callback must
* properly synchronize with the other thread to stop its drawing before
* returning from here.
*/
void (*onNativeWindowDestroyed)(GameActivity* activity,
ANativeWindow* window);
/**
* The current device AConfiguration has changed. The new configuration can
* be retrieved from assetManager.
*/
void (*onConfigurationChanged)(GameActivity* activity);
/**
* The system is running low on memory. Use this callback to release
* resources you do not need, to help the system avoid killing more
* important processes.
*/
void (*onTrimMemory)(GameActivity* activity, int level);
/**
* Callback called for every MotionEvent done on the GameActivity
* SurfaceView. Ownership of `event` is maintained by the library and it is
* only valid during the callback.
*/
bool (*onTouchEvent)(GameActivity* activity,
const GameActivityMotionEvent* event);
/**
* Callback called for every key down event on the GameActivity SurfaceView.
* Ownership of `event` is maintained by the library and it is only valid
* during the callback.
*/
bool (*onKeyDown)(GameActivity* activity,
const GameActivityKeyEvent* event);
/**
* Callback called for every key up event on the GameActivity SurfaceView.
* Ownership of `event` is maintained by the library and it is only valid
* during the callback.
*/
bool (*onKeyUp)(GameActivity* activity, const GameActivityKeyEvent* event);
/**
* Callback called for every soft-keyboard text input event.
* Ownership of `state` is maintained by the library and it is only valid
* during the callback.
*/
void (*onTextInputEvent)(GameActivity* activity,
const GameTextInputState* state);
/**
* Callback called when WindowInsets of the main app window have changed.
* Call GameActivity_getWindowInsets to retrieve the insets themselves.
*/
void (*onWindowInsetsChanged)(GameActivity* activity);
} 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.
* Ownership of out_event is maintained by the caller.
*/
void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
GameActivityMotionEvent* out_event);
/**
* \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
* above); if the code is being instantiated from a previously saved instance,
* the savedState will be non-NULL and point to the saved data. You must make
* any copy of this data you need -- it will be released after you return from
* this function.
*/
typedef void GameActivity_createFunc(GameActivity* activity, void* savedState,
size_t savedStateSize);
/**
* The name of the function that NativeInstance looks for when launching its
* native code. This is the default function that is used, you can specify
* "android.app.func_name" string meta-data in your manifest to use a different
* function.
*/
extern GameActivity_createFunc GameActivity_onCreate;
/**
* Finish the given activity. Its finish() method will be called, causing it
* to be stopped and destroyed. Note that this method can be called from
* *any* thread; it will send a message to the main thread of the process
* where the Java finish call will take place.
*/
void GameActivity_finish(GameActivity* activity);
/**
* Flags for GameActivity_setWindowFlags,
* as per the Java API at android.view.WindowManager.LayoutParams.
*/
enum GameActivitySetWindowFlags {
/**
* As long as this window is visible to the user, allow the lock
* screen to activate while the screen is on. This can be used
* independently, or in combination with {@link
* GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} and/or {@link
* GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}
*/
GAMEACTIVITY_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
/** Everything behind this window will be dimmed. */
GAMEACTIVITY_FLAG_DIM_BEHIND = 0x00000002,
/**
* Blur everything behind this window.
* @deprecated Blurring is no longer supported.
*/
GAMEACTIVITY_FLAG_BLUR_BEHIND = 0x00000004,
/**
* This window won't ever get key input focus, so the
* user can not send key or other button events to it. Those will
* instead go to whatever focusable window is behind it. This flag
* will also enable {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL} whether or not
* that is explicitly set.
*
* Setting this flag also implies that the window will not need to
* interact with
* a soft input method, so it will be Z-ordered and positioned
* independently of any active input method (typically this means it
* gets Z-ordered on top of the input method, so it can use the full
* screen for its content and cover the input method if needed. You
* can use {@link GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM} to modify this
* behavior.
*/
GAMEACTIVITY_FLAG_NOT_FOCUSABLE = 0x00000008,
/** This window can never receive touch events. */
GAMEACTIVITY_FLAG_NOT_TOUCHABLE = 0x00000010,
/**
* Even when this window is focusable (its
* {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set), allow any pointer
* events outside of the window to be sent to the windows behind it.
* Otherwise it will consume all pointer events itself, regardless of
* whether they are inside of the window.
*/
GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL = 0x00000020,
/**
* When set, if the device is asleep when the touch
* screen is pressed, you will receive this first touch event. Usually
* the first touch event is consumed by the system since the user can
* not see what they are pressing on.
*
* @deprecated This flag has no effect.
*/
GAMEACTIVITY_FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
/**
* As long as this window is visible to the user, keep
* the device's screen turned on and bright.
*/
GAMEACTIVITY_FLAG_KEEP_SCREEN_ON = 0x00000080,
/**
* Place the window within the entire screen, ignoring
* decorations around the border (such as the status bar). The
* window must correctly position its contents to take the screen
* decoration into account.
*/
GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN = 0x00000100,
/** Allows the window to extend outside of the screen. */
GAMEACTIVITY_FLAG_LAYOUT_NO_LIMITS = 0x00000200,
/**
* Hide all screen decorations (such as the status
* bar) while this window is displayed. This allows the window to
* use the entire display space for itself -- the status bar will
* be hidden when an app window with this flag set is on the top
* layer. A fullscreen window will ignore a value of {@link
* GAMEACTIVITY_SOFT_INPUT_ADJUST_RESIZE}; the window will stay
* fullscreen and will not resize.
*/
GAMEACTIVITY_FLAG_FULLSCREEN = 0x00000400,
/**
* Override {@link GAMEACTIVITY_FLAG_FULLSCREEN} and force the
* screen decorations (such as the status bar) to be shown.
*/
GAMEACTIVITY_FLAG_FORCE_NOT_FULLSCREEN = 0x00000800,
/**
* Turn on dithering when compositing this window to
* the screen.
* @deprecated This flag is no longer used.
*/
GAMEACTIVITY_FLAG_DITHER = 0x00001000,
/**
* Treat the content of the window as secure, preventing
* it from appearing in screenshots or from being viewed on non-secure
* displays.
*/
GAMEACTIVITY_FLAG_SECURE = 0x00002000,
/**
* A special mode where the layout parameters are used
* to perform scaling of the surface when it is composited to the
* screen.
*/
GAMEACTIVITY_FLAG_SCALED = 0x00004000,
/**
* Intended for windows that will often be used when the user is
* holding the screen against their face, it will aggressively
* filter the event stream to prevent unintended presses in this
* situation that may not be desired for a particular window, when
* such an event stream is detected, the application will receive
* a {@link AMOTION_EVENT_ACTION_CANCEL} to indicate this so
* applications can handle this accordingly by taking no action on
* the event until the finger is released.
*/
GAMEACTIVITY_FLAG_IGNORE_CHEEK_PRESSES = 0x00008000,
/**
* A special option only for use in combination with
* {@link GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN}. When requesting layout in
* the screen your window may appear on top of or behind screen decorations
* such as the status bar. By also including this flag, the window
* manager will report the inset rectangle needed to ensure your
* content is not covered by screen decorations.
*/
GAMEACTIVITY_FLAG_LAYOUT_INSET_DECOR = 0x00010000,
/**
* Invert the state of {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} with
* respect to how this window interacts with the current method.
* That is, if FLAG_NOT_FOCUSABLE is set and this flag is set,
* then the window will behave as if it needs to interact with the
* input method and thus be placed behind/away from it; if {@link
* GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set and this flag is set,
* then the window will behave as if it doesn't need to interact
* with the input method and can be placed to use more space and
* cover the input method.
*/
GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM = 0x00020000,
/**
* If you have set {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL}, you
* can set this flag to receive a single special MotionEvent with
* the action
* {@link AMOTION_EVENT_ACTION_OUTSIDE} for
* touches that occur outside of your window. Note that you will not
* receive the full down/move/up gesture, only the location of the
* first down as an {@link AMOTION_EVENT_ACTION_OUTSIDE}.
*/
GAMEACTIVITY_FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
/**
* Special flag to let windows be shown when the screen
* is locked. This will let application windows take precedence over
* key guard or any other lock screens. Can be used with
* {@link GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} to turn screen on and display
* windows directly before showing the key guard window. Can be used with
* {@link GAMEACTIVITY_FLAG_DISMISS_KEYGUARD} to automatically fully
* dismisss non-secure keyguards. This flag only applies to the top-most
* full-screen window.
*/
GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED = 0x00080000,
/**
* Ask that the system wallpaper be shown behind
* your window. The window surface must be translucent to be able
* to actually see the wallpaper behind it; this flag just ensures
* that the wallpaper surface will be there if this window actually
* has translucent regions.
*/
GAMEACTIVITY_FLAG_SHOW_WALLPAPER = 0x00100000,
/**
* When set as a window is being added or made
* visible, once the window has been shown then the system will
* poke the power manager's user activity (as if the user had woken
* up the device) to turn the screen on.
*/
GAMEACTIVITY_FLAG_TURN_SCREEN_ON = 0x00200000,
/**
* When set the window will cause the keyguard to
* be dismissed, only if it is not a secure lock keyguard. Because such
* a keyguard is not needed for security, it will never re-appear if
* the user navigates to another window (in contrast to
* {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}, which will only temporarily
* hide both secure and non-secure keyguards but ensure they reappear
* when the user moves to another UI that doesn't hide them).
* If the keyguard is currently active and is secure (requires an
* unlock pattern) than the user will still need to confirm it before
* seeing this window, unless {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} has
* also been set.
*/
GAMEACTIVITY_FLAG_DISMISS_KEYGUARD = 0x00400000,
};
/**
* Change the window flags of the given activity. Calls getWindow().setFlags()
* of the given activity.
* Note that some flags must be set before the window decoration is created,
* see
* https://developer.android.com/reference/android/view/Window#setFlags(int,%20int).
* Note also that this method can be called from
* *any* thread; it will send a message to the main thread of the process
* where the Java finish call will take place.
*/
void GameActivity_setWindowFlags(GameActivity* activity, uint32_t addFlags,
uint32_t removeFlags);
/**
* Flags for GameActivity_showSoftInput; see the Java InputMethodManager
* API for documentation.
*/
enum GameActivityShowSoftInputFlags {
/**
* Implicit request to show the input window, not as the result
* of a direct request by the user.
*/
GAMEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,
/**
* The user has forced the input method open (such as by
* long-pressing menu) so it should not be closed until they
* explicitly do so.
*/
GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
};
/**
* Show the IME while in the given activity. Calls
* InputMethodManager.showSoftInput() for the given activity. Note that this
* method can be called from *any* thread; it will send a message to the main
* thread of the process where the Java call will take place.
*/
void GameActivity_showSoftInput(GameActivity* activity, uint32_t flags);
/**
* Set the text entry state (see documentation of the GameTextInputState struct
* in the Game Text Input library reference).
*
* Ownership of the state is maintained by the caller.
*/
void GameActivity_setTextInputState(GameActivity* activity,
const GameTextInputState* state);
/**
* Get the last-received text entry state (see documentation of the
* GameTextInputState struct in the Game Text Input library reference).
*
*/
void GameActivity_getTextInputState(GameActivity* activity,
GameTextInputGetStateCallback callback,
void* context);
/**
* Get a pointer to the GameTextInput library instance.
*/
GameTextInput* GameActivity_getTextInput(const GameActivity* activity);
/**
* Flags for GameActivity_hideSoftInput; see the Java InputMethodManager
* API for documentation.
*/
enum GameActivityHideSoftInputFlags {
/**
* The soft input window should only be hidden if it was not
* explicitly shown by the user.
*/
GAMEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,
/**
* The soft input window should normally be hidden, unless it was
* originally shown with {@link GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED}.
*/
GAMEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
};
/**
* Hide the IME while in the given activity. Calls
* InputMethodManager.hideSoftInput() for the given activity. Note that this
* method can be called from *any* thread; it will send a message to the main
* thread of the process where the Java finish call will take place.
*/
void GameActivity_hideSoftInput(GameActivity* activity, uint32_t flags);
/**
* Get the current window insets of the particular component. See
* https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type
* for more details.
* You can use these insets to influence what you show on the screen.
*/
void GameActivity_getWindowInsets(GameActivity* activity,
GameCommonInsetsType type, ARect* insets);
/**
* Set options on how the IME behaves when it is requested for text input.
* See
* https://developer.android.com/reference/android/view/inputmethod/EditorInfo
* for the meaning of inputType, actionId and imeOptions.
*
* Note that this function will attach the current thread to the JVM if it is
* not already attached, so the caller must detach the thread from the JVM
* before the thread is destroyed using DetachCurrentThread.
*/
void GameActivity_setImeEditorInfo(GameActivity* activity, int inputType,
int actionId, int imeOptions);
#ifdef __cplusplus
}
#endif
/** @} */
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_H

View File

@ -0,0 +1,571 @@
/*
* Copyright (C) 2021 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 "android_native_app_glue.h"
#include <android/log.h>
#include <errno.h>
#include <jni.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define LOGI(...) \
((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
#define LOGE(...) \
((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__))
/* For debug builds, always enable the debug traces in this library */
#ifndef NDEBUG
#define LOGV(...) \
((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", \
__VA_ARGS__))
#else
#define LOGV(...) ((void)0)
#endif
static void free_saved_state(struct android_app* android_app) {
pthread_mutex_lock(&android_app->mutex);
if (android_app->savedState != NULL) {
free(android_app->savedState);
android_app->savedState = NULL;
android_app->savedStateSize = 0;
}
pthread_mutex_unlock(&android_app->mutex);
}
int8_t android_app_read_cmd(struct android_app* android_app) {
int8_t cmd;
if (read(android_app->msgread, &cmd, sizeof(cmd)) != sizeof(cmd)) {
LOGE("No data on command pipe!");
return -1;
}
if (cmd == APP_CMD_SAVE_STATE) free_saved_state(android_app);
return cmd;
}
static void print_cur_config(struct android_app* android_app) {
char lang[2], country[2];
AConfiguration_getLanguage(android_app->config, lang);
AConfiguration_getCountry(android_app->config, country);
LOGV(
"Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
"keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
"modetype=%d modenight=%d",
AConfiguration_getMcc(android_app->config),
AConfiguration_getMnc(android_app->config), lang[0], lang[1],
country[0], country[1],
AConfiguration_getOrientation(android_app->config),
AConfiguration_getTouchscreen(android_app->config),
AConfiguration_getDensity(android_app->config),
AConfiguration_getKeyboard(android_app->config),
AConfiguration_getNavigation(android_app->config),
AConfiguration_getKeysHidden(android_app->config),
AConfiguration_getNavHidden(android_app->config),
AConfiguration_getSdkVersion(android_app->config),
AConfiguration_getScreenSize(android_app->config),
AConfiguration_getScreenLong(android_app->config),
AConfiguration_getUiModeType(android_app->config),
AConfiguration_getUiModeNight(android_app->config));
}
void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) {
switch (cmd) {
case UNUSED_APP_CMD_INPUT_CHANGED:
LOGV("UNUSED_APP_CMD_INPUT_CHANGED");
// Do nothing. This can be used in the future to handle AInputQueue
// natively, like done in NativeActivity.
break;
case APP_CMD_INIT_WINDOW:
LOGV("APP_CMD_INIT_WINDOW");
pthread_mutex_lock(&android_app->mutex);
android_app->window = android_app->pendingWindow;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
break;
case APP_CMD_TERM_WINDOW:
LOGV("APP_CMD_TERM_WINDOW");
pthread_cond_broadcast(&android_app->cond);
break;
case APP_CMD_RESUME:
case APP_CMD_START:
case APP_CMD_PAUSE:
case APP_CMD_STOP:
LOGV("activityState=%d", cmd);
pthread_mutex_lock(&android_app->mutex);
android_app->activityState = cmd;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
break;
case APP_CMD_CONFIG_CHANGED:
LOGV("APP_CMD_CONFIG_CHANGED");
AConfiguration_fromAssetManager(
android_app->config, android_app->activity->assetManager);
print_cur_config(android_app);
break;
case APP_CMD_DESTROY:
LOGV("APP_CMD_DESTROY");
android_app->destroyRequested = 1;
break;
}
}
void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) {
switch (cmd) {
case APP_CMD_TERM_WINDOW:
LOGV("APP_CMD_TERM_WINDOW");
pthread_mutex_lock(&android_app->mutex);
android_app->window = NULL;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
break;
case APP_CMD_SAVE_STATE:
LOGV("APP_CMD_SAVE_STATE");
pthread_mutex_lock(&android_app->mutex);
android_app->stateSaved = 1;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
break;
case APP_CMD_RESUME:
free_saved_state(android_app);
break;
}
}
void app_dummy() {}
static void android_app_destroy(struct android_app* android_app) {
LOGV("android_app_destroy!");
free_saved_state(android_app);
pthread_mutex_lock(&android_app->mutex);
AConfiguration_delete(android_app->config);
android_app->destroyed = 1;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
// Can't touch android_app object after this.
}
static void process_cmd(struct android_app* app,
struct android_poll_source* source) {
int8_t cmd = android_app_read_cmd(app);
android_app_pre_exec_cmd(app, cmd);
if (app->onAppCmd != NULL) app->onAppCmd(app, cmd);
android_app_post_exec_cmd(app, cmd);
}
// 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;
LOGV("android_app_entry called");
android_app->config = AConfiguration_new();
LOGV("android_app = %p", android_app);
LOGV("config = %p", android_app->config);
LOGV("activity = %p", android_app->activity);
LOGV("assetmanager = %p", android_app->activity->assetManager);
AConfiguration_fromAssetManager(android_app->config,
android_app->activity->assetManager);
print_cur_config(android_app);
android_app->cmdPollSource.id = LOOPER_ID_MAIN;
android_app->cmdPollSource.app = android_app;
android_app->cmdPollSource.process = process_cmd;
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN,
ALOOPER_EVENT_INPUT, NULL, &android_app->cmdPollSource);
android_app->looper = looper;
pthread_mutex_lock(&android_app->mutex);
android_app->running = 1;
pthread_cond_broadcast(&android_app->cond);
pthread_mutex_unlock(&android_app->mutex);
android_main(android_app);
android_app_destroy(android_app);
return NULL;
}
// Codes from https://developer.android.com/reference/android/view/KeyEvent
#define KEY_EVENT_KEYCODE_VOLUME_DOWN 25
#define KEY_EVENT_KEYCODE_VOLUME_MUTE 164
#define KEY_EVENT_KEYCODE_VOLUME_UP 24
#define KEY_EVENT_KEYCODE_CAMERA 27
#define KEY_EVENT_KEYCODE_ZOOM_IN 168
#define KEY_EVENT_KEYCODE_ZOOM_OUT 169
// Double-buffer the key event filter to avoid race condition.
static bool default_key_filter(const GameActivityKeyEvent* event) {
// Ignore camera, volume, etc. buttons
return !(event->keyCode == KEY_EVENT_KEYCODE_VOLUME_DOWN ||
event->keyCode == KEY_EVENT_KEYCODE_VOLUME_MUTE ||
event->keyCode == KEY_EVENT_KEYCODE_VOLUME_UP ||
event->keyCode == KEY_EVENT_KEYCODE_CAMERA ||
event->keyCode == KEY_EVENT_KEYCODE_ZOOM_IN ||
event->keyCode == KEY_EVENT_KEYCODE_ZOOM_OUT);
}
// See
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_TOUCHSCREEN
#define SOURCE_TOUCHSCREEN 0x00001002
static bool default_motion_filter(const GameActivityMotionEvent* event) {
// Ignore any non-touch events.
return event->source == SOURCE_TOUCHSCREEN;
}
// --------------------------------------------------------------------
// Native activity interaction (called from main thread)
// --------------------------------------------------------------------
static struct android_app* android_app_create(GameActivity* activity,
void* savedState,
size_t savedStateSize) {
// struct android_app* android_app = calloc(1, sizeof(struct android_app));
struct android_app* android_app =
(struct android_app*)malloc(sizeof(struct android_app));
memset(android_app, 0, sizeof(struct android_app));
android_app->activity = activity;
pthread_mutex_init(&android_app->mutex, NULL);
pthread_cond_init(&android_app->cond, NULL);
if (savedState != NULL) {
android_app->savedState = malloc(savedStateSize);
android_app->savedStateSize = savedStateSize;
memcpy(android_app->savedState, savedState, savedStateSize);
}
int msgpipe[2];
if (pipe(msgpipe)) {
LOGE("could not create pipe: %s", strerror(errno));
return NULL;
}
android_app->msgread = msgpipe[0];
android_app->msgwrite = msgpipe[1];
android_app->keyEventFilter = default_key_filter;
android_app->motionEventFilter = default_motion_filter;
LOGV("Launching android_app_entry in a thread");
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&android_app->thread, &attr, android_app_entry, android_app);
// Wait for thread to start.
pthread_mutex_lock(&android_app->mutex);
while (!android_app->running) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);
return android_app;
}
static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
LOGE("Failure writing android_app cmd: %s", strerror(errno));
}
}
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);
if (android_app->pendingWindow != NULL) {
android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW);
}
android_app->pendingWindow = window;
if (window != NULL) {
android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);
}
while (android_app->window != android_app->pendingWindow) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);
}
static void android_app_set_activity_state(struct android_app* android_app,
int8_t cmd) {
pthread_mutex_lock(&android_app->mutex);
android_app_write_cmd(android_app, cmd);
while (android_app->activityState != cmd) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);
}
static void android_app_free(struct android_app* android_app) {
pthread_mutex_lock(&android_app->mutex);
android_app_write_cmd(android_app, APP_CMD_DESTROY);
while (!android_app->destroyed) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);
close(android_app->msgread);
close(android_app->msgwrite);
pthread_cond_destroy(&android_app->cond);
pthread_mutex_destroy(&android_app->mutex);
free(android_app);
}
static inline struct android_app* ToApp(GameActivity* activity) {
return (struct android_app*)activity->instance;
}
static void onDestroy(GameActivity* activity) {
LOGV("Destroy: %p", activity);
android_app_free(ToApp(activity));
}
static void onStart(GameActivity* activity) {
LOGV("Start: %p", activity);
android_app_set_activity_state(ToApp(activity), APP_CMD_START);
}
static void onResume(GameActivity* activity) {
LOGV("Resume: %p", activity);
android_app_set_activity_state(ToApp(activity), APP_CMD_RESUME);
}
static void onSaveInstanceState(GameActivity* activity,
SaveInstanceStateRecallback recallback,
void* context) {
LOGV("SaveInstanceState: %p", activity);
struct android_app* android_app = ToApp(activity);
void* savedState = NULL;
pthread_mutex_lock(&android_app->mutex);
android_app->stateSaved = 0;
android_app_write_cmd(android_app, APP_CMD_SAVE_STATE);
while (!android_app->stateSaved) {
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
if (android_app->savedState != NULL) {
// Tell the Java side about our state.
recallback((const char*)android_app->savedState,
android_app->savedStateSize, context);
// Now we can free it.
free(android_app->savedState);
android_app->savedState = NULL;
android_app->savedStateSize = 0;
}
pthread_mutex_unlock(&android_app->mutex);
}
static void onPause(GameActivity* activity) {
LOGV("Pause: %p", activity);
android_app_set_activity_state(ToApp(activity), APP_CMD_PAUSE);
}
static void onStop(GameActivity* activity) {
LOGV("Stop: %p", activity);
android_app_set_activity_state(ToApp(activity), APP_CMD_STOP);
}
static void onConfigurationChanged(GameActivity* activity) {
LOGV("ConfigurationChanged: %p", activity);
android_app_write_cmd(ToApp(activity), APP_CMD_CONFIG_CHANGED);
}
static void onTrimMemory(GameActivity* activity, int level) {
LOGV("TrimMemory: %p %d", activity, level);
android_app_write_cmd(ToApp(activity), APP_CMD_LOW_MEMORY);
}
static void onWindowFocusChanged(GameActivity* activity, bool focused) {
LOGV("WindowFocusChanged: %p -- %d", activity, focused);
android_app_write_cmd(ToApp(activity),
focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS);
}
static void onNativeWindowCreated(GameActivity* activity,
ANativeWindow* window) {
LOGV("NativeWindowCreated: %p -- %p", activity, window);
android_app_set_window(ToApp(activity), window);
}
static void onNativeWindowDestroyed(GameActivity* activity,
ANativeWindow* window) {
LOGV("NativeWindowDestroyed: %p -- %p", activity, window);
android_app_set_window(ToApp(activity), NULL);
}
static void onNativeWindowRedrawNeeded(GameActivity* activity,
ANativeWindow* window) {
LOGV("NativeWindowRedrawNeeded: %p -- %p", activity, window);
android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_REDRAW_NEEDED);
}
static void onNativeWindowResized(GameActivity* activity, ANativeWindow* window,
int32_t width, int32_t height) {
LOGV("NativeWindowResized: %p -- %p ( %d x %d )", activity, window, width,
height);
android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_RESIZED);
}
void android_app_set_motion_event_filter(struct android_app* app,
android_motion_event_filter filter) {
pthread_mutex_lock(&app->mutex);
app->motionEventFilter = filter;
pthread_mutex_unlock(&app->mutex);
}
static bool onTouchEvent(GameActivity* activity,
const GameActivityMotionEvent* event) {
struct android_app* android_app = ToApp(activity);
pthread_mutex_lock(&android_app->mutex);
if (android_app->motionEventFilter != NULL &&
!android_app->motionEventFilter(event)) {
pthread_mutex_unlock(&android_app->mutex);
return false;
}
struct android_input_buffer* inputBuffer =
&android_app->inputBuffers[android_app->currentInputBuffer];
// Add to the list of active motion events
if (inputBuffer->motionEventsCount <
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;
}
struct android_input_buffer* android_app_swap_input_buffers(
struct android_app* android_app) {
pthread_mutex_lock(&android_app->mutex);
struct android_input_buffer* inputBuffer =
&android_app->inputBuffers[android_app->currentInputBuffer];
if (inputBuffer->motionEventsCount == 0 &&
inputBuffer->keyEventsCount == 0) {
inputBuffer = NULL;
} else {
android_app->currentInputBuffer =
(android_app->currentInputBuffer + 1) %
NATIVE_APP_GLUE_MAX_INPUT_BUFFERS;
}
pthread_mutex_unlock(&android_app->mutex);
return inputBuffer;
}
void android_app_clear_motion_events(struct android_input_buffer* inputBuffer) {
inputBuffer->motionEventsCount = 0;
}
void android_app_set_key_event_filter(struct android_app* app,
android_key_event_filter filter) {
pthread_mutex_lock(&app->mutex);
app->keyEventFilter = filter;
pthread_mutex_unlock(&app->mutex);
}
static bool onKey(GameActivity* activity, const GameActivityKeyEvent* event) {
struct android_app* android_app = ToApp(activity);
pthread_mutex_lock(&android_app->mutex);
if (android_app->keyEventFilter != NULL &&
!android_app->keyEventFilter(event)) {
pthread_mutex_unlock(&android_app->mutex);
return false;
}
struct android_input_buffer* inputBuffer =
&android_app->inputBuffers[android_app->currentInputBuffer];
// Add to the list of active key down events
if (inputBuffer->keyEventsCount < NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS) {
int new_ix = inputBuffer->keyEventsCount;
memcpy(&inputBuffer->keyEvents[new_ix], event,
sizeof(GameActivityKeyEvent));
++inputBuffer->keyEventsCount;
}
pthread_mutex_unlock(&android_app->mutex);
return true;
}
void android_app_clear_key_events(struct android_input_buffer* inputBuffer) {
inputBuffer->keyEventsCount = 0;
}
static void onTextInputEvent(GameActivity* activity,
const GameTextInputState* state) {
struct android_app* android_app = ToApp(activity);
pthread_mutex_lock(&android_app->mutex);
android_app->textInputState = 1;
pthread_mutex_unlock(&android_app->mutex);
}
static void onWindowInsetsChanged(GameActivity* activity) {
LOGV("WindowInsetsChanged: %p", activity);
android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_INSETS_CHANGED);
}
JNIEXPORT
void GameActivity_onCreate(GameActivity* activity, void* savedState,
size_t savedStateSize) {
LOGV("Creating: %p", activity);
activity->callbacks->onDestroy = onDestroy;
activity->callbacks->onStart = onStart;
activity->callbacks->onResume = onResume;
activity->callbacks->onSaveInstanceState = onSaveInstanceState;
activity->callbacks->onPause = onPause;
activity->callbacks->onStop = onStop;
activity->callbacks->onTouchEvent = onTouchEvent;
activity->callbacks->onKeyDown = onKey;
activity->callbacks->onKeyUp = onKey;
activity->callbacks->onTextInputEvent = onTextInputEvent;
activity->callbacks->onConfigurationChanged = onConfigurationChanged;
activity->callbacks->onTrimMemory = onTrimMemory;
activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
activity->callbacks->onNativeWindowRedrawNeeded =
onNativeWindowRedrawNeeded;
activity->callbacks->onNativeWindowResized = onNativeWindowResized;
activity->callbacks->onWindowInsetsChanged = onWindowInsetsChanged;
LOGV("Callbacks set: %p", activity->callbacks);
activity->instance =
android_app_create(activity, savedState, savedStateSize);
}

View File

@ -0,0 +1,486 @@
/*
* Copyright (C) 2021 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.
*/
#pragma once
/**
* @addtogroup android_native_app_glue Native App Glue library
* The glue library to interface your game loop with GameActivity.
* @{
*/
#include <android/configuration.h>
#include <android/looper.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#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_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
/**
* The GameActivity interface provided by <game-activity/GameActivity.h>
* is based on a set of application-provided callbacks that will be called
* by the Activity's main thread when certain events occur.
*
* This means that each one of this callbacks _should_ _not_ block, or they
* risk having the system force-close the application. This programming
* model is direct, lightweight, but constraining.
*
* The 'android_native_app_glue' static library is used to provide a different
* execution model where the application can implement its own main event
* loop in a different thread instead. Here's how it works:
*
* 1/ The application must provide a function named "android_main()" that
* will be called when the activity is created, in a new thread that is
* distinct from the activity's main thread.
*
* 2/ android_main() receives a pointer to a valid "android_app" structure
* that contains references to other important objects, e.g. the
* GameActivity obejct instance the application is running in.
*
* 3/ the "android_app" object holds an ALooper instance that already
* listens to activity lifecycle events (e.g. "pause", "resume").
* See APP_CMD_XXX declarations below.
*
* This corresponds to an ALooper identifier returned by
* ALooper_pollOnce with value LOOPER_ID_MAIN.
*
* Your application can use the same ALooper to listen to additional
* file-descriptors. They can either be callback based, or with return
* identifiers starting with LOOPER_ID_USER.
*
* 4/ Whenever you receive a LOOPER_ID_MAIN event,
* the returned data will point to an android_poll_source structure. You
* can call the process() function on it, and fill in android_app->onAppCmd
* to be called for your own processing of the event.
*
* Alternatively, you can call the low-level functions to read and process
* the data directly... look at the process_cmd() and process_input()
* implementations in the glue to see how to do this.
*
* See the sample named "native-activity" that comes with the NDK with a
* full usage example. Also look at the documentation of GameActivity.
*/
struct android_app;
/**
* Data associated with an ALooper fd that will be returned as the "outData"
* when that source has data ready.
*/
struct android_poll_source {
/**
* The identifier of this source. May be LOOPER_ID_MAIN or
* LOOPER_ID_INPUT.
*/
int32_t id;
/** The android_app this ident is associated with. */
struct android_app* app;
/**
* Function to call to perform the standard processing of data from
* this source.
*/
void (*process)(struct android_app* app,
struct android_poll_source* source);
};
struct android_input_buffer {
/**
* Pointer to a read-only array of pointers to GameActivityMotionEvent.
* Only the first motionEventsCount events are valid.
*/
GameActivityMotionEvent motionEvents[NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS];
/**
* The number of valid motion events in `motionEvents`.
*/
uint64_t motionEventsCount;
/**
* Pointer to a read-only array of pointers to GameActivityKeyEvent.
* Only the first keyEventsCount events are valid.
*/
GameActivityKeyEvent keyEvents[NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS];
/**
* The number of valid "Key" events in `keyEvents`.
*/
uint64_t keyEventsCount;
};
/**
* Function pointer declaration for the filtering of key events.
* A function with this signature should be passed to
* android_app_set_key_event_filter and return false for any events that should
* not be handled by android_native_app_glue. These events will be handled by
* the system instead.
*/
typedef bool (*android_key_event_filter)(const GameActivityKeyEvent*);
/**
* Function pointer definition for the filtering of motion events.
* A function with this signature should be passed to
* android_app_set_motion_event_filter and return false for any events that
* should not be handled by android_native_app_glue. These events will be
* handled by the system instead.
*/
typedef bool (*android_motion_event_filter)(const GameActivityMotionEvent*);
/**
* This is the interface for the standard glue code of a threaded
* application. In this model, the application's code is running
* in its own thread separate from the main thread of the process.
* It is not required that this thread be associated with the Java
* VM, although it will need to be in order to make JNI calls any
* Java objects.
*/
struct android_app {
/**
* An optional pointer to application-defined state.
*/
void* userData;
/**
* A required callback for processing main app commands (`APP_CMD_*`).
* This is called each frame if there are app commands that need processing.
*/
void (*onAppCmd)(struct android_app* app, int32_t cmd);
/** The GameActivity object instance that this app is running in. */
GameActivity* activity;
/** The current configuration the app is running in. */
AConfiguration* config;
/**
* The last activity saved state, as provided at creation time.
* It is NULL if there was no state. You can use this as you need; the
* memory will remain around until you call android_app_exec_cmd() for
* APP_CMD_RESUME, at which point it will be freed and savedState set to
* NULL. These variables should only be changed when processing a
* APP_CMD_SAVE_STATE, at which point they will be initialized to NULL and
* you can malloc your state and place the information here. In that case
* the memory will be freed for you later.
*/
void* savedState;
/**
* The size of the activity saved state. It is 0 if `savedState` is NULL.
*/
size_t savedStateSize;
/** The ALooper associated with the app's thread. */
ALooper* looper;
/** When non-NULL, this is the window surface that the app can draw in. */
ANativeWindow* window;
/**
* Current content rectangle of the window; this is the area where the
* window's content should be placed to be seen by the user.
*/
ARect contentRect;
/**
* Current state of the app's activity. May be either APP_CMD_START,
* APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP.
*/
int activityState;
/**
* This is non-zero when the application's GameActivity is being
* destroyed and waiting for the app thread to complete.
*/
int destroyRequested;
#define NATIVE_APP_GLUE_MAX_INPUT_BUFFERS 2
/**
* This is used for buffering input from GameActivity. Once ready, the
* application thread switches the buffers and processes what was
* accumulated.
*/
struct android_input_buffer inputBuffers[NATIVE_APP_GLUE_MAX_INPUT_BUFFERS];
int currentInputBuffer;
/**
* 0 if no text input event is outstanding, 1 if it is.
* Use `GameActivity_getTextInputState` to get information
* about the text entered by the user.
*/
int textInputState;
// Below are "private" implementation of the glue code.
/** @cond INTERNAL */
pthread_mutex_t mutex;
pthread_cond_t cond;
int msgread;
int msgwrite;
pthread_t thread;
struct android_poll_source cmdPollSource;
int running;
int stateSaved;
int destroyed;
int redrawNeeded;
ANativeWindow* pendingWindow;
ARect pendingContentRect;
android_key_event_filter keyEventFilter;
android_motion_event_filter motionEventFilter;
/** @endcond */
};
/**
* Looper ID of commands coming from the app's main thread, an AInputQueue or
* user-defined sources.
*/
enum NativeAppGlueLooperId {
/**
* Looper data ID of commands coming from the app's main thread, which
* is returned as an identifier from ALooper_pollOnce(). The data for this
* identifier is a pointer to an android_poll_source structure.
* These can be retrieved and processed with android_app_read_cmd()
* and android_app_exec_cmd().
*/
LOOPER_ID_MAIN = 1,
/**
* Unused. Reserved for future use when usage of AInputQueue will be
* supported.
*/
LOOPER_ID_INPUT = 2,
/**
* Start of user-defined ALooper identifiers.
*/
LOOPER_ID_USER = 3,
};
/**
* Commands passed from the application's main Java thread to the game's thread.
*/
enum NativeAppGlueAppCmd {
/**
* Unused. Reserved for future use when usage of AInputQueue will be
* supported.
*/
UNUSED_APP_CMD_INPUT_CHANGED,
/**
* Command from main thread: a new ANativeWindow is ready for use. Upon
* receiving this command, android_app->window will contain the new window
* surface.
*/
APP_CMD_INIT_WINDOW,
/**
* Command from main thread: the existing ANativeWindow needs to be
* terminated. Upon receiving this command, android_app->window still
* contains the existing window; after calling android_app_exec_cmd
* it will be set to NULL.
*/
APP_CMD_TERM_WINDOW,
/**
* Command from main thread: the current ANativeWindow has been resized.
* Please redraw with its new size.
*/
APP_CMD_WINDOW_RESIZED,
/**
* Command from main thread: the system needs that the current ANativeWindow
* be redrawn. You should redraw the window before handing this to
* android_app_exec_cmd() in order to avoid transient drawing glitches.
*/
APP_CMD_WINDOW_REDRAW_NEEDED,
/**
* Command from main thread: the content area of the window has changed,
* such as from the soft input window being shown or hidden. You can
* find the new content rect in android_app::contentRect.
*/
APP_CMD_CONTENT_RECT_CHANGED,
/**
* Command from main thread: the app's activity window has gained
* input focus.
*/
APP_CMD_GAINED_FOCUS,
/**
* Command from main thread: the app's activity window has lost
* input focus.
*/
APP_CMD_LOST_FOCUS,
/**
* Command from main thread: the current device configuration has changed.
*/
APP_CMD_CONFIG_CHANGED,
/**
* Command from main thread: the system is running low on memory.
* Try to reduce your memory use.
*/
APP_CMD_LOW_MEMORY,
/**
* Command from main thread: the app's activity has been started.
*/
APP_CMD_START,
/**
* Command from main thread: the app's activity has been resumed.
*/
APP_CMD_RESUME,
/**
* Command from main thread: the app should generate a new saved state
* for itself, to restore from later if needed. If you have saved state,
* allocate it with malloc and place it in android_app.savedState with
* the size in android_app.savedStateSize. The will be freed for you
* later.
*/
APP_CMD_SAVE_STATE,
/**
* Command from main thread: the app's activity has been paused.
*/
APP_CMD_PAUSE,
/**
* Command from main thread: the app's activity has been stopped.
*/
APP_CMD_STOP,
/**
* Command from main thread: the app's activity is being destroyed,
* and waiting for the app thread to clean up and exit before proceeding.
*/
APP_CMD_DESTROY,
/**
* Command from main thread: the app's insets have changed.
*/
APP_CMD_WINDOW_INSETS_CHANGED,
};
/**
* Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next
* app command message.
*/
int8_t android_app_read_cmd(struct android_app* android_app);
/**
* Call with the command returned by android_app_read_cmd() to do the
* initial pre-processing of the given command. You can perform your own
* actions for the command after calling this function.
*/
void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd);
/**
* Call with the command returned by android_app_read_cmd() to do the
* final post-processing of the given command. You must have done your own
* actions for the command before calling this function.
*/
void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd);
/**
* Call this before processing input events to get the events buffer.
* The function returns NULL if there are no events to process.
*/
struct android_input_buffer* android_app_swap_input_buffers(
struct android_app* android_app);
/**
* Clear the array of motion events that were waiting to be handled, and release
* each of them.
*
* This method should be called after you have processed the motion events in
* your game loop. You should handle events at each iteration of your game loop.
*/
void android_app_clear_motion_events(struct android_input_buffer* inputBuffer);
/**
* Clear the array of key events that were waiting to be handled, and release
* each of them.
*
* This method should be called after you have processed the key up events in
* your game loop. You should handle events at each iteration of your game loop.
*/
void android_app_clear_key_events(struct android_input_buffer* inputBuffer);
/**
* This is the function that application code must implement, representing
* the main entry to the app.
*/
extern void android_main(struct android_app* app);
/**
* Set the filter to use when processing key events.
* Any events for which the filter returns false will be ignored by
* android_native_app_glue. If filter is set to NULL, no filtering is done.
*
* The default key filter will filter out volume and camera button presses.
*/
void android_app_set_key_event_filter(struct android_app* app,
android_key_event_filter filter);
/**
* Set the filter to use when processing touch and motion events.
* Any events for which the filter returns false will be ignored by
* android_native_app_glue. If filter is set to NULL, no filtering is done.
*
* Note that the default motion event filter will only allow touchscreen events
* through, in order to mimic NativeActivity's behaviour, so for controller
* events to be passed to the app, set the filter to NULL.
*/
void android_app_set_motion_event_filter(struct android_app* app,
android_motion_event_filter filter);
#ifdef __cplusplus
}
#endif
/** @} */

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup game_common Game Common
* Common structures and functions used within AGDK
* @{
*/
#pragma once
/**
* The type of a component for which to retrieve insets. See
* https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type
*/
typedef enum GameCommonInsetsType {
GAMECOMMON_INSETS_TYPE_CAPTION_BAR = 0,
GAMECOMMON_INSETS_TYPE_DISPLAY_CUTOUT,
GAMECOMMON_INSETS_TYPE_IME,
GAMECOMMON_INSETS_TYPE_MANDATORY_SYSTEM_GESTURES,
GAMECOMMON_INSETS_TYPE_NAVIGATION_BARS,
GAMECOMMON_INSETS_TYPE_STATUS_BARS,
GAMECOMMON_INSETS_TYPE_SYSTEM_BARS,
GAMECOMMON_INSETS_TYPE_SYSTEM_GESTURES,
GAMECOMMON_INSETS_TYPE_TAPABLE_ELEMENT,
GAMECOMMON_INSETS_TYPE_WATERFALL,
GAMECOMMON_INSETS_TYPE_COUNT
} GameCommonInsetsType;

View File

@ -0,0 +1,366 @@
/*
* Copyright (C) 2021 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 "game-text-input/gametextinput.h"
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <memory>
#include <vector>
#define LOG_TAG "GameTextInput"
static constexpr int32_t DEFAULT_MAX_STRING_SIZE = 1 << 16;
// Cache of field ids in the Java GameTextInputState class
struct StateClassInfo {
jfieldID text;
jfieldID selectionStart;
jfieldID selectionEnd;
jfieldID composingRegionStart;
jfieldID composingRegionEnd;
};
// Main GameTextInput object.
struct GameTextInput {
public:
GameTextInput(JNIEnv *env, uint32_t max_string_size);
~GameTextInput();
void setState(const GameTextInputState &state);
const GameTextInputState &getState() const { return currentState_; }
void setInputConnection(jobject inputConnection);
void processEvent(jobject textInputEvent);
void showIme(uint32_t flags);
void hideIme(uint32_t flags);
void setEventCallback(GameTextInputEventCallback callback, void *context);
jobject stateToJava(const GameTextInputState &state) const;
void stateFromJava(jobject textInputEvent,
GameTextInputGetStateCallback callback,
void *context) const;
void setImeInsetsCallback(GameTextInputImeInsetsCallback callback,
void *context);
void processImeInsets(const ARect *insets);
const ARect &getImeInsets() const { return currentInsets_; }
private:
// Copy string and set other fields
void setStateInner(const GameTextInputState &state);
static void processCallback(void *context, const GameTextInputState *state);
JNIEnv *env_ = nullptr;
// Cached at initialization from
// com/google/androidgamesdk/gametextinput/State.
jclass stateJavaClass_ = nullptr;
// The latest text input update.
GameTextInputState currentState_ = {};
// An instance of gametextinput.InputConnection.
jclass inputConnectionClass_ = nullptr;
jobject inputConnection_ = nullptr;
jmethodID inputConnectionSetStateMethod_;
jmethodID setSoftKeyboardActiveMethod_;
void (*eventCallback_)(void *context,
const struct GameTextInputState *state) = nullptr;
void *eventCallbackContext_ = nullptr;
void (*insetsCallback_)(void *context,
const struct ARect *insets) = nullptr;
ARect currentInsets_ = {};
void *insetsCallbackContext_ = nullptr;
StateClassInfo stateClassInfo_ = {};
// Constant-sized buffer used to store state text.
std::vector<char> stateStringBuffer_;
};
std::unique_ptr<GameTextInput> s_gameTextInput;
extern "C" {
///////////////////////////////////////////////////////////
/// GameTextInputState C Functions
///////////////////////////////////////////////////////////
// Convert to a Java structure.
jobject currentState_toJava(const GameTextInput *gameTextInput,
const GameTextInputState *state) {
if (state == nullptr) return NULL;
return gameTextInput->stateToJava(*state);
}
// Convert from Java structure.
void currentState_fromJava(const GameTextInput *gameTextInput,
jobject textInputEvent,
GameTextInputGetStateCallback callback,
void *context) {
gameTextInput->stateFromJava(textInputEvent, callback, context);
}
///////////////////////////////////////////////////////////
/// GameTextInput C Functions
///////////////////////////////////////////////////////////
struct GameTextInput *GameTextInput_init(JNIEnv *env,
uint32_t max_string_size) {
if (s_gameTextInput.get() != nullptr) {
__android_log_print(ANDROID_LOG_WARN, LOG_TAG,
"Warning: called GameTextInput_init twice without "
"calling GameTextInput_destroy");
return s_gameTextInput.get();
}
// Don't use make_unique, for C++11 compatibility
s_gameTextInput =
std::unique_ptr<GameTextInput>(new GameTextInput(env, max_string_size));
return s_gameTextInput.get();
}
void GameTextInput_destroy(GameTextInput *input) {
if (input == nullptr || s_gameTextInput.get() == nullptr) return;
s_gameTextInput.reset();
}
void GameTextInput_setState(GameTextInput *input,
const GameTextInputState *state) {
if (state == nullptr) return;
input->setState(*state);
}
void GameTextInput_getState(GameTextInput *input,
GameTextInputGetStateCallback callback,
void *context) {
callback(context, &input->getState());
}
void GameTextInput_setInputConnection(GameTextInput *input,
jobject inputConnection) {
input->setInputConnection(inputConnection);
}
void GameTextInput_processEvent(GameTextInput *input, jobject textInputEvent) {
input->processEvent(textInputEvent);
}
void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets) {
input->processImeInsets(insets);
}
void GameTextInput_showIme(struct GameTextInput *input, uint32_t flags) {
input->showIme(flags);
}
void GameTextInput_hideIme(struct GameTextInput *input, uint32_t flags) {
input->hideIme(flags);
}
void GameTextInput_setEventCallback(struct GameTextInput *input,
GameTextInputEventCallback callback,
void *context) {
input->setEventCallback(callback, context);
}
void GameTextInput_setImeInsetsCallback(struct GameTextInput *input,
GameTextInputImeInsetsCallback callback,
void *context) {
input->setImeInsetsCallback(callback, context);
}
void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets) {
*insets = input->getImeInsets();
}
} // extern "C"
///////////////////////////////////////////////////////////
/// GameTextInput C++ class Implementation
///////////////////////////////////////////////////////////
GameTextInput::GameTextInput(JNIEnv *env, uint32_t max_string_size)
: env_(env),
stateStringBuffer_(max_string_size == 0 ? DEFAULT_MAX_STRING_SIZE
: max_string_size) {
stateJavaClass_ = (jclass)env_->NewGlobalRef(
env_->FindClass("com/google/androidgamesdk/gametextinput/State"));
inputConnectionClass_ = (jclass)env_->NewGlobalRef(env_->FindClass(
"com/google/androidgamesdk/gametextinput/InputConnection"));
inputConnectionSetStateMethod_ =
env_->GetMethodID(inputConnectionClass_, "setState",
"(Lcom/google/androidgamesdk/gametextinput/State;)V");
setSoftKeyboardActiveMethod_ = env_->GetMethodID(
inputConnectionClass_, "setSoftKeyboardActive", "(ZI)V");
stateClassInfo_.text =
env_->GetFieldID(stateJavaClass_, "text", "Ljava/lang/String;");
stateClassInfo_.selectionStart =
env_->GetFieldID(stateJavaClass_, "selectionStart", "I");
stateClassInfo_.selectionEnd =
env_->GetFieldID(stateJavaClass_, "selectionEnd", "I");
stateClassInfo_.composingRegionStart =
env_->GetFieldID(stateJavaClass_, "composingRegionStart", "I");
stateClassInfo_.composingRegionEnd =
env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "I");
s_gameTextInput.get();
}
GameTextInput::~GameTextInput() {
if (stateJavaClass_ != NULL) {
env_->DeleteGlobalRef(stateJavaClass_);
stateJavaClass_ = NULL;
}
if (inputConnectionClass_ != NULL) {
env_->DeleteGlobalRef(inputConnectionClass_);
inputConnectionClass_ = NULL;
}
if (inputConnection_ != NULL) {
env_->DeleteGlobalRef(inputConnection_);
inputConnection_ = NULL;
}
}
void GameTextInput::setState(const GameTextInputState &state) {
if (inputConnection_ == nullptr) return;
jobject jstate = stateToJava(state);
env_->CallVoidMethod(inputConnection_, inputConnectionSetStateMethod_,
jstate);
env_->DeleteLocalRef(jstate);
setStateInner(state);
}
void GameTextInput::setStateInner(const GameTextInputState &state) {
// Check if we're setting using our own string (other parts may be
// different)
if (state.text_UTF8 == currentState_.text_UTF8) {
currentState_ = state;
return;
}
// Otherwise, copy across the string.
auto bytes_needed =
std::min(static_cast<uint32_t>(state.text_length + 1),
static_cast<uint32_t>(stateStringBuffer_.size()));
currentState_.text_UTF8 = stateStringBuffer_.data();
std::copy(state.text_UTF8, state.text_UTF8 + bytes_needed - 1,
stateStringBuffer_.data());
currentState_.text_length = state.text_length;
currentState_.selection = state.selection;
currentState_.composingRegion = state.composingRegion;
stateStringBuffer_[bytes_needed - 1] = 0;
}
void GameTextInput::setInputConnection(jobject inputConnection) {
if (inputConnection_ != NULL) {
env_->DeleteGlobalRef(inputConnection_);
}
inputConnection_ = env_->NewGlobalRef(inputConnection);
}
/*static*/ void GameTextInput::processCallback(
void *context, const GameTextInputState *state) {
auto thiz = static_cast<GameTextInput *>(context);
if (state != nullptr) thiz->setStateInner(*state);
}
void GameTextInput::processEvent(jobject textInputEvent) {
stateFromJava(textInputEvent, processCallback, this);
if (eventCallback_) {
eventCallback_(eventCallbackContext_, &currentState_);
}
}
void GameTextInput::showIme(uint32_t flags) {
if (inputConnection_ == nullptr) return;
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, true,
flags);
}
void GameTextInput::setEventCallback(GameTextInputEventCallback callback,
void *context) {
eventCallback_ = callback;
eventCallbackContext_ = context;
}
void GameTextInput::setImeInsetsCallback(
GameTextInputImeInsetsCallback callback, void *context) {
insetsCallback_ = callback;
insetsCallbackContext_ = context;
}
void GameTextInput::processImeInsets(const ARect *insets) {
currentInsets_ = *insets;
if (insetsCallback_) {
insetsCallback_(insetsCallbackContext_, &currentInsets_);
}
}
void GameTextInput::hideIme(uint32_t flags) {
if (inputConnection_ == nullptr) return;
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, false,
flags);
}
jobject GameTextInput::stateToJava(const GameTextInputState &state) const {
static jmethodID constructor = nullptr;
if (constructor == nullptr) {
constructor = env_->GetMethodID(stateJavaClass_, "<init>",
"(Ljava/lang/String;IIII)V");
if (constructor == nullptr) {
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
"Can't find gametextinput.State constructor");
return nullptr;
}
}
const char *text = state.text_UTF8;
if (text == nullptr) {
static char empty_string[] = "";
text = empty_string;
}
// Note that this expects 'modified' UTF-8 which is not the same as UTF-8
// https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
jstring jtext = env_->NewStringUTF(text);
jobject jobj =
env_->NewObject(stateJavaClass_, constructor, jtext,
state.selection.start, state.selection.end,
state.composingRegion.start, state.composingRegion.end);
env_->DeleteLocalRef(jtext);
return jobj;
}
void GameTextInput::stateFromJava(jobject textInputEvent,
GameTextInputGetStateCallback callback,
void *context) const {
jstring text =
(jstring)env_->GetObjectField(textInputEvent, stateClassInfo_.text);
// Note this is 'modified' UTF-8, not true UTF-8. It has no NULLs in it,
// except at the end. It's actually not specified whether the value returned
// by GetStringUTFChars includes a null at the end, but it *seems to* on
// Android.
const char *text_chars = env_->GetStringUTFChars(text, NULL);
int text_len = env_->GetStringUTFLength(
text); // Length in bytes, *not* including the null.
int selectionStart =
env_->GetIntField(textInputEvent, stateClassInfo_.selectionStart);
int selectionEnd =
env_->GetIntField(textInputEvent, stateClassInfo_.selectionEnd);
int composingRegionStart =
env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionStart);
int composingRegionEnd =
env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionEnd);
GameTextInputState state{text_chars,
text_len,
{selectionStart, selectionEnd},
{composingRegionStart, composingRegionEnd}};
callback(context, &state);
env_->ReleaseStringUTFChars(text, text_chars);
env_->DeleteLocalRef(text);
}

View File

@ -0,0 +1,290 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup game_text_input Game Text Input
* The interface to use GameTextInput.
* @{
*/
#pragma once
#include <android/rect.h>
#include <jni.h>
#include <stdint.h>
#include "gamecommon.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* 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
* start==end. An undefined span is specified with start = end = SPAN_UNDEFINED.
*/
typedef struct GameTextInputSpan {
/** The start of the region (inclusive). */
int32_t start;
/** The end of the region (exclusive). */
int32_t end;
} GameTextInputSpan;
/**
* Values with special meaning in a GameTextInputSpan.
*/
enum GameTextInputSpanFlag { SPAN_UNDEFINED = -1 };
/**
* This struct holds the state of an editable section of text.
* The text can have a selection and a composing region defined on it.
* A composing region is used by IMEs that allow input using multiple steps to
* compose a glyph or word. Use functions GameTextInput_getState and
* GameTextInput_setState to read and modify the state that an IME is editing.
*/
typedef struct GameTextInputState {
/**
* Text owned by the state, as a modified UTF-8 string. Null-terminated.
* https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
*/
const char *text_UTF8;
/**
* Length in bytes of text_UTF8, *not* including the null at end.
*/
int32_t text_length;
/**
* A selection defined on the text.
*/
GameTextInputSpan selection;
/**
* A composing region defined on the text.
*/
GameTextInputSpan composingRegion;
} GameTextInputState;
/**
* A callback called by GameTextInput_getState.
* @param context User-defined context.
* @param state State, owned by the library, that will be valid for the duration
* of the callback.
*/
typedef void (*GameTextInputGetStateCallback)(
void *context, const struct GameTextInputState *state);
/**
* Opaque handle to the GameTextInput API.
*/
typedef struct GameTextInput GameTextInput;
/**
* Initialize the GameTextInput library.
* If called twice without GameTextInput_destroy being called, the same pointer
* will be returned and a warning will be issued.
* @param env A JNI env valid on the calling thread.
* @param max_string_size The maximum length of a string that can be edited. If
* zero, the maximum defaults to 65536 bytes. A buffer of this size is allocated
* at initialization.
* @return A handle to the library.
*/
GameTextInput *GameTextInput_init(JNIEnv *env, uint32_t max_string_size);
/**
* When using GameTextInput, you need to create a gametextinput.InputConnection
* on the Java side and pass it using this function to the library, unless using
* GameActivity in which case this will be done for you. See the GameActivity
* source code or GameTextInput samples for examples of usage.
* @param input A valid GameTextInput library handle.
* @param inputConnection A gametextinput.InputConnection object.
*/
void GameTextInput_setInputConnection(GameTextInput *input,
jobject inputConnection);
/**
* Unless using GameActivity, it is required to call this function from your
* Java gametextinput.Listener.stateChanged method to convert eventState and
* trigger any event callbacks. When using GameActivity, this does not need to
* be called as event processing is handled by the Activity.
* @param input A valid GameTextInput library handle.
* @param eventState A Java gametextinput.State object.
*/
void GameTextInput_processEvent(GameTextInput *input, jobject eventState);
/**
* Free any resources owned by the GameTextInput library.
* Any subsequent calls to the library will fail until GameTextInput_init is
* called again.
* @param input A valid GameTextInput library handle.
*/
void GameTextInput_destroy(GameTextInput *input);
/**
* Flags to be passed to GameTextInput_showIme.
*/
enum ShowImeFlags {
SHOW_IME_UNDEFINED = 0, // Default value.
SHOW_IMPLICIT =
1, // Indicates that the user has forced the input method open so it
// should not be closed until they explicitly do so.
SHOW_FORCED = 2 // Indicates that this is an implicit request to show the
// input window, not as the result of a direct request by
// the user. The window may not be shown in this case.
};
/**
* Show the IME. Calls InputMethodManager.showSoftInput().
* @param input A valid GameTextInput library handle.
* @param flags Defined in ShowImeFlags above. For more information see:
* https://developer.android.com/reference/android/view/inputmethod/InputMethodManager
*/
void GameTextInput_showIme(GameTextInput *input, uint32_t flags);
/**
* Flags to be passed to GameTextInput_hideIme.
*/
enum HideImeFlags {
HIDE_IME_UNDEFINED = 0, // Default value.
HIDE_IMPLICIT_ONLY =
1, // Indicates that the soft input window should only be hidden if it
// was not explicitly shown by the user.
HIDE_NOT_ALWAYS =
2, // Indicates that the soft input window should normally be hidden,
// unless it was originally shown with SHOW_FORCED.
};
/**
* Show the IME. Calls InputMethodManager.hideSoftInputFromWindow().
* @param input A valid GameTextInput library handle.
* @param flags Defined in HideImeFlags above. For more information see:
* https://developer.android.com/reference/android/view/inputmethod/InputMethodManager
*/
void GameTextInput_hideIme(GameTextInput *input, uint32_t flags);
/**
* 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
* callback rather than returning the state in order to simplify ownership of
* text_UTF8 strings. These strings are only valid during the calling of the
* callback.
* @param input A valid GameTextInput library handle.
* @param callback A function that will be called with valid state.
* @param context Context used by the callback.
*/
void GameTextInput_getState(GameTextInput *input,
GameTextInputGetStateCallback callback,
void *context);
/**
* Set the current GameTextInput state. This state is reflected to any active
* IME.
* @param input A valid GameTextInput library handle.
* @param state The state to set. Ownership is maintained by the caller and must
* remain valid for the duration of the call.
*/
void GameTextInput_setState(GameTextInput *input,
const GameTextInputState *state);
/**
* Type of the callback needed by GameTextInput_setEventCallback that will be
* called every time the IME state changes.
* @param context User-defined context set in GameTextInput_setEventCallback.
* @param current_state Current IME state, owned by the library and valid during
* the callback.
*/
typedef void (*GameTextInputEventCallback)(
void *context, const GameTextInputState *current_state);
/**
* Optionally set a callback to be called whenever the IME state changes.
* Not necessary if you are using GameActivity, which handles these callbacks
* for you.
* @param input A valid GameTextInput library handle.
* @param callback Called by the library when the IME state changes.
* @param context Context passed as first argument to the callback.
*/
void GameTextInput_setEventCallback(GameTextInput *input,
GameTextInputEventCallback callback,
void *context);
/**
* Type of the callback needed by GameTextInput_setImeInsetsCallback that will
* be called every time the IME window insets change.
* @param context User-defined context set in
* GameTextInput_setImeWIndowInsetsCallback.
* @param current_insets Current IME insets, owned by the library and valid
* during the callback.
*/
typedef void (*GameTextInputImeInsetsCallback)(void *context,
const ARect *current_insets);
/**
* Optionally set a callback to be called whenever the IME insets change.
* Not necessary if you are using GameActivity, which handles these callbacks
* for you.
* @param input A valid GameTextInput library handle.
* @param callback Called by the library when the IME insets change.
* @param context Context passed as first argument to the callback.
*/
void GameTextInput_setImeInsetsCallback(GameTextInput *input,
GameTextInputImeInsetsCallback callback,
void *context);
/**
* Get the current window insets for the IME.
* @param input A valid GameTextInput library handle.
* @param insets Filled with the current insets by this function.
*/
void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets);
/**
* Unless using GameActivity, it is required to call this function from your
* Java gametextinput.Listener.onImeInsetsChanged method to
* trigger any event callbacks. When using GameActivity, this does not need to
* be called as insets processing is handled by the Activity.
* @param input A valid GameTextInput library handle.
* @param eventState A Java gametextinput.State object.
*/
void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets);
/**
* Convert a GameTextInputState struct to a Java gametextinput.State object.
* Don't forget to delete the returned Java local ref when you're done.
* @param input A valid GameTextInput library handle.
* @param state Input state to convert.
* @return A Java object of class gametextinput.State. The caller is required to
* delete this local reference.
*/
jobject GameTextInputState_toJava(const GameTextInput *input,
const GameTextInputState *state);
/**
* Convert from a Java gametextinput.State object into a C GameTextInputState
* struct.
* @param input A valid GameTextInput library handle.
* @param state A Java gametextinput.State object.
* @param callback A function called with the C struct, valid for the duration
* of the call.
* @param context Context passed to the callback.
*/
void GameTextInputState_fromJava(const GameTextInput *input, jobject state,
GameTextInputGetStateCallback callback,
void *context);
#ifdef __cplusplus
}
#endif
/** @} */

View File

@ -0,0 +1 @@
{"export_libraries":[],"library_name":null,"android":{"export_libraries":null,"library_name":null}}

View File

@ -0,0 +1,44 @@
#
# Copyright (C) 2021 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
#
# https://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.
#
project(paddleboat C CXX)
include_directories(${CMAKE_CURRENT_LIST_DIR}/paddleboat/include)
set( PADDLEBOAT_SRCS
${CMAKE_CURRENT_LIST_DIR}/InternalControllerTable.cpp
${CMAKE_CURRENT_LIST_DIR}/GameController.cpp
${CMAKE_CURRENT_LIST_DIR}/GameControllerDeviceInfo.cpp
${CMAKE_CURRENT_LIST_DIR}/GameControllerLog.cpp
${CMAKE_CURRENT_LIST_DIR}/GameControllerManager.cpp
${CMAKE_CURRENT_LIST_DIR}/GameControllerMappingUtils.cpp
${CMAKE_CURRENT_LIST_DIR}/paddleboat_c.cpp)
# set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Os")
# set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
# set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g0")
add_library(paddleboat_static STATIC ${PADDLEBOAT_SRCS})
set_target_properties( paddleboat_static PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/build )
add_library(paddleboat SHARED ${CMAKE_CURRENT_LIST_DIR}/paddleboat_c.cpp)
target_link_libraries(paddleboat
paddleboat_static
android
atomic
log)

View File

@ -0,0 +1,458 @@
/*
* Copyright (C) 2021 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
*
* https://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 "GameController.h"
#include <android/input.h>
#include <math.h>
#include <chrono>
#include <cstdlib>
#include <memory>
#include "GameControllerInternalConstants.h"
namespace paddleboat {
struct ControllerButtonMap {
int32_t buttonKeycodes[PADDLEBOAT_BUTTON_COUNT];
};
const ControllerButtonMap defaultButtonMap = {{
AKEYCODE_DPAD_UP, // PADDLEBOAT_BUTTON_DPAD_UP
AKEYCODE_DPAD_LEFT, // PADDLEBOAT_BUTTON_DPAD_LEFT
AKEYCODE_DPAD_DOWN, // PADDLEBOAT_BUTTON_DPAD_DOWN
AKEYCODE_DPAD_RIGHT, // PADDLEBOAT_BUTTON_DPAD_RIGHT
AKEYCODE_BUTTON_A, // PADDLEBOAT_BUTTON_A
AKEYCODE_BUTTON_B, // PADDLEBOAT_BUTTON_B
AKEYCODE_BUTTON_X, // PADDLEBOAT_BUTTON_X
AKEYCODE_BUTTON_Y, // PADDLEBOAT_BUTTON_Y
AKEYCODE_BUTTON_L1, // PADDLEBOAT_BUTTON_L1
AKEYCODE_BUTTON_L2, // PADDLEBOAT_BUTTON_L2
AKEYCODE_BUTTON_THUMBL, // PADDLEBOAT_BUTTON_L3
AKEYCODE_BUTTON_R1, // PADDLEBOAT_BUTTON_R1
AKEYCODE_BUTTON_R2, // PADDLEBOAT_BUTTON_R2
AKEYCODE_BUTTON_THUMBR, // PADDLEBOAT_BUTTON_R3
AKEYCODE_BUTTON_SELECT, // PADDLEBOAT_BUTTON_SELECT
AKEYCODE_BUTTON_START, // PADDLEBOAT_BUTTON_START
AKEYCODE_BUTTON_MODE, // PADDLEBOAT_BUTTON_SYSTEM
0, // PADDLEBOAT_BUTTON_TOUCHPAD
0, // PADDLEBOAT_BUTTON_AUX1
0, // PADDLEBOAT_BUTTON_AUX2
0, // PADDLEBOAT_BUTTON_AUX3
0 // PADDLEBOAT_BUTTON_AUX4
}};
// Axis must be at least this value to trigger a mapped button press
constexpr float AXIS_BUTTON_THRESHOLD = 0.1f;
void resetData(Paddleboat_Controller_Data &pbData) {
pbData.timestamp = 0;
pbData.buttonsDown = 0;
pbData.leftStick.stickX = 0.0f;
pbData.leftStick.stickY = 0.0f;
pbData.rightStick.stickX = 0.0f;
pbData.rightStick.stickY = 0.0f;
pbData.triggerL1 = 0.0f;
pbData.triggerL2 = 0.0f;
pbData.triggerR1 = 0.0f;
pbData.triggerR2 = 0.0f;
pbData.virtualPointer.pointerX = 0.0f;
pbData.virtualPointer.pointerY = 0.0f;
}
void resetInfo(Paddleboat_Controller_Info &pbInfo) {
pbInfo.controllerNumber = -1;
pbInfo.controllerFlags = 0;
pbInfo.vendorId = 0;
pbInfo.productId = 0;
pbInfo.deviceId = -1;
pbInfo.leftStickPrecision.stickFlatX = 0.0f;
pbInfo.leftStickPrecision.stickFlatY = 0.0f;
pbInfo.leftStickPrecision.stickFuzzX = 0.0f;
pbInfo.leftStickPrecision.stickFuzzY = 0.0f;
pbInfo.rightStickPrecision.stickFlatX = 0.0f;
pbInfo.rightStickPrecision.stickFlatY = 0.0f;
pbInfo.rightStickPrecision.stickFuzzX = 0.0f;
pbInfo.rightStickPrecision.stickFuzzY = 0.0f;
}
GameController::GameController()
: mConnectionIndex(-1),
mControllerData(),
mControllerInfo(),
mAxisInfo(),
mDeviceInfo(),
mControllerDataDirty(true) {
memset(mButtonKeycodes, 0, sizeof(mButtonKeycodes));
resetControllerData();
}
void GameController::resetControllerData() {
resetData(mControllerData);
resetInfo(mControllerInfo);
}
void GameController::setupController(
const Paddleboat_Controller_Mapping_Data *mappingData) {
const GameControllerDeviceInfo::InfoFields &infoFields =
*(mDeviceInfo.getInfo());
uint64_t axisLow = static_cast<uint64_t>(infoFields.mAxisBitsLow);
uint64_t axisHigh = static_cast<uint64_t>(infoFields.mAxisBitsHigh);
mControllerAxisMask = axisLow | (axisHigh << 32ULL);
mControllerInfo.controllerFlags = infoFields.mControllerFlags;
mControllerInfo.controllerNumber = infoFields.mControllerNumber;
mControllerInfo.deviceId = infoFields.mDeviceId;
mControllerInfo.productId = infoFields.mProductId;
mControllerInfo.vendorId = infoFields.mVendorId;
if (mappingData != nullptr) {
mControllerInfo.controllerFlags |= mappingData->flags;
for (int32_t i = 0; i < PADDLEBOAT_BUTTON_COUNT; ++i) {
if (mappingData->buttonMapping[i] != PADDLEBOAT_BUTTON_IGNORED) {
mButtonKeycodes[i] = mappingData->buttonMapping[i];
}
}
for (int32_t i = 0; i < PADDLEBOAT_MAPPING_AXIS_COUNT; ++i) {
if (mappingData->axisMapping[i] != PADDLEBOAT_AXIS_IGNORED) {
const GameControllerAxis gcAxis =
static_cast<GameControllerAxis>(i);
const int32_t nativeAxisId = mappingData->axisMapping[i];
const int32_t positiveButton =
(mappingData->axisPositiveButtonMapping[i] ==
PADDLEBOAT_AXIS_BUTTON_IGNORED)
? 0
: (1 << mappingData->axisPositiveButtonMapping[i]);
const int32_t negativeButton =
(mappingData->axisNegativeButtonMapping[i] ==
PADDLEBOAT_AXIS_BUTTON_IGNORED)
? 0
: (1 << mappingData->axisNegativeButtonMapping[i]);
setupAxis(gcAxis, nativeAxisId, nativeAxisId, positiveButton,
negativeButton);
}
}
adjustAxisConstants();
} else {
// Fallback defaults if there wasn't mapping data provided
initializeDefaultAxisMapping();
memcpy(mButtonKeycodes, &defaultButtonMap, sizeof(ControllerButtonMap));
mControllerInfo.controllerFlags |=
PADDLEBOAT_CONTROLLER_FLAG_GENERIC_PROFILE;
}
}
void GameController::initializeDefaultAxisMapping() {
setupAxis(GAMECONTROLLER_AXIS_LSTICK_X, AMOTION_EVENT_AXIS_X,
AMOTION_EVENT_AXIS_X, 0, 0);
setupAxis(GAMECONTROLLER_AXIS_LSTICK_Y, AMOTION_EVENT_AXIS_Y,
AMOTION_EVENT_AXIS_Y, 0, 0);
setupAxis(GAMECONTROLLER_AXIS_RSTICK_X, AMOTION_EVENT_AXIS_Z,
AMOTION_EVENT_AXIS_RX, 0, 0);
setupAxis(GAMECONTROLLER_AXIS_RSTICK_Y, AMOTION_EVENT_AXIS_RZ,
AMOTION_EVENT_AXIS_RY, 0, 0);
setupAxis(GAMECONTROLLER_AXIS_L2, AMOTION_EVENT_AXIS_LTRIGGER,
AMOTION_EVENT_AXIS_BRAKE, PADDLEBOAT_BUTTON_L2, 0);
setupAxis(GAMECONTROLLER_AXIS_R2, AMOTION_EVENT_AXIS_RTRIGGER,
AMOTION_EVENT_AXIS_GAS, PADDLEBOAT_BUTTON_R2, 0);
setupAxis(GAMECONTROLLER_AXIS_HAT_X, AMOTION_EVENT_AXIS_HAT_X,
AMOTION_EVENT_AXIS_HAT_X, PADDLEBOAT_BUTTON_DPAD_RIGHT,
PADDLEBOAT_BUTTON_DPAD_LEFT);
setupAxis(GAMECONTROLLER_AXIS_HAT_Y, AMOTION_EVENT_AXIS_HAT_Y,
AMOTION_EVENT_AXIS_HAT_Y, PADDLEBOAT_BUTTON_DPAD_DOWN,
PADDLEBOAT_BUTTON_DPAD_UP);
adjustAxisConstants();
}
void GameController::adjustAxisConstants() {
if (mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X].axisIndex >= 0) {
const bool stickAxisAdjust =
((mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X].axisFlags &
GAMECONTROLLER_AXIS_FLAG_APPLY_ADJUSTMENTS) != 0);
mControllerInfo.leftStickPrecision.stickFlatX =
mDeviceInfo.getFlatArray()[mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X]
.axisIndex];
mControllerInfo.leftStickPrecision.stickFlatY =
mDeviceInfo.getFlatArray()[mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_Y]
.axisIndex];
mControllerInfo.leftStickPrecision.stickFuzzX =
mDeviceInfo.getFuzzArray()[mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X]
.axisIndex];
mControllerInfo.leftStickPrecision.stickFuzzY =
mDeviceInfo.getFuzzArray()[mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_Y]
.axisIndex];
if (stickAxisAdjust) {
// We are adjusting the raw axis values, so we also adjust the
// 'flat' and 'fuzz' values for the sticks
mControllerInfo.leftStickPrecision.stickFlatX *=
mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X].axisMultiplier;
mControllerInfo.leftStickPrecision.stickFlatY *=
mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_Y].axisMultiplier;
mControllerInfo.leftStickPrecision.stickFuzzX *=
mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X].axisMultiplier;
mControllerInfo.leftStickPrecision.stickFuzzY *=
mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_Y].axisMultiplier;
}
}
if (mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X].axisIndex >= 0) {
const bool stickAxisAdjust =
((mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X].axisFlags &
GAMECONTROLLER_AXIS_FLAG_APPLY_ADJUSTMENTS) != 0);
mControllerInfo.rightStickPrecision.stickFlatX =
mDeviceInfo.getFlatArray()[mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X]
.axisIndex];
mControllerInfo.rightStickPrecision.stickFlatY =
mDeviceInfo.getFlatArray()[mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_Y]
.axisIndex];
mControllerInfo.rightStickPrecision.stickFuzzX =
mDeviceInfo.getFuzzArray()[mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X]
.axisIndex];
mControllerInfo.rightStickPrecision.stickFuzzY =
mDeviceInfo.getFuzzArray()[mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_Y]
.axisIndex];
if (stickAxisAdjust) {
// We are adjusting the raw axis values, so we also adjust the
// 'flat' and 'fuzz' values for the sticks
mControllerInfo.rightStickPrecision.stickFlatX *=
mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X].axisMultiplier;
mControllerInfo.rightStickPrecision.stickFlatY *=
mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_Y].axisMultiplier;
mControllerInfo.rightStickPrecision.stickFuzzX *=
mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X].axisMultiplier;
mControllerInfo.rightStickPrecision.stickFuzzY *=
mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_Y].axisMultiplier;
}
}
}
void GameController::setupAxis(const GameControllerAxis gcAxis,
const int32_t preferredNativeAxisId,
const int32_t secondaryNativeAxisId,
const int32_t buttonMask,
const int32_t buttonNegativeMask) {
mAxisInfo[gcAxis].resetInfo();
mAxisInfo[gcAxis].axisButtonMask = buttonMask;
mAxisInfo[gcAxis].axisButtonNegativeMask = buttonNegativeMask;
// Do we have a mapping for the preferred native axis?
if (preferredNativeAxisId < MAX_AXIS_COUNT &&
secondaryNativeAxisId < MAX_AXIS_COUNT) {
const int32_t preferredMask = (1U << preferredNativeAxisId);
const int32_t secondaryMask = (1U << secondaryNativeAxisId);
if ((mControllerAxisMask & preferredMask) != 0) {
// preferred axis is mapped
mAxisInfo[gcAxis].axisIndex = preferredNativeAxisId;
} else if ((preferredNativeAxisId != secondaryNativeAxisId) &&
((mControllerAxisMask & secondaryMask) != 0)) {
// secondary axis is mapped
mAxisInfo[gcAxis].axisIndex = secondaryNativeAxisId;
} else if (buttonMask) {
// There wasn't a matching axis,
// but we will fake this axis using a digital button mapping
mAxisInfo[gcAxis].axisFlags |=
GAMECONTROLLER_AXIS_FLAG_DIGITAL_TRIGGER;
}
// If we found a native axis, check its ranges and see if we need to set
// up adjustment values if the ranges aren't -1.0 to 1.0 for a stick or
// 0.0 to 1.0 for a trigger
if (mAxisInfo[gcAxis].axisIndex >= 0) {
bool isStickAxis = (!(gcAxis >= GAMECONTROLLER_AXIS_L1 &&
gcAxis <= GAMECONTROLLER_AXIS_R2));
const float minAdjust = isStickAxis ? 1.0f : 0.0f;
const float rawMin =
mDeviceInfo.getMinArray()[mAxisInfo[gcAxis].axisIndex];
const float rawMax =
mDeviceInfo.getMaxArray()[mAxisInfo[gcAxis].axisIndex];
const float diffMin = fabsf(rawMin + minAdjust);
const float diffMax = fabsf(1.0f - rawMax);
if (!(diffMin <= FLT_MIN && diffMax <= FLT_MIN)) {
mAxisInfo[gcAxis].axisFlags |=
GAMECONTROLLER_AXIS_FLAG_APPLY_ADJUSTMENTS;
if (isStickAxis) {
// normalize min and max axis to 1.0
mAxisInfo[gcAxis].axisMultiplier = 1.0f / rawMax;
// Make sure center of stick is 0.0
const float rawCenter = ((rawMax - rawMin) * 0.5f) + rawMin;
if (rawCenter >= FLT_MIN) {
mAxisInfo[gcAxis].axisAdjust =
-(rawCenter * mAxisInfo[gcAxis].axisMultiplier);
}
} else {
// This case is hit on PS5 API <= 30 having weird trigger
// axis mappings of: L2=RX R2=RY with a -1.0 to 1.0 range
mAxisInfo[gcAxis].axisMultiplier = 1.0f / (rawMax - rawMin);
mAxisInfo[gcAxis].axisAdjust =
(-rawMin) * mAxisInfo[gcAxis].axisMultiplier;
}
}
}
}
}
int32_t GameController::processGameActivityKeyEvent(
const Paddleboat_GameActivityKeyEvent *event, const size_t eventSize) {
return processKeyEventInternal(event->keyCode, event->action);
}
int32_t GameController::processGameActivityMotionEvent(
const Paddleboat_GameActivityMotionEvent *event, const size_t eventSize) {
int32_t handledEvent = IGNORED_EVENT;
if (event->pointerCount > 0) {
handledEvent =
processMotionEventInternal(event->pointers->axisValues, nullptr);
}
return handledEvent;
}
int32_t GameController::processKeyEvent(const AInputEvent *event) {
const int32_t eventKeyCode = AKeyEvent_getKeyCode(event);
const int32_t eventKeyAction = AKeyEvent_getAction(event);
return processKeyEventInternal(eventKeyCode, eventKeyAction);
}
int32_t GameController::processKeyEventInternal(const int32_t eventKeyCode,
const int32_t eventKeyAction) {
int32_t handledEvent = IGNORED_EVENT;
int32_t buttonMask = 0;
const bool bDown = (eventKeyAction == AKEY_EVENT_ACTION_DOWN);
for (uint32_t i = 0; i < PADDLEBOAT_BUTTON_COUNT; ++i) {
if (eventKeyCode == mButtonKeycodes[i]) {
int32_t newButtonFlag = (1 << i);
buttonMask |= newButtonFlag;
// If we have no analog axis mapped for a trigger, allow the
// button events to set the analog trigger data
switch (newButtonFlag) {
case PADDLEBOAT_BUTTON_L1:
if (mAxisInfo[GAMECONTROLLER_AXIS_L1].axisIndex == -1) {
mControllerData.triggerL1 = bDown ? 1.0f : 0.0f;
}
break;
case PADDLEBOAT_BUTTON_L2:
if (mAxisInfo[GAMECONTROLLER_AXIS_L2].axisIndex == -1) {
mControllerData.triggerL2 = bDown ? 1.0f : 0.0f;
}
break;
case PADDLEBOAT_BUTTON_R1:
if (mAxisInfo[GAMECONTROLLER_AXIS_R1].axisIndex == -1) {
mControllerData.triggerR1 = bDown ? 1.0f : 0.0f;
}
break;
case PADDLEBOAT_BUTTON_R2:
if (mAxisInfo[GAMECONTROLLER_AXIS_R2].axisIndex == -1) {
mControllerData.triggerR2 = bDown ? 1.0f : 0.0f;
}
break;
default:
break;
}
break;
}
}
if (buttonMask != 0) {
if (bDown) {
mControllerData.buttonsDown |= buttonMask;
setControllerDataDirty(true);
} else {
mControllerData.buttonsDown &= (~buttonMask);
setControllerDataDirty(true);
}
handledEvent = HANDLED_EVENT;
}
return handledEvent;
}
int32_t GameController::processMotionEvent(const AInputEvent *event) {
return processMotionEventInternal(nullptr, event);
}
int32_t GameController::processMotionEventInternal(const float *axisArray,
const AInputEvent *event) {
int32_t handledEvent = IGNORED_EVENT;
for (uint32_t axis = GAMECONTROLLER_AXIS_LSTICK_X;
axis < GAMECONTROLLER_AXIS_COUNT; ++axis) {
if (mAxisInfo[axis].axisIndex >= 0 &&
mAxisInfo[axis].axisIndex <
PADDLEBOAT_GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
float axisValue = 0.0f;
if (axisArray != nullptr) {
axisValue = axisArray[mAxisInfo[axis].axisIndex];
} else if (event != nullptr) {
axisValue = AMotionEvent_getAxisValue(
event, mAxisInfo[axis].axisIndex, 0);
}
if ((mAxisInfo[axis].axisFlags &
GAMECONTROLLER_AXIS_FLAG_APPLY_ADJUSTMENTS) != 0) {
axisValue = ((axisValue * mAxisInfo[axis].axisMultiplier) +
mAxisInfo[axis].axisAdjust);
}
// We take advantage of the GameControllerAxis matching the axis
// order in the Paddleboat_Controller_Data struct to use the current
// axis as an index into the axis entries in the
// Paddleboat_Controller_Data struct
if (axis < GAMECONTROLLER_AXIS_HAT_X) {
float *axisData = &mControllerData.leftStick.stickX;
axisData[axis] = axisValue;
}
// If this axis has a button associated with it, set/clear the flags
// as appropriate
if (mAxisInfo[axis].axisButtonMask != 0 ||
mAxisInfo[axis].axisButtonNegativeMask) {
if (axisValue > -AXIS_BUTTON_THRESHOLD &&
axisValue < AXIS_BUTTON_THRESHOLD) {
const uint32_t buttonMask =
mAxisInfo[axis].axisButtonMask |
mAxisInfo[axis].axisButtonNegativeMask;
mControllerData.buttonsDown &= (~buttonMask);
} else if (axisValue > AXIS_BUTTON_THRESHOLD) {
mControllerData.buttonsDown |=
mAxisInfo[axis].axisButtonMask;
} else if (axisValue < -AXIS_BUTTON_THRESHOLD) {
mControllerData.buttonsDown |=
mAxisInfo[axis].axisButtonNegativeMask;
}
}
setControllerDataDirty(true);
handledEvent = HANDLED_EVENT;
}
}
return handledEvent;
}
void GameController::setControllerDataDirty(const bool dirty) {
mControllerDataDirty = dirty;
if (dirty) {
// update the timestamp any time we mark dirty
const auto timestamp =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
mControllerData.timestamp = static_cast<uint64_t>(timestamp);
}
}
} // namespace paddleboat

View File

@ -0,0 +1,165 @@
/*
* Copyright (C) 2021 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
*
* https://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.
*/
#pragma once
#include <android/input.h>
#include "GameControllerDeviceInfo.h"
#include "GameControllerGameActivityMirror.h"
#include "paddleboat.h"
namespace paddleboat {
// set if axisMultiplier/axisAdjustment should be applied (raw device axis isn't
// -1.0 to 1.0)
inline constexpr uint32_t GAMECONTROLLER_AXIS_FLAG_APPLY_ADJUSTMENTS =
(1U << 0);
// set if trigger is being faked as an analog axis (device just has a on/off
// button flag)
inline constexpr uint32_t GAMECONTROLLER_AXIS_FLAG_DIGITAL_TRIGGER = (1U << 1);
class GameController {
public:
enum GameControllerAxis {
GAMECONTROLLER_AXIS_LSTICK_X = 0,
GAMECONTROLLER_AXIS_LSTICK_Y,
GAMECONTROLLER_AXIS_RSTICK_X,
GAMECONTROLLER_AXIS_RSTICK_Y,
GAMECONTROLLER_AXIS_L1,
GAMECONTROLLER_AXIS_L2,
GAMECONTROLLER_AXIS_R1,
GAMECONTROLLER_AXIS_R2,
GAMECONTROLLER_AXIS_HAT_X,
GAMECONTROLLER_AXIS_HAT_Y,
GAMECONTROLLER_AXIS_COUNT
};
struct GameControllerAxisInfo {
// Index into the device axis array, -1 is unmapped
int32_t axisIndex = -1;
// See GAMECONTROLLER_AXIS_FLAG constants
uint32_t axisFlags = 0;
// Button mask flag, if backed/shadowed by digital button when axis >
// 0.0
uint32_t axisButtonMask = 0;
// Button mask flag, if backed/shadowed by digital button when axis <
// 0.0
uint32_t axisButtonNegativeMask = 0;
// Multiplier to normalize to a 0.0 center -> 1.0 edge
float axisMultiplier = 1.0f;
// Adjustment to bring center to 0.0 if necessary
float axisAdjust = 0.0f;
void resetInfo() {
axisIndex = -1;
axisFlags = 0;
axisButtonMask = 0;
axisButtonNegativeMask = 0;
axisMultiplier = 1.0f;
axisAdjust = 0.0f;
}
};
GameController();
void setupController(const Paddleboat_Controller_Mapping_Data *mappingData);
void initializeDefaultAxisMapping();
int32_t processGameActivityKeyEvent(
const Paddleboat_GameActivityKeyEvent *event, const size_t eventSize);
int32_t processGameActivityMotionEvent(
const Paddleboat_GameActivityMotionEvent *event,
const size_t eventSize);
int32_t processKeyEvent(const AInputEvent *event);
int32_t processMotionEvent(const AInputEvent *event);
Paddleboat_ControllerStatus getControllerStatus() const {
return mControllerStatus;
}
void setControllerStatus(
const Paddleboat_ControllerStatus controllerStatus) {
mControllerStatus = controllerStatus;
}
int32_t getConnectionIndex() const { return mConnectionIndex; }
void setConnectionIndex(const int32_t connectionIndex) {
mConnectionIndex = connectionIndex;
}
uint64_t getControllerAxisMask() const { return mControllerAxisMask; }
Paddleboat_Controller_Data &getControllerData() { return mControllerData; }
const Paddleboat_Controller_Data &getControllerData() const {
return mControllerData;
}
Paddleboat_Controller_Info &getControllerInfo() { return mControllerInfo; }
const Paddleboat_Controller_Info &getControllerInfo() const {
return mControllerInfo;
}
GameControllerDeviceInfo &getDeviceInfo() { return mDeviceInfo; }
const GameControllerDeviceInfo &getDeviceInfo() const {
return mDeviceInfo;
}
GameControllerAxisInfo *getAxisInfo() { return mAxisInfo; }
const GameControllerAxisInfo *getAxisInfo() const { return mAxisInfo; }
bool getControllerDataDirty() const { return mControllerDataDirty; }
void setControllerDataDirty(const bool dirty);
void resetControllerData();
private:
int32_t processKeyEventInternal(const int32_t eventKeyCode,
const int32_t eventKeyAction);
int32_t processMotionEventInternal(const float *axisArray,
const AInputEvent *event);
void setupAxis(const GameControllerAxis gcAxis,
const int32_t preferredNativeAxisId,
const int32_t secondaryNativeAxisId,
const int32_t buttonMask, const int32_t buttonNegativeMask);
void adjustAxisConstants();
uint64_t mControllerAxisMask = 0;
Paddleboat_ControllerStatus mControllerStatus =
PADDLEBOAT_CONTROLLER_INACTIVE;
int32_t mConnectionIndex = -1;
Paddleboat_Controller_Data mControllerData;
Paddleboat_Controller_Info mControllerInfo;
int32_t mButtonKeycodes[PADDLEBOAT_BUTTON_COUNT];
GameControllerAxisInfo mAxisInfo[GAMECONTROLLER_AXIS_COUNT];
GameControllerDeviceInfo mDeviceInfo;
// Controller data has been updated since the last time it was read
bool mControllerDataDirty;
};
} // namespace paddleboat

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 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
*
* https://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 "GameControllerDeviceInfo.h"
#include <cstring>
namespace paddleboat {
GameControllerDeviceInfo::GameControllerDeviceInfo() {
mName[0] = '\0';
mInfo.mDeviceId = -1;
mInfo.mProductId = -1;
mInfo.mVendorId = -1;
mInfo.mAxisBitsLow = 0;
mInfo.mAxisBitsHigh = 0;
mInfo.mControllerNumber = -1;
mInfo.mControllerFlags = 0;
for (size_t i = 0; i < paddleboat::MAX_AXIS_COUNT; ++i) {
mAxisMinArray[i] = 0.0f;
mAxisMaxArray[i] = 0.0f;
mAxisFlatArray[i] = 0.0f;
mAxisFuzzArray[i] = 0.0f;
}
}
void GameControllerDeviceInfo::setName(const char *name) {
strncpy(mName, name, DEVICEINFO_MAX_NAME_LENGTH);
}
} // namespace paddleboat

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2021 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
*
* https://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.
*/
#pragma once
#include "GameControllerInternalConstants.h"
namespace paddleboat {
class GameControllerDeviceInfo {
public:
// These are copied directly from the int[] mGameControllerDeviceInfoArray,
// the layout should match the field definitions in
// Paddleboat_Controller_Info.java
struct InfoFields {
int32_t mDeviceId;
int32_t mVendorId;
int32_t mProductId;
int32_t mAxisBitsLow;
int32_t mAxisBitsHigh;
int32_t mControllerNumber;
int32_t mControllerFlags;
};
GameControllerDeviceInfo();
void setName(const char *name);
const char *getName() const { return mName; }
const InfoFields &getInfo() const { return mInfo; }
const float *getMinArray() const { return mAxisMinArray; }
const float *getMaxArray() const { return mAxisMaxArray; }
const float *getFlatArray() const { return mAxisFlatArray; }
const float *getFuzzArray() const { return mAxisFuzzArray; }
InfoFields *getInfo() { return &mInfo; }
float *getMinArray() { return mAxisMinArray; }
float *getMaxArray() { return mAxisMaxArray; }
float *getFlatArray() { return mAxisFlatArray; }
float *getFuzzArray() { return mAxisFuzzArray; }
private:
char mName[paddleboat::DEVICEINFO_MAX_NAME_LENGTH];
InfoFields mInfo;
float mAxisMinArray[paddleboat::MAX_AXIS_COUNT];
float mAxisMaxArray[paddleboat::MAX_AXIS_COUNT];
float mAxisFlatArray[paddleboat::MAX_AXIS_COUNT];
float mAxisFuzzArray[paddleboat::MAX_AXIS_COUNT];
};
} // namespace paddleboat

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2021 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
*
* https://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.
*/
#pragma once
#include <cstdint>
namespace paddleboat {
// GameActivity needs to use its own event data structures for key and motion
// events. We do not want to have a dependency on GameActivity. Unfortunately,
// this means we need to internally mirror the relevant structures.
// Paddleboat_processGameActivityInputEvent includes a struct size parameter,
// and we know that the GameActivity structures will only ever add fields, so we
// can determine if we are being passed a later version of the struct than the
// mirrored internal version.
// The following should mirror GameActivity.h
#define PADDLEBOAT_GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48
#define PADDLEBOAT_MAX_NUM_POINTERS_IN_MOTION_EVENT 8
typedef struct Paddleboat_GameActivityPointerInfo {
int32_t id;
float axisValues[PADDLEBOAT_GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
float rawX;
float rawY;
} Paddleboat_GameActivityPointerInfo;
typedef struct Paddleboat_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;
Paddleboat_GameActivityPointerInfo
pointers[PADDLEBOAT_MAX_NUM_POINTERS_IN_MOTION_EVENT];
float precisionX;
float precisionY;
} Paddleboat_GameActivityMotionEvent;
typedef struct Paddleboat_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;
} Paddleboat_GameActivityKeyEvent;
} // namespace paddleboat

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2021 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
*
* https://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.
*/
#pragma once
#include <cstdint>
namespace paddleboat {
inline constexpr size_t MAX_AXIS_COUNT = 48;
// Must match GameControllerManager.DEVICEINFO_ARRAY_SIZE
inline constexpr size_t DEVICEINFO_ARRAY_SIZE = 7;
inline constexpr size_t DEVICEINFO_ARRAY_BYTESIZE =
sizeof(int32_t) * DEVICEINFO_ARRAY_SIZE;
inline constexpr size_t DEVICEINFO_MAX_NAME_LENGTH = 128;
inline constexpr int32_t IGNORED_EVENT = 0;
inline constexpr int32_t HANDLED_EVENT = 1;
} // namespace paddleboat

View File

@ -0,0 +1,122 @@
/*
* Copyright (C) 2021 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
*
* https://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 "GameControllerLog.h"
#include <android/keycodes.h>
#include "GameControllerLogStrings.h"
#include "Log.h"
#define LOG_TAG "GameControllerManager"
// Filter input event logging to qualifying 'gamecontroller' event sources
#define LOG_FILTER_PADDLEBOAT_SOURCES
#define ELEMENTS_OF(x) (sizeof(x) / sizeof(x[0]))
namespace paddleboat {
const char *LogGetInputSourceString(const int32_t eventSource) {
const char *inputSourceString = "AINPUT_SOURCE_UNKNOWN";
switch (eventSource) {
case AINPUT_SOURCE_KEYBOARD:
inputSourceString = "AINPUT_SOURCE_KEYBOARD";
break;
case AINPUT_SOURCE_DPAD:
inputSourceString = "AINPUT_SOURCE_DPAD";
break;
case AINPUT_SOURCE_GAMEPAD:
inputSourceString = "AINPUT_SOURCE_GAMEPAD";
break;
case AINPUT_SOURCE_TOUCHSCREEN:
inputSourceString = "AINPUT_SOURCE_TOUCHSCREEN";
break;
case AINPUT_SOURCE_MOUSE:
inputSourceString = "AINPUT_SOURCE_MOUSE";
break;
case AINPUT_SOURCE_STYLUS:
inputSourceString = "AINPUT_SOURCE_STYLUS";
break;
case AINPUT_SOURCE_BLUETOOTH_STYLUS:
inputSourceString = "AINPUT_SOURCE_BLUETOOTH_STYLUS";
break;
case AINPUT_SOURCE_MOUSE_RELATIVE:
inputSourceString = "AINPUT_SOURCE_MOUSE_RELATIVE";
break;
case AINPUT_SOURCE_TOUCHPAD:
inputSourceString = "AINPUT_SOURCE_TOUCHPAD";
break;
case AINPUT_SOURCE_TOUCH_NAVIGATION:
inputSourceString = "AINPUT_SOURCE_TOUCH_NAVIGATION";
break;
case AINPUT_SOURCE_JOYSTICK:
inputSourceString = "AINPUT_SOURCE_JOYSTICK";
break;
case AINPUT_SOURCE_ROTARY_ENCODER:
inputSourceString = "AINPUT_SOURCE_ROTARY_ENCODER";
break;
default:
break;
}
return inputSourceString;
}
void LogInputEvent(const AInputEvent *event) {
const int32_t eventSource = AInputEvent_getSource(event);
#if defined LOG_FILTER_PADDLEBOAT_SOURCES
if (!(eventSource == AINPUT_SOURCE_DPAD ||
eventSource == AINPUT_SOURCE_GAMEPAD ||
eventSource == AINPUT_SOURCE_JOYSTICK)) {
return;
}
#endif
const int32_t eventDeviceId = AInputEvent_getDeviceId(event);
const int32_t eventType = AInputEvent_getType(event);
const char *inputSourceString = LogGetInputSourceString(eventSource);
if (eventType == AINPUT_EVENT_TYPE_KEY) {
const int32_t eventAction = AKeyEvent_getAction(event);
const int32_t eventFlags = AKeyEvent_getFlags(event);
const int32_t eventKeycode = AKeyEvent_getKeyCode(event);
const char *actionString =
(eventAction < ELEMENTS_OF(AKEY_ACTION_STRINGS) && eventAction >= 0)
? AKEY_ACTION_STRINGS[eventAction]
: "AKEY_ACTION out of range";
const char *keycodeString =
(eventKeycode < ELEMENTS_OF(AKEYCODE_STRINGS) && eventKeycode >= 0)
? AKEYCODE_STRINGS[eventKeycode]
: "AKEYCODE out of range";
ALOGI(
"LogInputEvent\nAINPUT_EVENT_TYPE_KEY deviceId %d source %s\n%s %s "
"%08x",
eventDeviceId, inputSourceString, actionString, keycodeString,
eventFlags);
} else if (eventType == AINPUT_EVENT_TYPE_MOTION) {
const int32_t eventAction =
AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK;
const int32_t eventFlags = AMotionEvent_getFlags(event);
const char *actionString =
(eventAction < ELEMENTS_OF(AMOTION_ACTION_STRINGS) &&
eventAction >= 0)
? AMOTION_ACTION_STRINGS[eventAction]
: "AMOTION_ACTION out of range";
ALOGI(
"LogInputEvent\nAINPUT_EVENT_TYPE_MOTION deviceId %d source %s\n%s "
"%08x",
eventDeviceId, inputSourceString, actionString, eventFlags);
}
}
} // namespace paddleboat

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2021 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
*
* https://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.
*/
#pragma once
#include <android/input.h>
#include <cstdint>
namespace paddleboat {
const char *LogGetInputSourceString(const int32_t eventSource);
void LogInputEvent(const AInputEvent *event);
} // namespace paddleboat

View File

@ -0,0 +1,391 @@
/*
* Copyright (C) 2021 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
*
* https://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.
*/
#pragma once
namespace paddleboat {
// String tables for debug logging purposes that must exactly map
// to the enum tables in input.h and keycode.h
const char *AINPUT_EVENT_STRINGS[3] = {
"AINPUT_EVENT_NULL", // 0
"AINPUT_EVENT_TYPE_KEY", // 1
"AINPUT_EVENT_TYPE_MOTION" // 2
};
const char *AKEY_ACTION_STRINGS[3] = {
"AKEY_EVENT_ACTION_DOWN", // 0
"AKEY_EVENT_ACTION_UP", // 1
"AKEY_EVENT_ACTION_MULTIPLE" // 2
};
const char *AKEYCODE_STRINGS[289] = {
"AKEYCODE_UNKNOWN", // 0
"AKEYCODE_SOFT_LEFT", // 1
"AKEYCODE_SOFT_RIGHT", // 2
"AKEYCODE_HOME", // 3
"AKEYCODE_BACK", // 4
"AKEYCODE_CALL", // 5
"AKEYCODE_ENDCALL", // 6
"AKEYCODE_0", // 7
"AKEYCODE_1", // 8
"AKEYCODE_2", // 9
"AKEYCODE_3", // 10
"AKEYCODE_4", // 11
"AKEYCODE_5", // 12
"AKEYCODE_6", // 13
"AKEYCODE_7", // 14
"AKEYCODE_8", // 15
"AKEYCODE_9", // 16
"AKEYCODE_STAR", // 17
"AKEYCODE_POUND", // 18
"AKEYCODE_DPAD_UP", // 19
"AKEYCODE_DPAD_DOWN", // 20
"AKEYCODE_DPAD_LEFT", // 21
"AKEYCODE_DPAD_RIGHT", // 22
"AKEYCODE_DPAD_CENTER", // 23
"AKEYCODE_VOLUME_UP", // 24
"AKEYCODE_VOLUME_DOWN", // 25
"AKEYCODE_POWER", // 26
"AKEYCODE_CAMERA", // 27
"AKEYCODE_CLEAR", // 28
"AKEYCODE_A", // 29
"AKEYCODE_B", // 30
"AKEYCODE_C", // 31
"AKEYCODE_D", // 32
"AKEYCODE_E", // 33
"AKEYCODE_F", // 34
"AKEYCODE_G", // 35
"AKEYCODE_H", // 36
"AKEYCODE_I", // 37
"AKEYCODE_J", // 38
"AKEYCODE_K", // 39
"AKEYCODE_L", // 40
"AKEYCODE_M", // 41
"AKEYCODE_N", // 42
"AKEYCODE_O", // 43
"AKEYCODE_P", // 44
"AKEYCODE_Q", // 45
"AKEYCODE_R", // 46
"AKEYCODE_S", // 47
"AKEYCODE_T", // 48
"AKEYCODE_U", // 49
"AKEYCODE_V", // 50
"AKEYCODE_W", // 51
"AKEYCODE_X", // 52
"AKEYCODE_Y", // 53
"AKEYCODE_Z", // 54
"AKEYCODE_COMMA", // 55
"AKEYCODE_PERIOD", // 56
"AKEYCODE_ALT_LEFT", // 57
"AKEYCODE_ALT_RIGHT", // 58
"AKEYCODE_SHIFT_LEFT", // 59
"AKEYCODE_SHIFT_RIGHT", // 60
"AKEYCODE_TAB", // 61
"AKEYCODE_SPACE", // 62
"AKEYCODE_SYM", // 63
"AKEYCODE_EXPLORER", // 64
"AKEYCODE_ENVELOPE", // 65
"AKEYCODE_ENTER", // 66
"AKEYCODE_DEL", // 67
"AKEYCODE_GRAVE", // 68
"AKEYCODE_MINUS", // 69
"AKEYCODE_EQUALS", // 70
"AKEYCODE_LEFT_BRACKET", // 71
"AKEYCODE_RIGHT_BRACKET", // 72
"AKEYCODE_BACKSLASH", // 73
"AKEYCODE_SEMICOLON", // 74
"AKEYCODE_APOSTROPHE", // 75
"AKEYCODE_SLASH", // 76
"AKEYCODE_AT", // 77
"AKEYCODE_NUM", // 78
"AKEYCODE_HEADSETHOOK", // 79
"AKEYCODE_FOCUS", // 80
"AKEYCODE_PLUS", // 81
"AKEYCODE_MENU", // 82
"AKEYCODE_NOTIFICATION", // 83
"AKEYCODE_SEARCH", // 84
"AKEYCODE_MEDIA_PLAY_PAUSE", // 85
"AKEYCODE_MEDIA_STOP", // 86
"AKEYCODE_MEDIA_NEXT", // 87
"AKEYCODE_MEDIA_PREVIOUS", // 88
"AKEYCODE_MEDIA_REWIND", // 89
"AKEYCODE_MEDIA_FAST_FORWARD", // 90
"AKEYCODE_MUTE", // 91
"AKEYCODE_PAGE_UP", // 92
"AKEYCODE_PAGE_DOWN", // 93
"AKEYCODE_PICTSYMBOLS", // 94
"AKEYCODE_SWITCH_CHARSET", // 95
"AKEYCODE_BUTTON_A", // 96
"AKEYCODE_BUTTON_B", // 97
"AKEYCODE_BUTTON_C", // 98
"AKEYCODE_BUTTON_X", // 99
"AKEYCODE_BUTTON_Y", // 100
"AKEYCODE_BUTTON_Z", // 101
"AKEYCODE_BUTTON_L1", // 102
"AKEYCODE_BUTTON_R1", // 103
"AKEYCODE_BUTTON_L2", // 104
"AKEYCODE_BUTTON_R2", // 105
"AKEYCODE_BUTTON_THUMBL", // 106
"AKEYCODE_BUTTON_THUMBR", // 107
"AKEYCODE_BUTTON_START", // 108
"AKEYCODE_BUTTON_SELECT", // 109
"AKEYCODE_BUTTON_MODE", // 110
"AKEYCODE_ESCAPE", // 111
"AKEYCODE_FORWARD_DEL", // 112
"AKEYCODE_CTRL_LEFT", // 113
"AKEYCODE_CTRL_RIGHT", // 114
"AKEYCODE_CAPS_LOCK", // 115
"AKEYCODE_SCROLL_LOCK", // 116
"AKEYCODE_META_LEFT", // 117
"AKEYCODE_META_RIGHT", // 118
"AKEYCODE_FUNCTION", // 119
"AKEYCODE_SYSRQ", // 120
"AKEYCODE_BREAK", // 121
"AKEYCODE_MOVE_HOME", // 122
"AKEYCODE_MOVE_END", // 123
"AKEYCODE_INSERT", // 124
"AKEYCODE_FORWARD", // 125
"AKEYCODE_MEDIA_PLAY", // 126
"AKEYCODE_MEDIA_PAUSE", // 127
"AKEYCODE_MEDIA_CLOSE", // 128
"AKEYCODE_MEDIA_EJECT", // 129
"AKEYCODE_MEDIA_RECORD", // 130
"AKEYCODE_F1", // 131
"AKEYCODE_F2", // 132
"AKEYCODE_F3", // 133
"AKEYCODE_F4", // 134
"AKEYCODE_F5", // 135
"AKEYCODE_F6", // 136
"AKEYCODE_F7", // 137
"AKEYCODE_F8", // 138
"AKEYCODE_F9", // 139
"AKEYCODE_F10", // 140
"AKEYCODE_F11", // 141
"AKEYCODE_F12", // 142
"AKEYCODE_NUM_LOCK", // 143
"AKEYCODE_NUMPAD_0", // 144
"AKEYCODE_NUMPAD_1", // 145
"AKEYCODE_NUMPAD_2", // 146
"AKEYCODE_NUMPAD_3", // 147
"AKEYCODE_NUMPAD_4", // 148
"AKEYCODE_NUMPAD_5", // 149
"AKEYCODE_NUMPAD_6", // 150
"AKEYCODE_NUMPAD_7", // 151
"AKEYCODE_NUMPAD_8", // 152
"AKEYCODE_NUMPAD_9", // 153
"AKEYCODE_NUMPAD_DIVIDE", // 154
"AKEYCODE_NUMPAD_MULTIPLY", // 155
"AKEYCODE_NUMPAD_SUBTRACT", // 156
"AKEYCODE_NUMPAD_ADD", // 157
"AKEYCODE_NUMPAD_DOT", // 158
"AKEYCODE_NUMPAD_COMMA", // 159
"AKEYCODE_NUMPAD_ENTER", // 160
"AKEYCODE_NUMPAD_EQUALS", // 161
"AKEYCODE_NUMPAD_LEFT_PAREN", // 162
"AKEYCODE_NUMPAD_RIGHT_PAREN", // 163
"AKEYCODE_VOLUME_MUTE", // 164
"AKEYCODE_INFO", // 165
"AKEYCODE_CHANNEL_UP", // 166
"AKEYCODE_CHANNEL_DOWN", // 167
"AKEYCODE_ZOOM_IN", // 168
"AKEYCODE_ZOOM_OUT", // 169
"AKEYCODE_TV", // 170
"AKEYCODE_WINDOW", // 171
"AKEYCODE_GUIDE", // 172
"AKEYCODE_DVR", // 173
"AKEYCODE_BOOKMARK", // 174
"AKEYCODE_CAPTIONS", // 175
"AKEYCODE_SETTINGS", // 176
"AKEYCODE_TV_POWER", // 177
"AKEYCODE_TV_INPUT", // 178
"AKEYCODE_STB_POWER", // 179
"AKEYCODE_STB_INPUT", // 180
"AKEYCODE_AVR_POWER", // 181
"AKEYCODE_AVR_INPUT", // 182
"AKEYCODE_PROG_RED", // 183
"AKEYCODE_PROG_GREEN", // 184
"AKEYCODE_PROG_YELLOW", // 185
"AKEYCODE_PROG_BLUE", // 186
"AKEYCODE_APP_SWITCH", // 187
"AKEYCODE_BUTTON_1", // 188
"AKEYCODE_BUTTON_2", // 189
"AKEYCODE_BUTTON_3", // 190
"AKEYCODE_BUTTON_4", // 191
"AKEYCODE_BUTTON_5", // 192
"AKEYCODE_BUTTON_6", // 193
"AKEYCODE_BUTTON_7", // 194
"AKEYCODE_BUTTON_8", // 195
"AKEYCODE_BUTTON_9", // 196
"AKEYCODE_BUTTON_10", // 197
"AKEYCODE_BUTTON_11", // 198
"AKEYCODE_BUTTON_12", // 199
"AKEYCODE_BUTTON_13", // 200
"AKEYCODE_BUTTON_14", // 201
"AKEYCODE_BUTTON_15", // 202
"AKEYCODE_BUTTON_16", // 203
"AKEYCODE_LANGUAGE_SWITCH", // 204
"AKEYCODE_MANNER_MODE", // 205
"AKEYCODE_3D_MODE", // 206
"AKEYCODE_CONTACTS", // 207
"AKEYCODE_CALENDAR", // 208
"AKEYCODE_MUSIC", // 209
"AKEYCODE_CALCULATOR", // 210
"AKEYCODE_ZENKAKU_HANKAKU", // 211
"AKEYCODE_EISU", // 212
"AKEYCODE_MUHENKAN", // 213
"AKEYCODE_HENKAN", // 214
"AKEYCODE_KATAKANA_HIRAGANA", // 215
"AKEYCODE_YEN", // 216
"AKEYCODE_RO", // 217
"AKEYCODE_KANA", // 218
"AKEYCODE_ASSIST", // 219
"AKEYCODE_BRIGHTNESS_DOWN", // 220
"AKEYCODE_BRIGHTNESS_UP", // 221
"AKEYCODE_MEDIA_AUDIO_TRACK", // 222
"AKEYCODE_SLEEP", // 223
"AKEYCODE_WAKEUP", // 224
"AKEYCODE_PAIRING", // 225
"AKEYCODE_MEDIA_TOP_MENU", // 226
"AKEYCODE_11", // 227
"AKEYCODE_12", // 228
"AKEYCODE_LAST_CHANNEL", // 229
"AKEYCODE_TV_DATA_SERVICE", // 230
"AKEYCODE_VOICE_ASSIST", // 231
"AKEYCODE_TV_RADIO_SERVICE", // 232
"AKEYCODE_TV_TELETEXT", // 233
"AKEYCODE_TV_NUMBER_ENTRY", // 234
"AKEYCODE_TV_TERRESTRIAL_ANALOG", // 235
"AKEYCODE_TV_TERRESTRIAL_DIGITAL", // 236
"AKEYCODE_TV_SATELLITE", // 237
"AKEYCODE_TV_SATELLITE_BS", // 238
"AKEYCODE_TV_SATELLITE_CS", // 239
"AKEYCODE_TV_SATELLITE_SERVICE", // 240
"AKEYCODE_TV_NETWORK", // 241
"AKEYCODE_TV_ANTENNA_CABLE", // 242
"AKEYCODE_TV_INPUT_HDMI_1", // 243
"AKEYCODE_TV_INPUT_HDMI_2", // 244
"AKEYCODE_TV_INPUT_HDMI_3", // 245
"AKEYCODE_TV_INPUT_HDMI_4", // 246
"AKEYCODE_TV_INPUT_COMPOSITE_1", // 247
"AKEYCODE_TV_INPUT_COMPOSITE_2", // 248
"AKEYCODE_TV_INPUT_COMPONENT_1", // 249
"AKEYCODE_TV_INPUT_COMPONENT_2", // 250
"AKEYCODE_TV_INPUT_VGA_1", // 251
"AKEYCODE_TV_AUDIO_DESCRIPTION", // 252
"AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP", // 253
"AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN", // 254
"AKEYCODE_TV_ZOOM_MODE", // 255
"AKEYCODE_TV_CONTENTS_MENU", // 256
"AKEYCODE_TV_MEDIA_CONTEXT_MENU", // 257
"AKEYCODE_TV_TIMER_PROGRAMMING", // 258
"AKEYCODE_HELP", // 259
"AKEYCODE_NAVIGATE_PREVIOUS", // 260
"AKEYCODE_NAVIGATE_NEXT", // 261
"AKEYCODE_NAVIGATE_IN", // 262
"AKEYCODE_NAVIGATE_OUT", // 263
"AKEYCODE_STEM_PRIMARY", // 264
"AKEYCODE_STEM_1", // 265
"AKEYCODE_STEM_2", // 266
"AKEYCODE_STEM_3", // 267
"AKEYCODE_DPAD_UP_LEFT", // 268
"AKEYCODE_DPAD_DOWN_LEFT", // 269
"AKEYCODE_DPAD_UP_RIGHT", // 270
"AKEYCODE_DPAD_DOWN_RIGHT", // 271
"AKEYCODE_MEDIA_SKIP_FORWARD", // 272
"AKEYCODE_MEDIA_SKIP_BACKWARD", // 273
"AKEYCODE_MEDIA_STEP_FORWARD", // 274
"AKEYCODE_MEDIA_STEP_BACKWARD", // 275
"AKEYCODE_SOFT_SLEEP", // 276
"AKEYCODE_CUT", // 277
"AKEYCODE_COPY", // 278
"AKEYCODE_PASTE", // 279
"AKEYCODE_SYSTEM_NAVIGATION_UP", // 280
"AKEYCODE_SYSTEM_NAVIGATION_DOWN", // 281
"AKEYCODE_SYSTEM_NAVIGATION_LEFT", // 282
"AKEYCODE_SYSTEM_NAVIGATION_RIGHT", // 283
"AKEYCODE_ALL_APPS", // 284
"AKEYCODE_REFRESH", // 285
"AKEYCODE_THUMBS_UP", // 286
"AKEYCODE_THUMBS_DOWN", // 287
"AKEYCODE_PROFILE_SWITCH", // 288
};
const char *AMOTION_AXIS_STRINGS[48] = {
"AMOTION_EVENT_AXIS_X", // 0
"AMOTION_EVENT_AXIS_Y", // 1
"AMOTION_EVENT_AXIS_PRESSURE", // 2
"AMOTION_EVENT_AXIS_SIZE", // 3
"AMOTION_EVENT_AXIS_TOUCH_MAJOR", // 4
"AMOTION_EVENT_AXIS_TOUCH_MINOR", // 5
"AMOTION_EVENT_AXIS_TOOL_MAJOR", // 6
"AMOTION_EVENT_AXIS_TOOL_MINOR", // 7
"AMOTION_EVENT_AXIS_ORIENTATION", // 8
"AMOTION_EVENT_AXIS_VSCROLL", // 9
"AMOTION_EVENT_AXIS_HSCROLL", // 10
"AMOTION_EVENT_AXIS_Z", // 11
"AMOTION_EVENT_AXIS_RX", // 12
"AMOTION_EVENT_AXIS_RY", // 13
"AMOTION_EVENT_AXIS_RZ", // 14
"AMOTION_EVENT_AXIS_HAT_X", // 15
"AMOTION_EVENT_AXIS_HAT_Y", // 16
"AMOTION_EVENT_AXIS_LTRIGGER", // 17
"AMOTION_EVENT_AXIS_RTRIGGER", // 18
"AMOTION_EVENT_AXIS_THROTTLE", // 19
"AMOTION_EVENT_AXIS_RUDDER", // 20
"AMOTION_EVENT_AXIS_WHEEL", // 21
"AMOTION_EVENT_AXIS_GAS", // 22
"AMOTION_EVENT_AXIS_BRAKE", // 23
"AMOTION_EVENT_AXIS_DISTANCE", // 24
"AMOTION_EVENT_AXIS_TILT", // 25
"AMOTION_EVENT_AXIS_SCROLL", // 26
"AMOTION_EVENT_AXIS_RELATIVE_X", // 27
"AMOTION_EVENT_AXIS_RELATIVE_Y", // 28
"AMOTION_EVENT_UNDEFINED", // 29
"AMOTION_EVENT_UNDEFINED", // 30
"AMOTION_EVENT_UNDEFINED", // 31
"AMOTION_EVENT_AXIS_GENERIC_1", // 32
"AMOTION_EVENT_AXIS_GENERIC_2", // 33
"AMOTION_EVENT_AXIS_GENERIC_3", // 34
"AMOTION_EVENT_AXIS_GENERIC_4", // 35
"AMOTION_EVENT_AXIS_GENERIC_5", // 36
"AMOTION_EVENT_AXIS_GENERIC_6", // 37
"AMOTION_EVENT_AXIS_GENERIC_7", // 38
"AMOTION_EVENT_AXIS_GENERIC_8", // 39
"AMOTION_EVENT_AXIS_GENERIC_9", // 40
"AMOTION_EVENT_AXIS_GENERIC_10", // 41
"AMOTION_EVENT_AXIS_GENERIC_11", // 42
"AMOTION_EVENT_AXIS_GENERIC_12", // 43
"AMOTION_EVENT_AXIS_GENERIC_13", // 44
"AMOTION_EVENT_AXIS_GENERIC_14", // 45
"AMOTION_EVENT_AXIS_GENERIC_15", // 46
"AMOTION_EVENT_AXIS_GENERIC_16" // 47
};
const char *AMOTION_ACTION_STRINGS[13] = {
"AMOTION_EVENT_ACTION_DOWN", // 0
"AMOTION_EVENT_ACTION_UP", // 1,
"AMOTION_EVENT_ACTION_MOVE", // 2
"AMOTION_EVENT_ACTION_CANCEL", // 3
"AMOTION_EVENT_ACTION_OUTSIDE", // 4
"AMOTION_EVENT_ACTION_POINTER_DOWN", // 5
"AMOTION_EVENT_ACTION_POINTER_UP", // 6
"AMOTION_EVENT_ACTION_HOVER_MOVE", // 7
"AMOTION_EVENT_ACTION_SCROLL", // 8
"AMOTION_EVENT_ACTION_HOVER_ENTER", // 9
"AMOTION_EVENT_ACTION_HOVER_EXIT", // 10
"AMOTION_EVENT_ACTION_BUTTON_PRESS", // 11
"AMOTION_EVENT_ACTION_BUTTON_RELEASE" // 12 _
};
} // namespace paddleboat

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,223 @@
/*
* Copyright (C) 2021 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
*
* https://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.
*/
#pragma once
#include <android/input.h>
#include <jni.h>
#include <mutex>
#include "GameController.h"
#include "ThreadUtil.h"
namespace paddleboat {
class GameControllerManager {
private:
// Allows construction with std::unique_ptr from a static method, but
// disallows construction outside of the class since no one else can
// construct a ConstructorTag
struct ConstructorTag {};
static constexpr int32_t MAX_MOUSE_DEVICES = 2;
static constexpr int32_t INVALID_MOUSE_ID = -1;
static constexpr int32_t MAX_REMAP_TABLE_SIZE = 256;
// Assuming update is getting called at 60Hz, wait one minute in between
// checking battery status
static constexpr int32_t BATTERY_REFRESH_WAIT = 60 * 60;
public:
GameControllerManager(JNIEnv *env, jobject jcontext, ConstructorTag);
~GameControllerManager();
static inline int32_t getRemapTableSize() { return MAX_REMAP_TABLE_SIZE; }
static Paddleboat_ErrorCode init(JNIEnv *env, jobject jcontext);
static void destroyInstance(JNIEnv *env);
static bool isInitialized();
// Get/Set whether AKEYCODE_BACK is 'eaten' or allowed to pass through to
// the system This can be used to block the OS backing out of the game, or
// allowing it if the game is in an appropriate state (i.e. the title
// screen)
static bool getBackButtonConsumed();
static void setBackButtonConsumed(bool consumed);
static Paddleboat_ErrorCode setControllerLight(
const int32_t controllerIndex, const Paddleboat_LightType lightType,
const uint32_t lightData, JNIEnv *env);
static void setControllerStatusCallback(
Paddleboat_ControllerStatusCallback statusCallback, void *userData);
static void setMotionDataCallback(
Paddleboat_MotionDataCallback motionDataCallback, void *userData);
static void setMouseStatusCallback(
Paddleboat_MouseStatusCallback statusCallback, void *userData);
static void onStop(JNIEnv *env);
static void onStart(JNIEnv *env);
static void update(JNIEnv *env);
static Paddleboat_ErrorCode getControllerData(
const int32_t controllerIndex,
Paddleboat_Controller_Data *controllerData);
static Paddleboat_ErrorCode getControllerInfo(
const int32_t controllerIndex, Paddleboat_Controller_Info *deviceInfo);
static Paddleboat_ErrorCode getControllerName(const int32_t controllerIndex,
const size_t bufferSize,
char *controllerName);
static Paddleboat_ControllerStatus getControllerStatus(
const int32_t controllerIndex);
static Paddleboat_ErrorCode setControllerVibrationData(
const int32_t controllerIndex,
const Paddleboat_Vibration_Data *vibrationData, JNIEnv *env);
static Paddleboat_ErrorCode getMouseData(Paddleboat_Mouse_Data *mouseData);
static Paddleboat_MouseStatus getMouseStatus();
static int32_t processInputEvent(const AInputEvent *event);
static int32_t processGameActivityKeyInputEvent(const void *event,
const size_t eventSize);
static int32_t processGameActivityMotionInputEvent(const void *event,
const size_t eventSize);
static uint64_t getActiveAxisMask();
static void addControllerRemapData(
const Paddleboat_Remap_Addition_Mode addMode,
const int32_t remapTableEntryCount,
const Paddleboat_Controller_Mapping_Data *mappingData);
static int32_t getControllerRemapTableData(
const int32_t destRemapTableEntryCount,
Paddleboat_Controller_Mapping_Data *mappingData);
// Called from the JNI bridge functions
static GameControllerDeviceInfo *onConnection();
static void onDisconnection(const int32_t deviceId);
static void onMotionData(const int32_t deviceId, const int32_t motionType,
const uint64_t timestamp, const float dataX,
const float dataY, const float dataZ);
static void onMouseConnection(const int32_t deviceId);
static void onMouseDisconnection(const int32_t deviceId);
static jclass getGameControllerClass();
static jobject getGameControllerObject();
// device debug helper function
static int32_t getLastKeycode();
private:
static GameControllerManager *getInstance();
Paddleboat_ErrorCode initMethods(JNIEnv *env);
bool isLightTypeSupported(const Paddleboat_Controller_Info &controllerInfo,
const Paddleboat_LightType lightType);
int32_t processControllerKeyEvent(const AInputEvent *event,
GameController &gameController);
int32_t processControllerGameActivityKeyEvent(
const Paddleboat_GameActivityKeyEvent *event, const size_t eventSize,
GameController &gameController);
int32_t processMouseEvent(const AInputEvent *event);
int32_t processGameActivityMouseEvent(const void *event,
const size_t eventSize,
const int32_t eventDeviceId);
void rescanVirtualMouseControllers();
void updateBattery(JNIEnv *env);
void updateMouseDataTimestamp();
void releaseGlobals(JNIEnv *env);
const Paddleboat_Controller_Mapping_Data *getMapForController(
const GameController &gameController);
bool mInitialized = false;
bool mGCMClassInitialized = false;
bool mBackButtonConsumed = true;
bool mMotionEventReporting = false;
int32_t mApiLevel = 16;
int32_t mBatteryWait = BATTERY_REFRESH_WAIT;
jobject mContext = NULL;
jclass mGameControllerClass = NULL;
jobject mGameControllerObject = NULL;
jmethodID mInitMethodId = NULL;
jmethodID mGetApiLevelMethodId = NULL;
jmethodID mGetBatteryLevelMethodId = NULL;
jmethodID mGetBatteryStatusMethodId = NULL;
jmethodID mSetLightMethodId = NULL;
jmethodID mSetNativeReadyMethodId = NULL;
jmethodID mSetReportMotionEventsMethodId = NULL;
jmethodID mSetVibrationMethodId = NULL;
uint64_t mActiveAxisMask = 0;
int32_t mRemapEntryCount = 0;
Paddleboat_Controller_Mapping_Data mMappingTable[MAX_REMAP_TABLE_SIZE];
Paddleboat_MotionDataCallback mMotionDataCallback = nullptr;
void *mMotionDataCallbackUserData = nullptr;
GameController mGameControllers[PADDLEBOAT_MAX_CONTROLLERS];
Paddleboat_ControllerStatusCallback mStatusCallback = nullptr;
void *mStatusCallbackUserData = nullptr;
// device debug helper
int32_t mLastKeyEventKeyCode = 0;
Paddleboat_MouseStatus mMouseStatus = PADDLEBOAT_MOUSE_NONE;
int32_t mMouseDeviceIds[MAX_MOUSE_DEVICES] = {INVALID_MOUSE_ID,
INVALID_MOUSE_ID};
int32_t mMouseControllerIndex = INVALID_MOUSE_ID;
Paddleboat_Mouse_Data mMouseData;
Paddleboat_MouseStatusCallback mMouseCallback = nullptr;
void *mMouseCallbackUserData = nullptr;
std::mutex mUpdateMutex;
static std::mutex sInstanceMutex;
static std::unique_ptr<GameControllerManager> sInstance
GUARDED_BY(sInstanceMutex);
};
} // namespace paddleboat

View File

@ -0,0 +1,179 @@
/*
* Copyright (C) 2021 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
*
* https://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 "GameControllerMappingUtils.h"
#include "GameControllerManager.h"
namespace paddleboat {
MappingTableSearch::MappingTableSearch()
: mappingRoot(nullptr),
vendorId(0),
productId(0),
minApi(0),
maxApi(0),
tableIndex(0),
mapEntryCount(0),
tableEntryCount(0),
tableMaxEntryCount(GameControllerManager::getRemapTableSize()) {}
MappingTableSearch::MappingTableSearch(
Paddleboat_Controller_Mapping_Data *mapRoot, int32_t entryCount)
: mappingRoot(mapRoot),
vendorId(0),
productId(0),
minApi(0),
maxApi(0),
tableIndex(0),
mapEntryCount(0),
tableEntryCount(entryCount),
tableMaxEntryCount(GameControllerManager::getRemapTableSize()) {}
void MappingTableSearch::initSearchParameters(const int32_t newVendorId,
const int32_t newProductId,
const int32_t newMinApi,
const int32_t newMaxApi) {
vendorId = newVendorId;
productId = newProductId;
minApi = newMinApi;
maxApi = newMaxApi;
tableIndex = 0;
}
bool GameControllerMappingUtils::findMatchingMapEntry(
MappingTableSearch *searchEntry) {
int32_t currentIndex = 0;
// Starting out with a linear search. Updating the map table is something
// that should only ever be done once at startup, if it actually takes an
// appreciable time to execute when working with a big remap dictionary,
// this is low-hanging fruit to optimize.
const Paddleboat_Controller_Mapping_Data *mapRoot =
searchEntry->mappingRoot;
while (currentIndex < searchEntry->tableEntryCount) {
const Paddleboat_Controller_Mapping_Data &mapEntry =
mapRoot[currentIndex];
if (mapEntry.vendorId > searchEntry->vendorId) {
// Passed by the search vendorId value, so we don't already exist in
// the table, set the current index as the insert point and bail
searchEntry->tableIndex = currentIndex;
return false;
} else if (searchEntry->vendorId == mapEntry.vendorId) {
if (mapEntry.productId > searchEntry->productId) {
// Passed by the search productId value, so we don't already
// exist in the table, set the current index as the insert point
// and bail
searchEntry->tableIndex = currentIndex;
return false;
} else if (searchEntry->productId == mapEntry.productId) {
// Any overlap of the min/max API range is treated as matching
// an existing entry
if ((searchEntry->minApi >= mapEntry.minimumEffectiveApiLevel &&
searchEntry->minApi <=
mapEntry.maximumEffectiveApiLevel) ||
(searchEntry->minApi >= mapEntry.minimumEffectiveApiLevel &&
mapEntry.maximumEffectiveApiLevel == 0)) {
searchEntry->tableIndex = currentIndex;
return true;
}
}
}
++currentIndex;
}
searchEntry->tableIndex = currentIndex;
return false;
}
bool GameControllerMappingUtils::insertMapEntry(
const Paddleboat_Controller_Mapping_Data *mappingData,
MappingTableSearch *searchEntry) {
bool insertSuccess = false;
// Verify there is room in the table for another entry
if (searchEntry->tableEntryCount < searchEntry->tableMaxEntryCount &&
searchEntry->tableIndex < searchEntry->tableMaxEntryCount) {
// Empty table, or inserting at the end, no relocation of existing data
// required, otherwise shift existing data down starting at the insert
// index.
if (!(searchEntry->tableEntryCount == 0 ||
searchEntry->tableIndex == searchEntry->tableEntryCount)) {
const size_t copySize =
(searchEntry->tableEntryCount - searchEntry->tableIndex) *
sizeof(Paddleboat_Controller_Mapping_Data);
memmove(&searchEntry->mappingRoot[searchEntry->tableIndex + 1],
&searchEntry->mappingRoot[searchEntry->tableIndex],
copySize);
}
// Insert the new data
memcpy(&searchEntry->mappingRoot[searchEntry->tableIndex], mappingData,
sizeof(Paddleboat_Controller_Mapping_Data));
insertSuccess = true;
}
return insertSuccess;
}
const Paddleboat_Controller_Mapping_Data *
GameControllerMappingUtils::validateMapTable(
const Paddleboat_Controller_Mapping_Data *mappingRoot,
const int32_t tableEntryCount) {
// The map table is always assumed to be sorted by increasing vendorId. Each
// sequence of entries with the same vendorId are sorted by increasing
// productId. Multiple entries with the same vendorId/productId range are
// sorted by increasing min/max API ranges. vendorId
// productId
// minApi
int32_t currentIndex = 0;
int32_t previousVendorId = -1;
while (currentIndex < tableEntryCount) {
if (mappingRoot[currentIndex].vendorId < previousVendorId) {
// failure in vendorId order, return the offending entry
return &mappingRoot[currentIndex];
}
int32_t previousProductId = mappingRoot[currentIndex].productId;
int32_t previousMinApi =
mappingRoot[currentIndex].minimumEffectiveApiLevel;
int32_t previousMaxApi =
mappingRoot[currentIndex].maximumEffectiveApiLevel;
previousVendorId = mappingRoot[currentIndex++].vendorId;
while (currentIndex < tableEntryCount &&
mappingRoot[currentIndex].vendorId == previousVendorId) {
while (currentIndex < tableEntryCount &&
mappingRoot[currentIndex].productId == previousProductId) {
if (mappingRoot[currentIndex].minimumEffectiveApiLevel <
previousMinApi ||
mappingRoot[currentIndex].minimumEffectiveApiLevel <
previousMaxApi) {
// failure in API order, return the offending entry
return &mappingRoot[currentIndex];
}
previousMinApi =
mappingRoot[currentIndex].minimumEffectiveApiLevel;
previousMaxApi =
mappingRoot[currentIndex++].maximumEffectiveApiLevel;
}
if (mappingRoot[currentIndex].productId < previousProductId) {
// failure in productId order, return the offending entry
return &mappingRoot[currentIndex];
}
previousProductId = mappingRoot[currentIndex++].productId;
}
}
// Validation success, return nullptr (no offending entries to return)
return nullptr;
}
} // namespace paddleboat

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2021 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
*
* https://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.
*/
#pragma once
#include <cstdint>
#ifndef PADDLEBOAT_H
#include "paddleboat.h"
#endif
namespace paddleboat {
class MappingTableSearch {
public:
MappingTableSearch();
MappingTableSearch(Paddleboat_Controller_Mapping_Data *mapRoot,
int32_t entryCount);
void initSearchParameters(const int32_t newVendorId,
const int32_t newProductId,
const int32_t newMinApi, const int32_t newMaxApi);
Paddleboat_Controller_Mapping_Data *mappingRoot;
int32_t vendorId;
int32_t productId;
int32_t minApi;
int32_t maxApi;
int32_t tableIndex;
int32_t mapEntryCount;
int32_t tableEntryCount;
int32_t tableMaxEntryCount;
};
class GameControllerMappingUtils {
public:
static bool findMatchingMapEntry(MappingTableSearch *searchEntry);
static bool insertMapEntry(
const Paddleboat_Controller_Mapping_Data *mappingData,
MappingTableSearch *searchEntry);
static const Paddleboat_Controller_Mapping_Data *validateMapTable(
const Paddleboat_Controller_Mapping_Data *mappingRoot,
const int32_t tableEntryCount);
};
} // namespace paddleboat

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2021 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
*
* https://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.
*/
#pragma once
#include <cstdint>
#include "paddleboat.h"
namespace paddleboat {
const Paddleboat_Controller_Mapping_Data *GetInternalControllerData();
int32_t GetInternalControllerDataCount();
} // namespace paddleboat

View File

@ -0,0 +1,40 @@
/*
* Copyright 2018 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.
*/
#pragma once
#include <android/log.h>
#include <string>
#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 ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGW_ONCE_IF(cond, ...) \
do { \
static bool alogw_once##__FILE__##__LINE__##__ = true; \
if (cond && alogw_once##__FILE__##__LINE__##__) { \
alogw_once##__FILE__##__LINE__##__ = false; \
ALOGW(__VA_ARGS__); \
} \
} while (0)
#ifndef NDEBUG
#define ALOGV(...) \
__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#else
#define ALOGV(...)
#endif

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2021 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.
*/
#pragma once
#include <cstdint>
#include <functional>
#include <memory>
// Enable thread safety attributes only with clang.
// The attributes can be safely erased when compiling with other compilers.
#if defined(__clang__) && (!defined(SWIG))
#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
#else
#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
#endif
#if !defined GAMESDK_THREAD_CHECKS
#define GAMESDK_THREAD_CHECKS 1
#endif
#if GAMESDK_THREAD_CHECKS
#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
#define REQUIRES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
#define NO_THREAD_SAFETY_ANALYSIS \
THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
#else
#define GUARDED_BY(x)
#define REQUIRES(...)
#define NO_THREAD_SAFETY_ANALYSIS
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,182 @@
/*
* Copyright (C) 2021 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
*
* https://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 "GameControllerManager.h"
#include "paddleboat.h"
using namespace paddleboat;
extern "C" {
// Internal macros to track Paddleboat version, do not use directly.
#define PADDLEBOAT_MAJOR_VERSION 1
#define PADDLEBOAT_MINOR_VERSION 1
#define PADDLEBOAT_BUGFIX_VERSION 0
#define PADDLEBOAT_PACKED_VERSION \
((PADDLEBOAT_MAJOR_VERSION << 24) | (PADDLEBOAT_MINOR_VERSION << 16) | \
(PADDLEBOAT_BUGFIX_VERSION))
#define PADDLEBOAT_VERSION_CONCAT_NX(PREFIX, MAJOR, MINOR, BUGFIX) \
PREFIX##_##MAJOR##_##MINOR##_##BUGFIX
#define PADDLEBOAT_VERSION_CONCAT(PREFIX, MAJOR, MINOR, BUGFIX) \
PADDLEBOAT_VERSION_CONCAT_NX(PREFIX, MAJOR, MINOR, BUGFIX)
#define PADDLEBOAT_VERSION_SYMBOL \
PADDLEBOAT_VERSION_CONCAT(PADDLEBOAT_version, PADDLEBOAT_MAJOR_VERSION, \
PADDLEBOAT_MINOR_VERSION, \
PADDLEBOAT_BUGFIX_VERSION)
void PADDLEBOAT_VERSION_SYMBOL();
Paddleboat_ErrorCode Paddleboat_init(JNIEnv *env, jobject jcontext) {
PADDLEBOAT_VERSION_SYMBOL();
Paddleboat_ErrorCode errorCode = GameControllerManager::init(env, jcontext);
if (errorCode == PADDLEBOAT_NO_ERROR) {
GameControllerManager::update(env);
}
return errorCode;
}
bool Paddleboat_isInitialized() {
return GameControllerManager::isInitialized();
}
void Paddleboat_destroy(JNIEnv *env) {
GameControllerManager::destroyInstance(env);
}
void Paddleboat_onStop(JNIEnv *env) { GameControllerManager::onStop(env); }
void Paddleboat_onStart(JNIEnv *env) { GameControllerManager::onStart(env); }
int32_t Paddleboat_processInputEvent(const AInputEvent *event) {
return GameControllerManager::processInputEvent(event);
}
int32_t Paddleboat_processGameActivityKeyInputEvent(const void *event,
const size_t eventSize) {
return GameControllerManager::processGameActivityKeyInputEvent(event,
eventSize);
}
int32_t Paddleboat_processGameActivityMotionInputEvent(const void *event,
const size_t eventSize) {
return GameControllerManager::processGameActivityMotionInputEvent(
event, eventSize);
}
uint64_t Paddleboat_getActiveAxisMask() {
return GameControllerManager::getActiveAxisMask();
}
bool Paddleboat_getBackButtonConsumed() {
return GameControllerManager::getBackButtonConsumed();
}
void Paddleboat_setBackButtonConsumed(bool consumeBackButton) {
GameControllerManager::setBackButtonConsumed(consumeBackButton);
}
void Paddleboat_setControllerStatusCallback(
Paddleboat_ControllerStatusCallback statusCallback, void *userData) {
GameControllerManager::setControllerStatusCallback(statusCallback,
userData);
}
void Paddleboat_setMotionDataCallback(
Paddleboat_MotionDataCallback motionDataCallback, void *userData) {
GameControllerManager::setMotionDataCallback(motionDataCallback, userData);
}
void Paddleboat_setMouseStatusCallback(
Paddleboat_MouseStatusCallback statusCallback, void *userData) {
GameControllerManager::setMouseStatusCallback(statusCallback, userData);
}
Paddleboat_ErrorCode Paddleboat_getControllerData(
const int32_t controllerIndex, Paddleboat_Controller_Data *controllerData) {
return GameControllerManager::getControllerData(controllerIndex,
controllerData);
}
Paddleboat_ErrorCode Paddleboat_getControllerInfo(
const int32_t controllerIndex, Paddleboat_Controller_Info *controllerInfo) {
return GameControllerManager::getControllerInfo(controllerIndex,
controllerInfo);
}
Paddleboat_ErrorCode Paddleboat_getControllerName(const int32_t controllerIndex,
const size_t bufferSize,
char *controllerName) {
return GameControllerManager::getControllerName(controllerIndex, bufferSize,
controllerName);
}
Paddleboat_ControllerStatus Paddleboat_getControllerStatus(
const int32_t controllerIndex) {
return GameControllerManager::getControllerStatus(controllerIndex);
}
Paddleboat_ErrorCode Paddleboat_setControllerLight(
const int32_t controllerIndex, const Paddleboat_LightType lightType,
const uint32_t lightData, JNIEnv *env) {
return GameControllerManager::setControllerLight(controllerIndex, lightType,
lightData, env);
}
Paddleboat_ErrorCode Paddleboat_setControllerVibrationData(
const int32_t controllerIndex,
const Paddleboat_Vibration_Data *vibrationData, JNIEnv *env) {
return GameControllerManager::setControllerVibrationData(
controllerIndex, vibrationData, env);
}
Paddleboat_ErrorCode Paddleboat_getMouseData(Paddleboat_Mouse_Data *mouseData) {
return GameControllerManager::getMouseData(mouseData);
}
Paddleboat_MouseStatus Paddleboat_getMouseStatus() {
return GameControllerManager::getMouseStatus();
}
void Paddleboat_addControllerRemapData(
const Paddleboat_Remap_Addition_Mode addMode,
const int32_t remapTableEntryCount,
const Paddleboat_Controller_Mapping_Data *mappingData) {
GameControllerManager::addControllerRemapData(addMode, remapTableEntryCount,
mappingData);
}
int32_t Paddleboat_getControllerRemapTableData(
const int32_t destRemapTableEntryCount,
Paddleboat_Controller_Mapping_Data *mappingData) {
return GameControllerManager::getControllerRemapTableData(
destRemapTableEntryCount, mappingData);
}
void Paddleboat_update(JNIEnv *env) { GameControllerManager::update(env); }
int32_t Paddleboat_getLastKeycode() {
return GameControllerManager::getLastKeycode();
}
void PADDLEBOAT_VERSION_SYMBOL() {
// Intentionally empty: this function is used to ensure that the proper
// version of the library is linked against the proper headers.
// In case of mismatch, a linker error will be triggered because of an
// undefined symbol, as the name of the function depends on the version.
}
} // extern "C" {