mirror of
https://github.com/rust-mobile/android-activity.git
synced 2026-07-12 01:28:54 +00:00
android-game-sdk-rs: bump v2.0.2 -> v4.0.0
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
committed by
Robert Bragg
parent
49f2b86424
commit
85eb7274f4
@@ -10,6 +10,7 @@ This is a basic checklist for things that need to be done before updating to a n
|
||||
|
||||
- [ ] Ensure all patches applied to the local repo have been backported (see section below on backporting patches)
|
||||
- [ ] Rebase patches on top of a new *release* version of agdk. If you're not sure which version you should be rebasing on, open an issue.
|
||||
- [ ] If there have been substantial path changes, remove the existing files first so that its a clean graft
|
||||
- [ ] Copy any new files over from the new version using the [copy_files](./copy_files) script (see section below on copying files)
|
||||
- [ ] Update [build.rs](../build.rs) with any new includes and src files
|
||||
- [ ] Regenerate ffi bindings using [generate_bindings.sh](./generate_bindings.sh)
|
||||
|
||||
@@ -35,23 +35,27 @@ mkdir -p "$DEST_DIR"
|
||||
while IFS= read -r file || [ -n "$file" ]; do
|
||||
# Skip empty lines and comments
|
||||
if [ -z "$file" ] || [[ "$file" =~ ^# ]]; then
|
||||
continue
|
||||
continue
|
||||
fi
|
||||
|
||||
fpath="$(find "$SOURCE_DIR" -name "$file" -type f)"
|
||||
|
||||
# Find matching files
|
||||
find "$SOURCE_DIR" -name "$file" -type f | while read -r file; do
|
||||
# Get the relative path
|
||||
rel_path="${file#$SOURCE_DIR/}"
|
||||
dest_file="$DEST_DIR/$rel_path"
|
||||
dest_dir=$(dirname "$dest_file")
|
||||
if [ -f "$fpath" ]; then
|
||||
# Get the relative path
|
||||
rel_path="${fpath#$SOURCE_DIR/}"
|
||||
dest_file="$DEST_DIR/$rel_path"
|
||||
dest_dir=$(dirname "$dest_file")
|
||||
|
||||
# Create destination directory structure
|
||||
mkdir -p "$dest_dir"
|
||||
# Create destination directory structure
|
||||
mkdir -p "$dest_dir"
|
||||
|
||||
# Copy the file
|
||||
cp "$file" "$dest_file"
|
||||
echo "Copied: $rel_path"
|
||||
done
|
||||
# Copy the file
|
||||
cp "$fpath" "$dest_file"
|
||||
echo "Copied: $rel_path"
|
||||
else
|
||||
echo "ERROR: '$file' not found"
|
||||
fi
|
||||
done < "$FILE_LIST"
|
||||
|
||||
echo "Copy operation completed."
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
GameActivity.h
|
||||
android_native_app_glue.c
|
||||
android_native_app_glue.h
|
||||
GameActivity.cpp
|
||||
GameActivityEvents.h
|
||||
GameActivityEvents.cpp
|
||||
GameActivityEvents.h
|
||||
GameActivityEvents_internal.h
|
||||
GameActivity.h
|
||||
GameActivityLog.h
|
||||
gamesdk_common.h
|
||||
gametextinput.cpp
|
||||
gametextinput.h
|
||||
gamecommon.h
|
||||
gamesdk_common.h
|
||||
android_native_app_glue.h
|
||||
android_native_app_glue.c
|
||||
system_utils.cpp
|
||||
system_utils.h
|
||||
|
||||
-1147
File diff suppressed because it is too large
Load Diff
+465
-390
@@ -31,26 +31,43 @@
|
||||
#include <android/input.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/rect.h>
|
||||
#include <common/gamesdk_common.h>
|
||||
#include <game-activity/GameActivityEvents.h>
|
||||
#include <game-text-input/gametextinput.h>
|
||||
#include <jni.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "common/gamesdk_common.h"
|
||||
#include "game-activity/GameActivityEvents.h"
|
||||
#include "game-text-input/gametextinput.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define GAMEACTIVITY_MAJOR_VERSION 2
|
||||
#define GAMEACTIVITY_MAJOR_VERSION 4
|
||||
#define GAMEACTIVITY_MINOR_VERSION 0
|
||||
#define GAMEACTIVITY_BUGFIX_VERSION 2
|
||||
#define GAMEACTIVITY_PACKED_VERSION \
|
||||
ANDROID_GAMESDK_PACKED_VERSION(GAMEACTIVITY_MAJOR_VERSION, \
|
||||
GAMEACTIVITY_MINOR_VERSION, \
|
||||
GAMEACTIVITY_BUGFIX_VERSION)
|
||||
#define GAMEACTIVITY_BUGFIX_VERSION 0
|
||||
#define GAMEACTIVITY_PACKED_VERSION \
|
||||
ANDROID_GAMESDK_PACKED_VERSION(GAMEACTIVITY_MAJOR_VERSION, \
|
||||
GAMEACTIVITY_MINOR_VERSION, \
|
||||
GAMEACTIVITY_BUGFIX_VERSION)
|
||||
|
||||
/**
|
||||
* The type of a component for which to retrieve insets. See
|
||||
* https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type
|
||||
*/
|
||||
typedef enum GameCommonInsetsType : uint8_t {
|
||||
GAMECOMMON_INSETS_TYPE_CAPTION_BAR = 0,
|
||||
GAMECOMMON_INSETS_TYPE_DISPLAY_CUTOUT,
|
||||
GAMECOMMON_INSETS_TYPE_IME,
|
||||
GAMECOMMON_INSETS_TYPE_MANDATORY_SYSTEM_GESTURES,
|
||||
GAMECOMMON_INSETS_TYPE_NAVIGATION_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_STATUS_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_SYSTEM_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_SYSTEM_GESTURES,
|
||||
GAMECOMMON_INSETS_TYPE_TAPABLE_ELEMENT,
|
||||
GAMECOMMON_INSETS_TYPE_WATERFALL,
|
||||
GAMECOMMON_INSETS_TYPE_COUNT
|
||||
} GameCommonInsetsType;
|
||||
|
||||
/**
|
||||
* {@link GameActivityCallbacks}
|
||||
@@ -63,66 +80,66 @@ struct GameActivityCallbacks;
|
||||
* code as it is being launched.
|
||||
*/
|
||||
typedef struct GameActivity {
|
||||
/**
|
||||
* Pointer to the callback function table of the native application.
|
||||
* You can set the functions here to your own callbacks. The callbacks
|
||||
* pointer itself here should not be changed; it is allocated and managed
|
||||
* for you by the framework.
|
||||
*/
|
||||
struct GameActivityCallbacks* callbacks;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* The GameActivity object handle.
|
||||
*/
|
||||
jobject javaGameActivity;
|
||||
|
||||
/**
|
||||
* Path to this application's internal data directory.
|
||||
*/
|
||||
const char* internalDataPath;
|
||||
/**
|
||||
* Path to this application's internal data directory.
|
||||
*/
|
||||
const char* internalDataPath;
|
||||
|
||||
/**
|
||||
* Path to this application's external (removable/mountable) data directory.
|
||||
*/
|
||||
const char* externalDataPath;
|
||||
/**
|
||||
* Path to this application's external (removable/mountable) data directory.
|
||||
*/
|
||||
const char* externalDataPath;
|
||||
|
||||
/**
|
||||
* The platform's SDK version code.
|
||||
*/
|
||||
int32_t sdkVersion;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* Available starting with Honeycomb: path to the directory containing
|
||||
* the application's OBB files (if any). If the app doesn't have any
|
||||
* OBB files, this directory may not exist.
|
||||
*/
|
||||
const char* obbPath;
|
||||
} GameActivity;
|
||||
|
||||
/**
|
||||
@@ -139,144 +156,153 @@ typedef void (*SaveInstanceStateRecallback)(const char* bytes, int len,
|
||||
* to have it called.
|
||||
*/
|
||||
typedef struct GameActivityCallbacks {
|
||||
/**
|
||||
* GameActivity has started. See Java documentation for Activity.onStart()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onStart)(GameActivity* activity);
|
||||
/**
|
||||
* GameActivity has 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);
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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 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 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);
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* The drawing window for this native activity has been created. You
|
||||
* can use the given native window object to start drawing.
|
||||
*/
|
||||
void (*onNativeWindowCreated)(GameActivity* activity, ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity has been resized. You should
|
||||
* retrieve the new size from the window and ensure that your rendering in
|
||||
* it now matches.
|
||||
*/
|
||||
void (*onNativeWindowResized)(GameActivity* activity, ANativeWindow* window,
|
||||
int32_t newWidth, int32_t newHeight);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity needs to be redrawn. To
|
||||
* avoid transient artifacts during screen changes (such resizing after
|
||||
* rotation), applications should not return from this function until they
|
||||
* have finished drawing their window in its current state.
|
||||
*/
|
||||
void (*onNativeWindowRedrawNeeded)(GameActivity* activity,
|
||||
ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity is going to be destroyed.
|
||||
* You MUST ensure that you do not touch the window object after returning
|
||||
* from this function: in the common case of drawing to the window from
|
||||
* another thread, that means the implementation of this callback must
|
||||
* properly synchronize with the other thread to stop its drawing before
|
||||
* returning from here.
|
||||
*/
|
||||
void (*onNativeWindowDestroyed)(GameActivity* activity,
|
||||
ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity has been resized. You should
|
||||
* retrieve the new size from the window and ensure that your rendering in
|
||||
* it now matches.
|
||||
*/
|
||||
void (*onNativeWindowResized)(GameActivity* activity, ANativeWindow* window,
|
||||
int32_t newWidth, int32_t newHeight);
|
||||
/**
|
||||
* The current device AConfiguration has changed. The new configuration can
|
||||
* be retrieved from assetManager.
|
||||
*/
|
||||
void (*onConfigurationChanged)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* The current device AConfiguration has changed. The new configuration can
|
||||
* be retrieved from assetManager.
|
||||
*/
|
||||
void (*onConfigurationChanged)(GameActivity* activity);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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 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 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 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 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 when WindowInsets of the main app window have changed.
|
||||
* Call GameActivity_getWindowInsets to retrieve the insets themselves.
|
||||
*/
|
||||
void (*onWindowInsetsChanged)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* 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 when the rectangle in the window where the content
|
||||
* should be placed has changed.
|
||||
*/
|
||||
void (*onContentRectChanged)(GameActivity* activity, const ARect* rect);
|
||||
|
||||
/**
|
||||
* 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 the software keyboard is shown or hidden.
|
||||
*/
|
||||
void (*onSoftwareKeyboardVisibilityChanged)(GameActivity* activity,
|
||||
bool visible);
|
||||
|
||||
/**
|
||||
* Callback called when WindowInsets of the main app window have changed.
|
||||
* Call GameActivity_getWindowInsets to retrieve the insets themselves.
|
||||
*/
|
||||
void (*onWindowInsetsChanged)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Callback called when the rectangle in the window where the content
|
||||
* should be placed has changed.
|
||||
*/
|
||||
void (*onContentRectChanged)(GameActivity *activity, const ARect *rect);
|
||||
/**
|
||||
* Callback called when the software keyboard is shown or hidden.
|
||||
*/
|
||||
bool (*onEditorAction)(GameActivity* activity, int action);
|
||||
} GameActivityCallbacks;
|
||||
|
||||
/**
|
||||
@@ -310,187 +336,187 @@ void GameActivity_finish(GameActivity* activity);
|
||||
* Flags for GameActivity_setWindowFlags,
|
||||
* as per the Java API at android.view.WindowManager.LayoutParams.
|
||||
*/
|
||||
enum GameActivitySetWindowFlags {
|
||||
/**
|
||||
* As long as this window is visible to the user, allow the lock
|
||||
* screen to activate while the screen is on. This can be used
|
||||
* independently, or in combination with {@link
|
||||
* GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} and/or {@link
|
||||
* GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
|
||||
/** Everything behind this window will be dimmed. */
|
||||
GAMEACTIVITY_FLAG_DIM_BEHIND = 0x00000002,
|
||||
/**
|
||||
* Blur everything behind this window.
|
||||
* @deprecated Blurring is no longer supported.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_BLUR_BEHIND = 0x00000004,
|
||||
/**
|
||||
* This window won't ever get key input focus, so the
|
||||
* user can not send key or other button events to it. Those will
|
||||
* instead go to whatever focusable window is behind it. This flag
|
||||
* will also enable {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL} whether or not
|
||||
* that is explicitly set.
|
||||
*
|
||||
* Setting this flag also implies that the window will not need to
|
||||
* interact with
|
||||
* a soft input method, so it will be Z-ordered and positioned
|
||||
* independently of any active input method (typically this means it
|
||||
* gets Z-ordered on top of the input method, so it can use the full
|
||||
* screen for its content and cover the input method if needed. You
|
||||
* can use {@link GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM} to modify this
|
||||
* behavior.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_NOT_FOCUSABLE = 0x00000008,
|
||||
/** This window can never receive touch events. */
|
||||
GAMEACTIVITY_FLAG_NOT_TOUCHABLE = 0x00000010,
|
||||
/**
|
||||
* Even when this window is focusable (its
|
||||
* {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set), allow any pointer
|
||||
* events outside of the window to be sent to the windows behind it.
|
||||
* Otherwise it will consume all pointer events itself, regardless of
|
||||
* whether they are inside of the window.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL = 0x00000020,
|
||||
/**
|
||||
* When set, if the device is asleep when the touch
|
||||
* screen is pressed, you will receive this first touch event. Usually
|
||||
* the first touch event is consumed by the system since the user can
|
||||
* not see what they are pressing on.
|
||||
*
|
||||
* @deprecated This flag has no effect.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
|
||||
/**
|
||||
* As long as this window is visible to the user, keep
|
||||
* the device's screen turned on and bright.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_KEEP_SCREEN_ON = 0x00000080,
|
||||
/**
|
||||
* Place the window within the entire screen, ignoring
|
||||
* decorations around the border (such as the status bar). The
|
||||
* window must correctly position its contents to take the screen
|
||||
* decoration into account.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN = 0x00000100,
|
||||
/** Allows the window to extend outside of the screen. */
|
||||
GAMEACTIVITY_FLAG_LAYOUT_NO_LIMITS = 0x00000200,
|
||||
/**
|
||||
* Hide all screen decorations (such as the status
|
||||
* bar) while this window is displayed. This allows the window to
|
||||
* use the entire display space for itself -- the status bar will
|
||||
* be hidden when an app window with this flag set is on the top
|
||||
* layer. A fullscreen window will ignore a value of {@link
|
||||
* GAMEACTIVITY_SOFT_INPUT_ADJUST_RESIZE}; the window will stay
|
||||
* fullscreen and will not resize.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_FULLSCREEN = 0x00000400,
|
||||
/**
|
||||
* Override {@link GAMEACTIVITY_FLAG_FULLSCREEN} and force the
|
||||
* screen decorations (such as the status bar) to be shown.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_FORCE_NOT_FULLSCREEN = 0x00000800,
|
||||
/**
|
||||
* Turn on dithering when compositing this window to
|
||||
* the screen.
|
||||
* @deprecated This flag is no longer used.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_DITHER = 0x00001000,
|
||||
/**
|
||||
* Treat the content of the window as secure, preventing
|
||||
* it from appearing in screenshots or from being viewed on non-secure
|
||||
* displays.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SECURE = 0x00002000,
|
||||
/**
|
||||
* A special mode where the layout parameters are used
|
||||
* to perform scaling of the surface when it is composited to the
|
||||
* screen.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SCALED = 0x00004000,
|
||||
/**
|
||||
* Intended for windows that will often be used when the user is
|
||||
* holding the screen against their face, it will aggressively
|
||||
* filter the event stream to prevent unintended presses in this
|
||||
* situation that may not be desired for a particular window, when
|
||||
* such an event stream is detected, the application will receive
|
||||
* a {@link AMOTION_EVENT_ACTION_CANCEL} to indicate this so
|
||||
* applications can handle this accordingly by taking no action on
|
||||
* the event until the finger is released.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_IGNORE_CHEEK_PRESSES = 0x00008000,
|
||||
/**
|
||||
* A special option only for use in combination with
|
||||
* {@link GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN}. When requesting layout in
|
||||
* the screen your window may appear on top of or behind screen decorations
|
||||
* such as the status bar. By also including this flag, the window
|
||||
* manager will report the inset rectangle needed to ensure your
|
||||
* content is not covered by screen decorations.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_LAYOUT_INSET_DECOR = 0x00010000,
|
||||
/**
|
||||
* Invert the state of {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} with
|
||||
* respect to how this window interacts with the current method.
|
||||
* That is, if FLAG_NOT_FOCUSABLE is set and this flag is set,
|
||||
* then the window will behave as if it needs to interact with the
|
||||
* input method and thus be placed behind/away from it; if {@link
|
||||
* GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set and this flag is set,
|
||||
* then the window will behave as if it doesn't need to interact
|
||||
* with the input method and can be placed to use more space and
|
||||
* cover the input method.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM = 0x00020000,
|
||||
/**
|
||||
* If you have set {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL}, you
|
||||
* can set this flag to receive a single special MotionEvent with
|
||||
* the action
|
||||
* {@link AMOTION_EVENT_ACTION_OUTSIDE} for
|
||||
* touches that occur outside of your window. Note that you will not
|
||||
* receive the full down/move/up gesture, only the location of the
|
||||
* first down as an {@link AMOTION_EVENT_ACTION_OUTSIDE}.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
|
||||
/**
|
||||
* Special flag to let windows be shown when the screen
|
||||
* is locked. This will let application windows take precedence over
|
||||
* key guard or any other lock screens. Can be used with
|
||||
* {@link GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} to turn screen on and display
|
||||
* windows directly before showing the key guard window. Can be used with
|
||||
* {@link GAMEACTIVITY_FLAG_DISMISS_KEYGUARD} to automatically fully
|
||||
* dismisss non-secure keyguards. This flag only applies to the top-most
|
||||
* full-screen window.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED = 0x00080000,
|
||||
/**
|
||||
* Ask that the system wallpaper be shown behind
|
||||
* your window. The window surface must be translucent to be able
|
||||
* to actually see the wallpaper behind it; this flag just ensures
|
||||
* that the wallpaper surface will be there if this window actually
|
||||
* has translucent regions.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SHOW_WALLPAPER = 0x00100000,
|
||||
/**
|
||||
* When set as a window is being added or made
|
||||
* visible, once the window has been shown then the system will
|
||||
* poke the power manager's user activity (as if the user had woken
|
||||
* up the device) to turn the screen on.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_TURN_SCREEN_ON = 0x00200000,
|
||||
/**
|
||||
* When set the window will cause the keyguard to
|
||||
* be dismissed, only if it is not a secure lock keyguard. Because such
|
||||
* a keyguard is not needed for security, it will never re-appear if
|
||||
* the user navigates to another window (in contrast to
|
||||
* {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}, which will only temporarily
|
||||
* hide both secure and non-secure keyguards but ensure they reappear
|
||||
* when the user moves to another UI that doesn't hide them).
|
||||
* If the keyguard is currently active and is secure (requires an
|
||||
* unlock pattern) than the user will still need to confirm it before
|
||||
* seeing this window, unless {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} has
|
||||
* also been set.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_DISMISS_KEYGUARD = 0x00400000,
|
||||
enum GameActivitySetWindowFlags : uint32_t {
|
||||
/**
|
||||
* As long as this window is visible to the user, allow the lock
|
||||
* screen to activate while the screen is on. This can be used
|
||||
* independently, or in combination with {@link
|
||||
* GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} and/or {@link
|
||||
* GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
|
||||
/** Everything behind this window will be dimmed. */
|
||||
GAMEACTIVITY_FLAG_DIM_BEHIND = 0x00000002,
|
||||
/**
|
||||
* Blur everything behind this window.
|
||||
* @deprecated Blurring is no longer supported.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_BLUR_BEHIND = 0x00000004,
|
||||
/**
|
||||
* This window won't ever get key input focus, so the
|
||||
* user can not send key or other button events to it. Those will
|
||||
* instead go to whatever focusable window is behind it. This flag
|
||||
* will also enable {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL} whether or not
|
||||
* that is explicitly set.
|
||||
*
|
||||
* Setting this flag also implies that the window will not need to
|
||||
* interact with
|
||||
* a soft input method, so it will be Z-ordered and positioned
|
||||
* independently of any active input method (typically this means it
|
||||
* gets Z-ordered on top of the input method, so it can use the full
|
||||
* screen for its content and cover the input method if needed. You
|
||||
* can use {@link GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM} to modify this
|
||||
* behavior.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_NOT_FOCUSABLE = 0x00000008,
|
||||
/** This window can never receive touch events. */
|
||||
GAMEACTIVITY_FLAG_NOT_TOUCHABLE = 0x00000010,
|
||||
/**
|
||||
* Even when this window is focusable (its
|
||||
* {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set), allow any pointer
|
||||
* events outside of the window to be sent to the windows behind it.
|
||||
* Otherwise it will consume all pointer events itself, regardless of
|
||||
* whether they are inside of the window.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL = 0x00000020,
|
||||
/**
|
||||
* When set, if the device is asleep when the touch
|
||||
* screen is pressed, you will receive this first touch event. Usually
|
||||
* the first touch event is consumed by the system since the user can
|
||||
* not see what they are pressing on.
|
||||
*
|
||||
* @deprecated This flag has no effect.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
|
||||
/**
|
||||
* As long as this window is visible to the user, keep
|
||||
* the device's screen turned on and bright.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_KEEP_SCREEN_ON = 0x00000080,
|
||||
/**
|
||||
* Place the window within the entire screen, ignoring
|
||||
* decorations around the border (such as the status bar). The
|
||||
* window must correctly position its contents to take the screen
|
||||
* decoration into account.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN = 0x00000100,
|
||||
/** Allows the window to extend outside of the screen. */
|
||||
GAMEACTIVITY_FLAG_LAYOUT_NO_LIMITS = 0x00000200,
|
||||
/**
|
||||
* Hide all screen decorations (such as the status
|
||||
* bar) while this window is displayed. This allows the window to
|
||||
* use the entire display space for itself -- the status bar will
|
||||
* be hidden when an app window with this flag set is on the top
|
||||
* layer. A fullscreen window will ignore a value of {@link
|
||||
* GAMEACTIVITY_SOFT_INPUT_ADJUST_RESIZE}; the window will stay
|
||||
* fullscreen and will not resize.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_FULLSCREEN = 0x00000400,
|
||||
/**
|
||||
* Override {@link GAMEACTIVITY_FLAG_FULLSCREEN} and force the
|
||||
* screen decorations (such as the status bar) to be shown.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_FORCE_NOT_FULLSCREEN = 0x00000800,
|
||||
/**
|
||||
* Turn on dithering when compositing this window to
|
||||
* the screen.
|
||||
* @deprecated This flag is no longer used.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_DITHER = 0x00001000,
|
||||
/**
|
||||
* Treat the content of the window as secure, preventing
|
||||
* it from appearing in screenshots or from being viewed on non-secure
|
||||
* displays.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SECURE = 0x00002000,
|
||||
/**
|
||||
* A special mode where the layout parameters are used
|
||||
* to perform scaling of the surface when it is composited to the
|
||||
* screen.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SCALED = 0x00004000,
|
||||
/**
|
||||
* Intended for windows that will often be used when the user is
|
||||
* holding the screen against their face, it will aggressively
|
||||
* filter the event stream to prevent unintended presses in this
|
||||
* situation that may not be desired for a particular window, when
|
||||
* such an event stream is detected, the application will receive
|
||||
* a {@link AMOTION_EVENT_ACTION_CANCEL} to indicate this so
|
||||
* applications can handle this accordingly by taking no action on
|
||||
* the event until the finger is released.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_IGNORE_CHEEK_PRESSES = 0x00008000,
|
||||
/**
|
||||
* A special option only for use in combination with
|
||||
* {@link GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN}. When requesting layout in
|
||||
* the screen your window may appear on top of or behind screen decorations
|
||||
* such as the status bar. By also including this flag, the window
|
||||
* manager will report the inset rectangle needed to ensure your
|
||||
* content is not covered by screen decorations.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_LAYOUT_INSET_DECOR = 0x00010000,
|
||||
/**
|
||||
* Invert the state of {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} with
|
||||
* respect to how this window interacts with the current method.
|
||||
* That is, if FLAG_NOT_FOCUSABLE is set and this flag is set,
|
||||
* then the window will behave as if it needs to interact with the
|
||||
* input method and thus be placed behind/away from it; if {@link
|
||||
* GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set and this flag is set,
|
||||
* then the window will behave as if it doesn't need to interact
|
||||
* with the input method and can be placed to use more space and
|
||||
* cover the input method.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM = 0x00020000,
|
||||
/**
|
||||
* If you have set {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL}, you
|
||||
* can set this flag to receive a single special MotionEvent with
|
||||
* the action
|
||||
* {@link AMOTION_EVENT_ACTION_OUTSIDE} for
|
||||
* touches that occur outside of your window. Note that you will not
|
||||
* receive the full down/move/up gesture, only the location of the
|
||||
* first down as an {@link AMOTION_EVENT_ACTION_OUTSIDE}.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
|
||||
/**
|
||||
* Special flag to let windows be shown when the screen
|
||||
* is locked. This will let application windows take precedence over
|
||||
* key guard or any other lock screens. Can be used with
|
||||
* {@link GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} to turn screen on and display
|
||||
* windows directly before showing the key guard window. Can be used with
|
||||
* {@link GAMEACTIVITY_FLAG_DISMISS_KEYGUARD} to automatically fully
|
||||
* dismisss non-secure keyguards. This flag only applies to the top-most
|
||||
* full-screen window.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED = 0x00080000,
|
||||
/**
|
||||
* Ask that the system wallpaper be shown behind
|
||||
* your window. The window surface must be translucent to be able
|
||||
* to actually see the wallpaper behind it; this flag just ensures
|
||||
* that the wallpaper surface will be there if this window actually
|
||||
* has translucent regions.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SHOW_WALLPAPER = 0x00100000,
|
||||
/**
|
||||
* When set as a window is being added or made
|
||||
* visible, once the window has been shown then the system will
|
||||
* poke the power manager's user activity (as if the user had woken
|
||||
* up the device) to turn the screen on.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_TURN_SCREEN_ON = 0x00200000,
|
||||
/**
|
||||
* When set the window will cause the keyguard to
|
||||
* be dismissed, only if it is not a secure lock keyguard. Because such
|
||||
* a keyguard is not needed for security, it will never re-appear if
|
||||
* the user navigates to another window (in contrast to
|
||||
* {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}, which will only temporarily
|
||||
* hide both secure and non-secure keyguards but ensure they reappear
|
||||
* when the user moves to another UI that doesn't hide them).
|
||||
* If the keyguard is currently active and is secure (requires an
|
||||
* unlock pattern) than the user will still need to confirm it before
|
||||
* seeing this window, unless {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} has
|
||||
* also been set.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_DISMISS_KEYGUARD = 0x00400000,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -510,19 +536,19 @@ void GameActivity_setWindowFlags(GameActivity* activity, uint32_t addFlags,
|
||||
* Flags for GameActivity_showSoftInput; see the Java InputMethodManager
|
||||
* API for documentation.
|
||||
*/
|
||||
enum GameActivityShowSoftInputFlags {
|
||||
/**
|
||||
* Implicit request to show the input window, not as the result
|
||||
* of a direct request by the user.
|
||||
*/
|
||||
GAMEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,
|
||||
enum GameActivityShowSoftInputFlags : uint8_t {
|
||||
/**
|
||||
* Implicit request to show the input window, not as the result
|
||||
* of a direct request by the user.
|
||||
*/
|
||||
GAMEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,
|
||||
|
||||
/**
|
||||
* The user has forced the input method open (such as by
|
||||
* long-pressing menu) so it should not be closed until they
|
||||
* explicitly do so.
|
||||
*/
|
||||
GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
|
||||
/**
|
||||
* The user has forced the input method open (such as by
|
||||
* long-pressing menu) so it should not be closed until they
|
||||
* explicitly do so.
|
||||
*/
|
||||
GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -533,6 +559,13 @@ enum GameActivityShowSoftInputFlags {
|
||||
*/
|
||||
void GameActivity_showSoftInput(GameActivity* activity, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Restarts the input method. Calls InputMethodManager.restartInput().
|
||||
* Note that this method can be called from *any* thread; it will send a message
|
||||
* to the main thread of the process where the Java call will take place.
|
||||
*/
|
||||
void GameActivity_restartInput(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Set the text entry state (see documentation of the GameTextInputState struct
|
||||
* in the Game Text Input library reference).
|
||||
@@ -560,17 +593,17 @@ GameTextInput* GameActivity_getTextInput(const GameActivity* activity);
|
||||
* Flags for GameActivity_hideSoftInput; see the Java InputMethodManager
|
||||
* API for documentation.
|
||||
*/
|
||||
enum GameActivityHideSoftInputFlags {
|
||||
/**
|
||||
* The soft input window should only be hidden if it was not
|
||||
* explicitly shown by the user.
|
||||
*/
|
||||
GAMEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,
|
||||
/**
|
||||
* The soft input window should normally be hidden, unless it was
|
||||
* originally shown with {@link GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED}.
|
||||
*/
|
||||
GAMEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
|
||||
enum GameActivityHideSoftInputFlags : uint16_t {
|
||||
/**
|
||||
* The soft input window should only be hidden if it was not
|
||||
* explicitly shown by the user.
|
||||
*/
|
||||
GAMEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,
|
||||
/**
|
||||
* The soft input window should normally be hidden, unless it was
|
||||
* originally shown with {@link GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED}.
|
||||
*/
|
||||
GAMEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -590,18 +623,23 @@ void GameActivity_hideSoftInput(GameActivity* activity, uint32_t flags);
|
||||
void GameActivity_getWindowInsets(GameActivity* activity,
|
||||
GameCommonInsetsType type, ARect* insets);
|
||||
|
||||
/**
|
||||
* Tells whether the software keyboard is visible or not.
|
||||
*/
|
||||
bool GameActivity_isSoftwareKeyboardVisible(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Set options on how the IME behaves when it is requested for text input.
|
||||
* See
|
||||
* https://developer.android.com/reference/android/view/inputmethod/EditorInfo
|
||||
* for the meaning of inputType, actionId and imeOptions.
|
||||
*
|
||||
* Note that this function will attach the current thread to the JVM if it is
|
||||
* not already attached, so the caller must detach the thread from the JVM
|
||||
* before the thread is destroyed using DetachCurrentThread.
|
||||
* <b>Note:</b> currently only TYPE_NULL AND TYPE_CLASS_NUMBER are supported.
|
||||
*/
|
||||
void GameActivity_setImeEditorInfo(GameActivity* activity, int inputType,
|
||||
int actionId, int imeOptions);
|
||||
void GameActivity_setImeEditorInfo(GameActivity* activity,
|
||||
enum GameTextInputType inputType,
|
||||
enum GameTextInputActionType actionId,
|
||||
enum GameTextInputImeOptions imeOptions);
|
||||
|
||||
/**
|
||||
* These are getters for Configuration class members. They may be called from
|
||||
@@ -615,6 +653,7 @@ int GameActivity_getFontWeightAdjustment(GameActivity* activity);
|
||||
int GameActivity_getHardKeyboardHidden(GameActivity* activity);
|
||||
int GameActivity_getKeyboard(GameActivity* activity);
|
||||
int GameActivity_getKeyboardHidden(GameActivity* activity);
|
||||
int GameActivity_getLocalesCount(GameActivity* activity);
|
||||
int GameActivity_getMcc(GameActivity* activity);
|
||||
int GameActivity_getMnc(GameActivity* activity);
|
||||
int GameActivity_getNavigation(GameActivity* activity);
|
||||
@@ -627,6 +666,42 @@ int GameActivity_getSmallestScreenWidthDp(GameActivity* activity);
|
||||
int GameActivity_getTouchscreen(GameActivity* activity);
|
||||
int GameActivity_getUIMode(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* The functions below return Java locale information.
|
||||
*
|
||||
* In simple cases there will be just one locale, but it's possible tha
|
||||
* there are more than one locale objects. Users are encouraged to write code
|
||||
* that handles all locales and not just the first one.
|
||||
*
|
||||
* The functions in the block below return string values in the provided buffer.
|
||||
* Return value is zero if there were no errors, otherwise it's non-zero.
|
||||
* If the return value is zero, `dst` will contain a null-terminated string:
|
||||
* strlen(dst) <= dst_size - 1.
|
||||
* If the return value is non-zero, the content of dst is undefined.
|
||||
*
|
||||
* Parameters:
|
||||
*
|
||||
* dst, dst_size: define a receiver buffer. Locale string can be something
|
||||
* short like "EN/EN", but it may be longer. You should be safe with a buffer
|
||||
* size of 256 bytes.
|
||||
*
|
||||
* If the buffer is too small, ENOBUFS is returned. Try allocating a larger
|
||||
* buffer in this case.
|
||||
*
|
||||
* localeIdx must be between 0 and the value of GameActivity_getLocalesCount().
|
||||
* If localeIdx is out of range, EINVAL is returned.
|
||||
*
|
||||
* Refer to Java documentation of locales for more information.
|
||||
*/
|
||||
int GameActivity_getLocaleLanguage(char* dst, size_t dst_size,
|
||||
GameActivity* activity, size_t localeIdx);
|
||||
int GameActivity_getLocaleScript(char* dst, size_t dst_size,
|
||||
GameActivity* activity, size_t localeIdx);
|
||||
int GameActivity_getLocaleCountry(char* dst, size_t dst_size,
|
||||
GameActivity* activity, size_t localeIdx);
|
||||
int GameActivity_getLocaleVariant(char* dst, size_t dst_size,
|
||||
GameActivity* activity, size_t localeIdx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
-414
@@ -1,414 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "GameActivityEvents.h"
|
||||
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "GameActivityLog.h"
|
||||
|
||||
// TODO(b/187147166): these functions were extracted from the Game SDK
|
||||
// (gamesdk/src/common/system_utils.h). system_utils.h/cpp should be used
|
||||
// instead.
|
||||
namespace {
|
||||
|
||||
std::string getSystemPropViaGet(const char *key,
|
||||
const char *default_value = "") {
|
||||
char buffer[PROP_VALUE_MAX + 1] = ""; // +1 for terminator
|
||||
int bufferLen = __system_property_get(key, buffer);
|
||||
if (bufferLen > 0)
|
||||
return buffer;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string GetSystemProp(const char *key, const char *default_value = "") {
|
||||
return getSystemPropViaGet(key, default_value);
|
||||
}
|
||||
|
||||
int GetSystemPropAsInt(const char *key, int default_value = 0) {
|
||||
std::string prop = GetSystemProp(key);
|
||||
return prop == "" ? default_value : strtoll(prop.c_str(), nullptr, 10);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
#ifndef NELEM
|
||||
#define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0])))
|
||||
#endif
|
||||
|
||||
static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = {
|
||||
/* AMOTION_EVENT_AXIS_X */ true,
|
||||
/* AMOTION_EVENT_AXIS_Y */ true,
|
||||
// Disable all other axes by default (they can be enabled using
|
||||
// `GameActivityPointerAxes_enableAxis`).
|
||||
false};
|
||||
|
||||
extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabledAxes[axis] = true;
|
||||
}
|
||||
|
||||
float GameActivityPointerAxes_getAxisValue(
|
||||
const GameActivityPointerAxes *pointerInfo, int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!enabledAxes[axis]) {
|
||||
ALOGW("Axis %d must be enabled before it can be accessed.", axis);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return pointerInfo->axisValues[axis];
|
||||
}
|
||||
|
||||
extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabledAxes[axis] = false;
|
||||
}
|
||||
|
||||
float GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
const GameActivityMotionEvent *event, int axis, int pointerIndex,
|
||||
int historyPos) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
ALOGE("Invalid axis %d", axis);
|
||||
return -1;
|
||||
}
|
||||
if (pointerIndex < 0 || pointerIndex >= event->pointerCount) {
|
||||
ALOGE("Invalid pointer index %d", pointerIndex);
|
||||
return -1;
|
||||
}
|
||||
if (historyPos < 0 || historyPos >= event->historySize) {
|
||||
ALOGE("Invalid history index %d", historyPos);
|
||||
return -1;
|
||||
}
|
||||
if (!enabledAxes[axis]) {
|
||||
ALOGW("Axis %d must be enabled before it can be accessed.", axis);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pointerOffset = pointerIndex * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
int historyValuesOffset = historyPos * event->pointerCount *
|
||||
GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
return event
|
||||
->historicalAxisValues[historyValuesOffset + pointerOffset + axis];
|
||||
}
|
||||
|
||||
static struct {
|
||||
jmethodID getDeviceId;
|
||||
jmethodID getSource;
|
||||
jmethodID getAction;
|
||||
|
||||
jmethodID getEventTime;
|
||||
jmethodID getDownTime;
|
||||
|
||||
jmethodID getFlags;
|
||||
jmethodID getMetaState;
|
||||
|
||||
jmethodID getActionButton;
|
||||
jmethodID getButtonState;
|
||||
jmethodID getClassification;
|
||||
jmethodID getEdgeFlags;
|
||||
|
||||
jmethodID getHistorySize;
|
||||
jmethodID getHistoricalEventTime;
|
||||
|
||||
jmethodID getPointerCount;
|
||||
jmethodID getPointerId;
|
||||
|
||||
jmethodID getToolType;
|
||||
|
||||
jmethodID getRawX;
|
||||
jmethodID getRawY;
|
||||
jmethodID getXPrecision;
|
||||
jmethodID getYPrecision;
|
||||
jmethodID getAxisValue;
|
||||
|
||||
jmethodID getHistoricalAxisValue;
|
||||
} gMotionEventClassInfo;
|
||||
|
||||
extern "C" void GameActivityMotionEvent_destroy(
|
||||
GameActivityMotionEvent *c_event) {
|
||||
delete c_event->historicalAxisValues;
|
||||
delete c_event->historicalEventTimesMillis;
|
||||
delete c_event->historicalEventTimesNanos;
|
||||
}
|
||||
|
||||
extern "C" void GameActivityMotionEvent_fromJava(
|
||||
JNIEnv *env, jobject motionEvent, GameActivityMotionEvent *out_event) {
|
||||
static bool gMotionEventClassInfoInitialized = false;
|
||||
if (!gMotionEventClassInfoInitialized) {
|
||||
int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk");
|
||||
gMotionEventClassInfo = {0};
|
||||
jclass motionEventClass = env->FindClass("android/view/MotionEvent");
|
||||
gMotionEventClassInfo.getDeviceId =
|
||||
env->GetMethodID(motionEventClass, "getDeviceId", "()I");
|
||||
gMotionEventClassInfo.getSource =
|
||||
env->GetMethodID(motionEventClass, "getSource", "()I");
|
||||
gMotionEventClassInfo.getAction =
|
||||
env->GetMethodID(motionEventClass, "getAction", "()I");
|
||||
gMotionEventClassInfo.getEventTime =
|
||||
env->GetMethodID(motionEventClass, "getEventTime", "()J");
|
||||
gMotionEventClassInfo.getDownTime =
|
||||
env->GetMethodID(motionEventClass, "getDownTime", "()J");
|
||||
gMotionEventClassInfo.getFlags =
|
||||
env->GetMethodID(motionEventClass, "getFlags", "()I");
|
||||
gMotionEventClassInfo.getMetaState =
|
||||
env->GetMethodID(motionEventClass, "getMetaState", "()I");
|
||||
if (sdkVersion >= 23) {
|
||||
gMotionEventClassInfo.getActionButton =
|
||||
env->GetMethodID(motionEventClass, "getActionButton", "()I");
|
||||
}
|
||||
if (sdkVersion >= 14) {
|
||||
gMotionEventClassInfo.getButtonState =
|
||||
env->GetMethodID(motionEventClass, "getButtonState", "()I");
|
||||
}
|
||||
if (sdkVersion >= 29) {
|
||||
gMotionEventClassInfo.getClassification =
|
||||
env->GetMethodID(motionEventClass, "getClassification", "()I");
|
||||
}
|
||||
gMotionEventClassInfo.getEdgeFlags =
|
||||
env->GetMethodID(motionEventClass, "getEdgeFlags", "()I");
|
||||
|
||||
gMotionEventClassInfo.getHistorySize =
|
||||
env->GetMethodID(motionEventClass, "getHistorySize", "()I");
|
||||
gMotionEventClassInfo.getHistoricalEventTime = env->GetMethodID(
|
||||
motionEventClass, "getHistoricalEventTime", "(I)J");
|
||||
|
||||
gMotionEventClassInfo.getPointerCount =
|
||||
env->GetMethodID(motionEventClass, "getPointerCount", "()I");
|
||||
gMotionEventClassInfo.getPointerId =
|
||||
env->GetMethodID(motionEventClass, "getPointerId", "(I)I");
|
||||
gMotionEventClassInfo.getToolType =
|
||||
env->GetMethodID(motionEventClass, "getToolType", "(I)I");
|
||||
if (sdkVersion >= 29) {
|
||||
gMotionEventClassInfo.getRawX =
|
||||
env->GetMethodID(motionEventClass, "getRawX", "(I)F");
|
||||
gMotionEventClassInfo.getRawY =
|
||||
env->GetMethodID(motionEventClass, "getRawY", "(I)F");
|
||||
}
|
||||
gMotionEventClassInfo.getXPrecision =
|
||||
env->GetMethodID(motionEventClass, "getXPrecision", "()F");
|
||||
gMotionEventClassInfo.getYPrecision =
|
||||
env->GetMethodID(motionEventClass, "getYPrecision", "()F");
|
||||
gMotionEventClassInfo.getAxisValue =
|
||||
env->GetMethodID(motionEventClass, "getAxisValue", "(II)F");
|
||||
|
||||
gMotionEventClassInfo.getHistoricalAxisValue = env->GetMethodID(
|
||||
motionEventClass, "getHistoricalAxisValue", "(III)F");
|
||||
gMotionEventClassInfoInitialized = true;
|
||||
}
|
||||
|
||||
int pointerCount =
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getPointerCount);
|
||||
pointerCount =
|
||||
std::min(pointerCount, GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT);
|
||||
out_event->pointerCount = pointerCount;
|
||||
for (int i = 0; i < pointerCount; ++i) {
|
||||
out_event->pointers[i] = {
|
||||
/*id=*/env->CallIntMethod(motionEvent,
|
||||
gMotionEventClassInfo.getPointerId, i),
|
||||
/*toolType=*/
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getToolType,
|
||||
i),
|
||||
/*axisValues=*/{0},
|
||||
/*rawX=*/gMotionEventClassInfo.getRawX
|
||||
? env->CallFloatMethod(motionEvent,
|
||||
gMotionEventClassInfo.getRawX, i)
|
||||
: 0,
|
||||
/*rawY=*/gMotionEventClassInfo.getRawY
|
||||
? env->CallFloatMethod(motionEvent,
|
||||
gMotionEventClassInfo.getRawY, i)
|
||||
: 0,
|
||||
};
|
||||
|
||||
for (int axisIndex = 0;
|
||||
axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; ++axisIndex) {
|
||||
if (enabledAxes[axisIndex]) {
|
||||
out_event->pointers[i].axisValues[axisIndex] =
|
||||
env->CallFloatMethod(motionEvent,
|
||||
gMotionEventClassInfo.getAxisValue,
|
||||
axisIndex, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int historySize =
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getHistorySize);
|
||||
out_event->historySize = historySize;
|
||||
out_event->historicalAxisValues =
|
||||
new float[historySize * pointerCount *
|
||||
GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
|
||||
out_event->historicalEventTimesMillis = new int64_t[historySize];
|
||||
out_event->historicalEventTimesNanos = new int64_t[historySize];
|
||||
|
||||
for (int historyIndex = 0; historyIndex < historySize; historyIndex++) {
|
||||
out_event->historicalEventTimesMillis[historyIndex] =
|
||||
env->CallLongMethod(motionEvent,
|
||||
gMotionEventClassInfo.getHistoricalEventTime,
|
||||
historyIndex);
|
||||
out_event->historicalEventTimesNanos[historyIndex] =
|
||||
out_event->historicalEventTimesMillis[historyIndex] * 1000000;
|
||||
for (int i = 0; i < pointerCount; ++i) {
|
||||
int pointerOffset = i * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
int historyAxisOffset = historyIndex * pointerCount *
|
||||
GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
float *axisValues =
|
||||
&out_event
|
||||
->historicalAxisValues[historyAxisOffset + pointerOffset];
|
||||
for (int axisIndex = 0;
|
||||
axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
++axisIndex) {
|
||||
if (enabledAxes[axisIndex]) {
|
||||
axisValues[axisIndex] = env->CallFloatMethod(
|
||||
motionEvent,
|
||||
gMotionEventClassInfo.getHistoricalAxisValue, axisIndex,
|
||||
i, historyIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out_event->deviceId =
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getDeviceId);
|
||||
out_event->source =
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getSource);
|
||||
out_event->action =
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getAction);
|
||||
out_event->eventTime =
|
||||
env->CallLongMethod(motionEvent, gMotionEventClassInfo.getEventTime) *
|
||||
1000000;
|
||||
out_event->downTime =
|
||||
env->CallLongMethod(motionEvent, gMotionEventClassInfo.getDownTime) *
|
||||
1000000;
|
||||
out_event->flags =
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getFlags);
|
||||
out_event->metaState =
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getMetaState);
|
||||
out_event->actionButton =
|
||||
gMotionEventClassInfo.getActionButton
|
||||
? env->CallIntMethod(motionEvent,
|
||||
gMotionEventClassInfo.getActionButton)
|
||||
: 0;
|
||||
out_event->buttonState =
|
||||
gMotionEventClassInfo.getButtonState
|
||||
? env->CallIntMethod(motionEvent,
|
||||
gMotionEventClassInfo.getButtonState)
|
||||
: 0;
|
||||
out_event->classification =
|
||||
gMotionEventClassInfo.getClassification
|
||||
? env->CallIntMethod(motionEvent,
|
||||
gMotionEventClassInfo.getClassification)
|
||||
: 0;
|
||||
out_event->edgeFlags =
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getEdgeFlags);
|
||||
out_event->precisionX =
|
||||
env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getXPrecision);
|
||||
out_event->precisionY =
|
||||
env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getYPrecision);
|
||||
}
|
||||
|
||||
static struct {
|
||||
jmethodID getDeviceId;
|
||||
jmethodID getSource;
|
||||
jmethodID getAction;
|
||||
|
||||
jmethodID getEventTime;
|
||||
jmethodID getDownTime;
|
||||
|
||||
jmethodID getFlags;
|
||||
jmethodID getMetaState;
|
||||
|
||||
jmethodID getModifiers;
|
||||
jmethodID getRepeatCount;
|
||||
jmethodID getKeyCode;
|
||||
jmethodID getScanCode;
|
||||
//jmethodID getUnicodeChar;
|
||||
} gKeyEventClassInfo;
|
||||
|
||||
extern "C" void GameActivityKeyEvent_fromJava(JNIEnv *env, jobject keyEvent,
|
||||
GameActivityKeyEvent *out_event) {
|
||||
static bool gKeyEventClassInfoInitialized = false;
|
||||
if (!gKeyEventClassInfoInitialized) {
|
||||
int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk");
|
||||
gKeyEventClassInfo = {0};
|
||||
jclass keyEventClass = env->FindClass("android/view/KeyEvent");
|
||||
gKeyEventClassInfo.getDeviceId =
|
||||
env->GetMethodID(keyEventClass, "getDeviceId", "()I");
|
||||
gKeyEventClassInfo.getSource =
|
||||
env->GetMethodID(keyEventClass, "getSource", "()I");
|
||||
gKeyEventClassInfo.getAction =
|
||||
env->GetMethodID(keyEventClass, "getAction", "()I");
|
||||
gKeyEventClassInfo.getEventTime =
|
||||
env->GetMethodID(keyEventClass, "getEventTime", "()J");
|
||||
gKeyEventClassInfo.getDownTime =
|
||||
env->GetMethodID(keyEventClass, "getDownTime", "()J");
|
||||
gKeyEventClassInfo.getFlags =
|
||||
env->GetMethodID(keyEventClass, "getFlags", "()I");
|
||||
gKeyEventClassInfo.getMetaState =
|
||||
env->GetMethodID(keyEventClass, "getMetaState", "()I");
|
||||
if (sdkVersion >= 13) {
|
||||
gKeyEventClassInfo.getModifiers =
|
||||
env->GetMethodID(keyEventClass, "getModifiers", "()I");
|
||||
}
|
||||
gKeyEventClassInfo.getRepeatCount =
|
||||
env->GetMethodID(keyEventClass, "getRepeatCount", "()I");
|
||||
gKeyEventClassInfo.getKeyCode =
|
||||
env->GetMethodID(keyEventClass, "getKeyCode", "()I");
|
||||
gKeyEventClassInfo.getScanCode =
|
||||
env->GetMethodID(keyEventClass, "getScanCode", "()I");
|
||||
//gKeyEventClassInfo.getUnicodeChar =
|
||||
// env->GetMethodID(keyEventClass, "getUnicodeChar", "()I");
|
||||
|
||||
gKeyEventClassInfoInitialized = true;
|
||||
}
|
||||
|
||||
*out_event = {
|
||||
/*deviceId=*/env->CallIntMethod(keyEvent,
|
||||
gKeyEventClassInfo.getDeviceId),
|
||||
/*source=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getSource),
|
||||
/*action=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getAction),
|
||||
// TODO: introduce a millisecondsToNanoseconds helper:
|
||||
/*eventTime=*/
|
||||
env->CallLongMethod(keyEvent, gKeyEventClassInfo.getEventTime) *
|
||||
1000000,
|
||||
/*downTime=*/
|
||||
env->CallLongMethod(keyEvent, gKeyEventClassInfo.getDownTime) * 1000000,
|
||||
/*flags=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getFlags),
|
||||
/*metaState=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getMetaState),
|
||||
/*modifiers=*/gKeyEventClassInfo.getModifiers
|
||||
? env->CallIntMethod(keyEvent, gKeyEventClassInfo.getModifiers)
|
||||
: 0,
|
||||
/*repeatCount=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getRepeatCount),
|
||||
/*keyCode=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getKeyCode),
|
||||
/*scanCode=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getScanCode)
|
||||
/*unicodeChar=*/
|
||||
//env->CallIntMethod(keyEvent, gKeyEventClassInfo.getUnicodeChar)
|
||||
};
|
||||
}
|
||||
+74
-98
@@ -57,29 +57,29 @@ extern "C" {
|
||||
* \see GameActivityMotionEvent
|
||||
*/
|
||||
typedef struct GameActivityPointerAxes {
|
||||
int32_t id;
|
||||
int32_t toolType;
|
||||
float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
|
||||
float rawX;
|
||||
float rawY;
|
||||
int32_t id;
|
||||
int32_t toolType;
|
||||
float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
|
||||
float rawX;
|
||||
float rawY;
|
||||
} GameActivityPointerAxes;
|
||||
|
||||
/** \brief Get the toolType of the pointer. */
|
||||
inline int32_t GameActivityPointerAxes_getToolType(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->toolType;
|
||||
return pointerInfo->toolType;
|
||||
}
|
||||
|
||||
/** \brief Get the current X coordinate of the pointer. */
|
||||
inline float GameActivityPointerAxes_getX(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X];
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X];
|
||||
}
|
||||
|
||||
/** \brief Get the current Y coordinate of the pointer. */
|
||||
inline float GameActivityPointerAxes_getY(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y];
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,44 +126,44 @@ float GameActivityPointerAxes_getAxisValue(
|
||||
|
||||
inline float GameActivityPointerAxes_getPressure(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_PRESSURE);
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_PRESSURE);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getSize(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_SIZE);
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_SIZE);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getTouchMajor(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOUCH_MAJOR);
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOUCH_MAJOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getTouchMinor(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOUCH_MINOR);
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOUCH_MINOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getToolMajor(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOOL_MAJOR);
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOOL_MAJOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getToolMinor(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOOL_MINOR);
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOOL_MINOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getOrientation(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_ORIENTATION);
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_ORIENTATION);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +171,7 @@ inline float GameActivityPointerAxes_getOrientation(
|
||||
*/
|
||||
#if (defined GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE)
|
||||
#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT \
|
||||
GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE
|
||||
GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE
|
||||
#else
|
||||
#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8
|
||||
#endif
|
||||
@@ -183,32 +183,32 @@ inline float GameActivityPointerAxes_getOrientation(
|
||||
* (see https://developer.android.com/reference/android/view/MotionEvent).
|
||||
*/
|
||||
typedef struct GameActivityMotionEvent {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
|
||||
int32_t actionButton;
|
||||
int32_t buttonState;
|
||||
int32_t classification;
|
||||
int32_t edgeFlags;
|
||||
int32_t actionButton;
|
||||
int32_t buttonState;
|
||||
int32_t classification;
|
||||
int32_t edgeFlags;
|
||||
|
||||
uint32_t pointerCount;
|
||||
GameActivityPointerAxes
|
||||
pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT];
|
||||
uint32_t pointerCount;
|
||||
GameActivityPointerAxes
|
||||
pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT];
|
||||
|
||||
int historySize;
|
||||
int64_t* historicalEventTimesMillis;
|
||||
int64_t* historicalEventTimesNanos;
|
||||
float* historicalAxisValues;
|
||||
int historySize;
|
||||
int64_t* historicalEventTimesMillis;
|
||||
int64_t* historicalEventTimesNanos;
|
||||
float* historicalAxisValues;
|
||||
|
||||
float precisionX;
|
||||
float precisionY;
|
||||
float precisionX;
|
||||
float precisionY;
|
||||
} GameActivityMotionEvent;
|
||||
|
||||
float GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
@@ -217,78 +217,66 @@ float GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
|
||||
inline int GameActivityMotionEvent_getHistorySize(
|
||||
const GameActivityMotionEvent* event) {
|
||||
return event->historySize;
|
||||
return event->historySize;
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalX(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_X, pointerIndex, historyPos);
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_X, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalY(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_Y, pointerIndex, historyPos);
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_Y, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalPressure(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historyPos);
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalSize(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_SIZE, pointerIndex, historyPos);
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_SIZE, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalTouchMajor(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historyPos);
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalTouchMinor(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historyPos);
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalToolMajor(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historyPos);
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalToolMinor(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historyPos);
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalOrientation(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historyPos);
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
/** \brief Handle the freeing of the GameActivityMotionEvent struct. */
|
||||
void GameActivityMotionEvent_destroy(GameActivityMotionEvent* c_event);
|
||||
|
||||
/**
|
||||
* \brief Convert a Java `MotionEvent` to a `GameActivityMotionEvent`.
|
||||
*
|
||||
* This is done automatically by the GameActivity: see `onTouchEvent` to set
|
||||
* a callback to consume the received events.
|
||||
* This function can be used if you re-implement events handling in your own
|
||||
* activity.
|
||||
* Ownership of out_event is maintained by the caller.
|
||||
*/
|
||||
void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityMotionEvent* out_event);
|
||||
|
||||
/**
|
||||
* \brief Describe a key event that happened on the GameActivity SurfaceView.
|
||||
*
|
||||
@@ -298,35 +286,23 @@ void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
* nanoseconds in this struct.
|
||||
*/
|
||||
typedef struct GameActivityKeyEvent {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
|
||||
int32_t modifiers;
|
||||
int32_t repeatCount;
|
||||
int32_t keyCode;
|
||||
int32_t scanCode;
|
||||
//int32_t unicodeChar;
|
||||
int32_t modifiers;
|
||||
int32_t repeatCount;
|
||||
int32_t keyCode;
|
||||
int32_t scanCode;
|
||||
// int32_t unicodeChar;
|
||||
} GameActivityKeyEvent;
|
||||
|
||||
/**
|
||||
* \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`.
|
||||
*
|
||||
* This is done automatically by the GameActivity: see `onKeyUp` and `onKeyDown`
|
||||
* to set a callback to consume the received events.
|
||||
* This function can be used if you re-implement events handling in your own
|
||||
* activity.
|
||||
* Ownership of out_event is maintained by the caller.
|
||||
*/
|
||||
void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityKeyEvent* out_event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+13
-13
@@ -26,7 +26,7 @@
|
||||
#define ALOGV(...)
|
||||
#else
|
||||
#define ALOGV(...) \
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
|
||||
#endif
|
||||
|
||||
/* Returns 2nd arg. Used to substitute default value if caller's vararg list
|
||||
@@ -40,21 +40,21 @@
|
||||
#define __android_rest(first, ...) , ##__VA_ARGS__
|
||||
|
||||
#define android_printAssert(cond, tag, fmt...) \
|
||||
__android_log_assert(cond, tag, \
|
||||
__android_second(0, ##fmt, NULL) __android_rest(fmt))
|
||||
__android_log_assert(cond, tag, \
|
||||
__android_second(0, ##fmt, NULL) __android_rest(fmt))
|
||||
|
||||
#define CONDITION(cond) (__builtin_expect((cond) != 0, 0))
|
||||
|
||||
#ifndef LOG_ALWAYS_FATAL_IF
|
||||
#define LOG_ALWAYS_FATAL_IF(cond, ...) \
|
||||
((CONDITION(cond)) \
|
||||
? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \
|
||||
: (void)0)
|
||||
#define LOG_ALWAYS_FATAL_IF(cond, ...) \
|
||||
((CONDITION(cond)) \
|
||||
? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \
|
||||
: (void)0)
|
||||
#endif
|
||||
|
||||
#ifndef LOG_ALWAYS_FATAL
|
||||
#define LOG_ALWAYS_FATAL(...) \
|
||||
(((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
|
||||
(((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -62,14 +62,14 @@
|
||||
*/
|
||||
#ifndef SLOGW
|
||||
#define SLOGW(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
|
||||
((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
|
||||
#endif
|
||||
|
||||
#ifndef SLOGW_IF
|
||||
#define SLOGW_IF(cond, ...) \
|
||||
((__predict_false(cond)) \
|
||||
? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
|
||||
: (void)0)
|
||||
#define SLOGW_IF(cond, ...) \
|
||||
((__predict_false(cond)) \
|
||||
? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
|
||||
: (void)0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
-732
@@ -1,732 +0,0 @@
|
||||
/*
|
||||
* 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 <assert.h>
|
||||
#include <errno.h>
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define NATIVE_APP_GLUE_MOTION_EVENTS_DEFAULT_BUF_SIZE 16
|
||||
#define NATIVE_APP_GLUE_KEY_EVENTS_DEFAULT_BUF_SIZE 4
|
||||
|
||||
#define LOGI(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
|
||||
#define LOGE(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__))
|
||||
#define LOGW(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_WARN, "threaded_app", __VA_ARGS__))
|
||||
#define LOGW_ONCE(...) \
|
||||
do { \
|
||||
static bool alogw_once##__FILE__##__LINE__##__ = true; \
|
||||
if (alogw_once##__FILE__##__LINE__##__) { \
|
||||
alogw_once##__FILE__##__LINE__##__ = false; \
|
||||
LOGW(__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* 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;
|
||||
int input_buf_idx = 0;
|
||||
|
||||
LOGV("android_app_entry called");
|
||||
android_app->config = AConfiguration_new();
|
||||
LOGV("android_app = %p", android_app);
|
||||
LOGV("config = %p", android_app->config);
|
||||
LOGV("activity = %p", android_app->activity);
|
||||
LOGV("assetmanager = %p", android_app->activity->assetManager);
|
||||
AConfiguration_fromAssetManager(android_app->config,
|
||||
android_app->activity->assetManager);
|
||||
|
||||
print_cur_config(android_app);
|
||||
|
||||
/* initialize event buffers */
|
||||
for (input_buf_idx = 0; input_buf_idx < NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; input_buf_idx++) {
|
||||
struct android_input_buffer *buf = &android_app->inputBuffers[input_buf_idx];
|
||||
|
||||
buf->motionEventsBufferSize = NATIVE_APP_GLUE_MOTION_EVENTS_DEFAULT_BUF_SIZE;
|
||||
buf->motionEvents = (GameActivityMotionEvent *) malloc(sizeof(GameActivityMotionEvent) *
|
||||
buf->motionEventsBufferSize);
|
||||
|
||||
buf->keyEventsBufferSize = NATIVE_APP_GLUE_KEY_EVENTS_DEFAULT_BUF_SIZE;
|
||||
buf->keyEvents = (GameActivityKeyEvent *) malloc(sizeof(GameActivityKeyEvent) *
|
||||
buf->keyEventsBufferSize);
|
||||
}
|
||||
|
||||
android_app->cmdPollSource.id = LOOPER_ID_MAIN;
|
||||
android_app->cmdPollSource.app = android_app;
|
||||
android_app->cmdPollSource.process = process_cmd;
|
||||
|
||||
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
|
||||
ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN,
|
||||
ALOOPER_EVENT_INPUT, NULL, &android_app->cmdPollSource);
|
||||
android_app->looper = looper;
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->running = 1;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
_rust_glue_entry(android_app);
|
||||
|
||||
android_app_destroy(android_app);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Codes from https://developer.android.com/reference/android/view/KeyEvent
|
||||
#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);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return;
|
||||
}
|
||||
if (android_app->pendingWindow != NULL) {
|
||||
android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW);
|
||||
}
|
||||
android_app->pendingWindow = window;
|
||||
if (window != NULL) {
|
||||
android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);
|
||||
}
|
||||
while (android_app->window != android_app->pendingWindow) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_set_activity_state(struct android_app* android_app,
|
||||
int8_t cmd) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (!android_app->destroyed) {
|
||||
android_app_write_cmd(android_app, cmd);
|
||||
while (android_app->activityState != cmd) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_free(struct android_app* android_app) {
|
||||
int input_buf_idx = 0;
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// It's possible that onDestroy is called after we have already 'destroyed'
|
||||
// the app (via `android_app_destroy` due to `android_main` returning.
|
||||
//
|
||||
// In this case `->destroyed` will already be set (so we won't deadlock in
|
||||
// the loop below) but we still need to close the messaging fds and finish
|
||||
// freeing the android_app
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_DESTROY);
|
||||
while (!android_app->destroyed) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
for (input_buf_idx = 0; input_buf_idx < NATIVE_APP_GLUE_MAX_INPUT_BUFFERS; input_buf_idx++) {
|
||||
struct android_input_buffer *buf = &android_app->inputBuffers[input_buf_idx];
|
||||
|
||||
android_app_clear_motion_events(buf);
|
||||
free(buf->motionEvents);
|
||||
free(buf->keyEvents);
|
||||
}
|
||||
|
||||
close(android_app->msgread);
|
||||
close(android_app->msgwrite);
|
||||
pthread_cond_destroy(&android_app->cond);
|
||||
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);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
android_app->stateSaved = 0;
|
||||
android_app_write_cmd(android_app, APP_CMD_SAVE_STATE);
|
||||
while (!android_app->stateSaved) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
|
||||
if (android_app->savedState != NULL) {
|
||||
// Tell the Java side about our state.
|
||||
recallback((const char*)android_app->savedState,
|
||||
android_app->savedStateSize, context);
|
||||
// Now we can free it.
|
||||
free(android_app->savedState);
|
||||
android_app->savedState = NULL;
|
||||
android_app->savedStateSize = 0;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void onPause(GameActivity* activity) {
|
||||
LOGV("Pause: %p", activity);
|
||||
android_app_set_activity_state(ToApp(activity), APP_CMD_PAUSE);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool android_app_input_available_wake_up(struct android_app* app) {
|
||||
pthread_mutex_lock(&app->mutex);
|
||||
bool available = app->inputAvailableWakeUp;
|
||||
app->inputAvailableWakeUp = false;
|
||||
pthread_mutex_unlock(&app->mutex);
|
||||
return available;
|
||||
}
|
||||
|
||||
// NB: should be called with the android_app->mutex held already
|
||||
static void notifyInput(struct android_app* android_app) {
|
||||
// Don't spam the mainloop with wake ups if we've already sent one
|
||||
if (android_app->inputSwapPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (android_app->looper != NULL) {
|
||||
// for the app thread to know why it received the wake() up
|
||||
android_app->inputAvailableWakeUp = true;
|
||||
android_app->inputSwapPending = true;
|
||||
ALooper_wake(android_app->looper);
|
||||
}
|
||||
}
|
||||
|
||||
static bool onTouchEvent(GameActivity* activity,
|
||||
const GameActivityMotionEvent* event) {
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (android_app->motionEventFilter != NULL &&
|
||||
!android_app->motionEventFilter(event)) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct android_input_buffer* inputBuffer =
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
|
||||
// Add to the list of active motion events
|
||||
if (inputBuffer->motionEventsCount >= inputBuffer->motionEventsBufferSize) {
|
||||
inputBuffer->motionEventsBufferSize *= 2;
|
||||
inputBuffer->motionEvents = (GameActivityMotionEvent *) realloc(inputBuffer->motionEvents,
|
||||
sizeof(GameActivityMotionEvent) * inputBuffer->motionEventsBufferSize);
|
||||
|
||||
if (inputBuffer->motionEvents == NULL) {
|
||||
LOGE("onTouchEvent: out of memory");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int new_ix = inputBuffer->motionEventsCount;
|
||||
memcpy(&inputBuffer->motionEvents[new_ix], event, sizeof(GameActivityMotionEvent));
|
||||
++inputBuffer->motionEventsCount;
|
||||
notifyInput(android_app);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
android_app->inputSwapPending = false;
|
||||
android_app->inputAvailableWakeUp = false;
|
||||
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
return inputBuffer;
|
||||
}
|
||||
|
||||
void android_app_clear_motion_events(struct android_input_buffer* inputBuffer) {
|
||||
// We do not need to lock here if the inputBuffer has already been swapped
|
||||
// as is handled by the game loop thread
|
||||
while (inputBuffer->motionEventsCount > 0) {
|
||||
GameActivityMotionEvent_destroy(
|
||||
&inputBuffer->motionEvents[inputBuffer->motionEventsCount - 1]);
|
||||
|
||||
inputBuffer->motionEventsCount--;
|
||||
}
|
||||
assert(inputBuffer->motionEventsCount == 0);
|
||||
}
|
||||
|
||||
void android_app_set_key_event_filter(struct android_app* app,
|
||||
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);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (android_app->keyEventFilter != NULL &&
|
||||
!android_app->keyEventFilter(event)) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct android_input_buffer* inputBuffer =
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
|
||||
// Add to the list of active key down events
|
||||
if (inputBuffer->keyEventsCount >= inputBuffer->keyEventsBufferSize) {
|
||||
inputBuffer->keyEventsBufferSize = inputBuffer->keyEventsBufferSize * 2;
|
||||
inputBuffer->keyEvents = (GameActivityKeyEvent *) realloc(inputBuffer->keyEvents,
|
||||
sizeof(GameActivityKeyEvent) * inputBuffer->keyEventsBufferSize);
|
||||
|
||||
if (inputBuffer->keyEvents == NULL) {
|
||||
LOGE("onKey: out of memory");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int new_ix = inputBuffer->keyEventsCount;
|
||||
memcpy(&inputBuffer->keyEvents[new_ix], event, sizeof(GameActivityKeyEvent));
|
||||
++inputBuffer->keyEventsCount;
|
||||
notifyInput(android_app);
|
||||
|
||||
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);
|
||||
if (!android_app->destroyed) {
|
||||
android_app->textInputState = 1;
|
||||
notifyInput(android_app);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void onWindowInsetsChanged(GameActivity* activity) {
|
||||
LOGV("WindowInsetsChanged: %p", activity);
|
||||
android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_INSETS_CHANGED);
|
||||
}
|
||||
|
||||
static void onContentRectChanged(GameActivity* activity, const ARect *rect) {
|
||||
LOGV("ContentRectChanged: %p -- (%d %d) (%d %d)", activity, rect->left, rect->top,
|
||||
rect->right, rect->bottom);
|
||||
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->contentRect = *rect;
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_CONTENT_RECT_CHANGED);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
// XXX: This symbol is renamed with a _C suffix and then re-exported from
|
||||
// Rust because Rust/Cargo don't give us a way to directly export symbols
|
||||
// from C/C++ code: https://github.com/rust-lang/rfcs/issues/2771
|
||||
//
|
||||
JNIEXPORT
|
||||
void GameActivity_onCreate_C(GameActivity* activity, void* savedState,
|
||||
size_t savedStateSize) {
|
||||
LOGV("Creating: %p", activity);
|
||||
activity->callbacks->onDestroy = onDestroy;
|
||||
activity->callbacks->onStart = onStart;
|
||||
activity->callbacks->onResume = onResume;
|
||||
activity->callbacks->onSaveInstanceState = onSaveInstanceState;
|
||||
activity->callbacks->onPause = onPause;
|
||||
activity->callbacks->onStop = onStop;
|
||||
activity->callbacks->onTouchEvent = onTouchEvent;
|
||||
activity->callbacks->onKeyDown = onKey;
|
||||
activity->callbacks->onKeyUp = onKey;
|
||||
activity->callbacks->onTextInputEvent = onTextInputEvent;
|
||||
activity->callbacks->onConfigurationChanged = onConfigurationChanged;
|
||||
activity->callbacks->onTrimMemory = onTrimMemory;
|
||||
activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
|
||||
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
|
||||
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
|
||||
activity->callbacks->onNativeWindowRedrawNeeded =
|
||||
onNativeWindowRedrawNeeded;
|
||||
activity->callbacks->onNativeWindowResized = onNativeWindowResized;
|
||||
activity->callbacks->onWindowInsetsChanged = onWindowInsetsChanged;
|
||||
activity->callbacks->onContentRectChanged = onContentRectChanged;
|
||||
LOGV("Callbacks set: %p", activity->callbacks);
|
||||
|
||||
activity->instance =
|
||||
android_app_create(activity, savedState, savedStateSize);
|
||||
}
|
||||
+284
-241
@@ -27,6 +27,7 @@
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "game-activity/GameActivity.h"
|
||||
|
||||
@@ -86,55 +87,54 @@ struct android_app;
|
||||
* when that source has data ready.
|
||||
*/
|
||||
struct android_poll_source {
|
||||
/**
|
||||
* The identifier of this source. May be LOOPER_ID_MAIN or
|
||||
* LOOPER_ID_INPUT.
|
||||
*/
|
||||
int32_t id;
|
||||
/**
|
||||
* The 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;
|
||||
/** 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);
|
||||
/**
|
||||
* Function to call to perform the standard processing of data from
|
||||
* this source.
|
||||
*/
|
||||
void (*process)(struct android_app* app, struct android_poll_source* source);
|
||||
};
|
||||
|
||||
struct android_input_buffer {
|
||||
/**
|
||||
* Pointer to a read-only array of GameActivityMotionEvent.
|
||||
* Only the first motionEventsCount events are valid.
|
||||
*/
|
||||
GameActivityMotionEvent *motionEvents;
|
||||
/**
|
||||
* Pointer to a read-only array of GameActivityMotionEvent.
|
||||
* Only the first motionEventsCount events are valid.
|
||||
*/
|
||||
GameActivityMotionEvent* motionEvents;
|
||||
|
||||
/**
|
||||
* The number of valid motion events in `motionEvents`.
|
||||
*/
|
||||
uint64_t motionEventsCount;
|
||||
/**
|
||||
* The number of valid motion events in `motionEvents`.
|
||||
*/
|
||||
uint64_t motionEventsCount;
|
||||
|
||||
/**
|
||||
* The size of the `motionEvents` buffer.
|
||||
*/
|
||||
uint64_t motionEventsBufferSize;
|
||||
/**
|
||||
* The size of the `motionEvents` buffer.
|
||||
*/
|
||||
uint64_t motionEventsBufferSize;
|
||||
|
||||
/**
|
||||
* Pointer to a read-only array of GameActivityKeyEvent.
|
||||
* Only the first keyEventsCount events are valid.
|
||||
*/
|
||||
GameActivityKeyEvent *keyEvents;
|
||||
/**
|
||||
* Pointer to a read-only array of GameActivityKeyEvent.
|
||||
* Only the first keyEventsCount events are valid.
|
||||
*/
|
||||
GameActivityKeyEvent* keyEvents;
|
||||
|
||||
/**
|
||||
* The number of valid "Key" events in `keyEvents`.
|
||||
*/
|
||||
uint64_t keyEventsCount;
|
||||
/**
|
||||
* The number of valid "Key" events in `keyEvents`.
|
||||
*/
|
||||
uint64_t keyEventsCount;
|
||||
|
||||
/**
|
||||
* The size of the `keyEvents` buffer.
|
||||
*/
|
||||
uint64_t keyEventsBufferSize;
|
||||
/**
|
||||
* The size of the `keyEvents` buffer.
|
||||
*/
|
||||
uint64_t keyEventsBufferSize;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -164,261 +164,294 @@ typedef bool (*android_motion_event_filter)(const GameActivityMotionEvent*);
|
||||
* Java objects.
|
||||
*/
|
||||
struct android_app {
|
||||
/**
|
||||
* An optional pointer to application-defined state.
|
||||
*/
|
||||
void* userData;
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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 GameActivity object instance that this app is running in. */
|
||||
GameActivity* activity;
|
||||
|
||||
/** The current configuration the app is running in. */
|
||||
AConfiguration* config;
|
||||
/** 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 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 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;
|
||||
/** 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;
|
||||
/** 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 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;
|
||||
/**
|
||||
* Whether the software keyboard is visible or not.
|
||||
*/
|
||||
bool softwareKeyboardVisible;
|
||||
|
||||
/**
|
||||
* This is non-zero when the application's GameActivity is being
|
||||
* destroyed and waiting for the app thread to complete.
|
||||
*/
|
||||
int destroyRequested;
|
||||
/**
|
||||
* Last editor action. Valid within APP_CMD_SOFTWARE_KB_VIS_CHANGED handler.
|
||||
*/
|
||||
int editorAction;
|
||||
|
||||
/**
|
||||
* Current state of the app's activity. May be either APP_CMD_START,
|
||||
* APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP.
|
||||
*/
|
||||
int activityState;
|
||||
|
||||
/**
|
||||
* This is non-zero when the application's GameActivity is being
|
||||
* destroyed and waiting for the app thread to complete.
|
||||
*/
|
||||
int destroyRequested;
|
||||
|
||||
#define NATIVE_APP_GLUE_MAX_INPUT_BUFFERS 2
|
||||
|
||||
/**
|
||||
* This is used for buffering input from GameActivity. Once ready, the
|
||||
* application thread switches the buffers and processes what was
|
||||
* accumulated.
|
||||
*/
|
||||
struct android_input_buffer inputBuffers[NATIVE_APP_GLUE_MAX_INPUT_BUFFERS];
|
||||
/**
|
||||
* 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;
|
||||
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;
|
||||
/**
|
||||
* 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 */
|
||||
// Below are "private" implementation of the glue code.
|
||||
/** @cond INTERNAL */
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
|
||||
int msgread;
|
||||
int msgwrite;
|
||||
int msgread;
|
||||
int msgwrite;
|
||||
|
||||
pthread_t thread;
|
||||
pthread_t thread;
|
||||
|
||||
struct android_poll_source cmdPollSource;
|
||||
struct android_poll_source cmdPollSource;
|
||||
|
||||
int running;
|
||||
int stateSaved;
|
||||
int destroyed;
|
||||
int redrawNeeded;
|
||||
ANativeWindow* pendingWindow;
|
||||
ARect pendingContentRect;
|
||||
int running;
|
||||
int stateSaved;
|
||||
int destroyed;
|
||||
int redrawNeeded;
|
||||
ANativeWindow* pendingWindow;
|
||||
ARect pendingContentRect;
|
||||
|
||||
android_key_event_filter keyEventFilter;
|
||||
android_motion_event_filter motionEventFilter;
|
||||
android_key_event_filter keyEventFilter;
|
||||
android_motion_event_filter motionEventFilter;
|
||||
|
||||
// When new input is received we set both of these flags and use the looper to
|
||||
// wake up the application mainloop.
|
||||
//
|
||||
// To avoid spamming the mainloop with wake ups from lots of input though we
|
||||
// don't sent a wake up if the inputSwapPending flag is already set. (i.e.
|
||||
// we already expect input to be processed in a finite amount of time due to
|
||||
// our previous wake up)
|
||||
//
|
||||
// When a wake up is received then we will check this flag (clearing it
|
||||
// at the same time). If it was set then an InputAvailable event is sent to
|
||||
// the application - which should lead to all input being processed within
|
||||
// a finite amount of time.
|
||||
//
|
||||
// The next time android_app_swap_input_buffers is called, both flags will be
|
||||
// cleared.
|
||||
//
|
||||
// NB: both of these should only be read with the app mutex held
|
||||
bool inputAvailableWakeUp;
|
||||
bool inputSwapPending;
|
||||
// When new input is received we set both of these flags and use the looper to
|
||||
// wake up the application mainloop.
|
||||
//
|
||||
// To avoid spamming the mainloop with wake ups from lots of input though we
|
||||
// don't sent a wake up if the inputSwapPending flag is already set. (i.e.
|
||||
// we already expect input to be processed in a finite amount of time due to
|
||||
// our previous wake up)
|
||||
//
|
||||
// When a wake up is received then we will check this flag (clearing it
|
||||
// at the same time). If it was set then an InputAvailable event is sent to
|
||||
// the application - which should lead to all input being processed within
|
||||
// a finite amount of time.
|
||||
//
|
||||
// The next time android_app_swap_input_buffers is called, both flags will be
|
||||
// cleared.
|
||||
//
|
||||
// NB: both of these should only be read with the app mutex held
|
||||
bool inputAvailableWakeUp;
|
||||
bool inputSwapPending;
|
||||
|
||||
/** @endcond */
|
||||
/** @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,
|
||||
enum NativeAppGlueLooperId : int8_t {
|
||||
/**
|
||||
* Looper data ID of commands coming from the app's main thread, which
|
||||
* is returned as an identifier from ALooper_pollOnce(). The data for this
|
||||
* identifier is a pointer to an android_poll_source structure.
|
||||
* These can be retrieved and processed with android_app_read_cmd()
|
||||
* and android_app_exec_cmd().
|
||||
*/
|
||||
LOOPER_ID_MAIN = 1,
|
||||
|
||||
/**
|
||||
* Unused. Reserved for future use when usage of AInputQueue will be
|
||||
* supported.
|
||||
*/
|
||||
LOOPER_ID_INPUT = 2,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* Start of user-defined ALooper identifiers.
|
||||
*/
|
||||
LOOPER_ID_USER = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Commands passed from the application's main Java thread to the game's thread.
|
||||
*
|
||||
* Values from 0 to 127 are reserved for this library; values from -128 to -1
|
||||
* can be used for custom user's events.
|
||||
*/
|
||||
enum NativeAppGlueAppCmd {
|
||||
/**
|
||||
* Unused. Reserved for future use when usage of AInputQueue will be
|
||||
* supported.
|
||||
*/
|
||||
UNUSED_APP_CMD_INPUT_CHANGED,
|
||||
enum NativeAppGlueAppCmd : int8_t {
|
||||
/**
|
||||
* Unused. Reserved for future use when usage of AInputQueue will be
|
||||
* supported.
|
||||
*/
|
||||
UNUSED_APP_CMD_INPUT_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: a new ANativeWindow is ready for use. Upon
|
||||
* receiving this command, android_app->window will contain the new window
|
||||
* surface.
|
||||
*/
|
||||
APP_CMD_INIT_WINDOW,
|
||||
/**
|
||||
* Command from main thread: 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 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 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 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 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 software keyboard was shown or hidden.
|
||||
*/
|
||||
APP_CMD_SOFTWARE_KB_VIS_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity window has lost
|
||||
* input focus.
|
||||
*/
|
||||
APP_CMD_LOST_FOCUS,
|
||||
/**
|
||||
* Command from main thread: the app's activity window has gained
|
||||
* input focus.
|
||||
*/
|
||||
APP_CMD_GAINED_FOCUS,
|
||||
|
||||
/**
|
||||
* Command from main thread: the current device configuration has changed.
|
||||
*/
|
||||
APP_CMD_CONFIG_CHANGED,
|
||||
/**
|
||||
* Command from main thread: the app's activity window has lost
|
||||
* input focus.
|
||||
*/
|
||||
APP_CMD_LOST_FOCUS,
|
||||
|
||||
/**
|
||||
* 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 current device configuration has changed.
|
||||
*/
|
||||
APP_CMD_CONFIG_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been started.
|
||||
*/
|
||||
APP_CMD_START,
|
||||
/**
|
||||
* 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 resumed.
|
||||
*/
|
||||
APP_CMD_RESUME,
|
||||
/**
|
||||
* Command from main thread: the app's activity has been started.
|
||||
*/
|
||||
APP_CMD_START,
|
||||
|
||||
/**
|
||||
* 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 resumed.
|
||||
*/
|
||||
APP_CMD_RESUME,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been paused.
|
||||
*/
|
||||
APP_CMD_PAUSE,
|
||||
/**
|
||||
* 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 stopped.
|
||||
*/
|
||||
APP_CMD_STOP,
|
||||
/**
|
||||
* Command from main thread: the app's activity has been paused.
|
||||
*/
|
||||
APP_CMD_PAUSE,
|
||||
|
||||
/**
|
||||
* 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 activity has been stopped.
|
||||
*/
|
||||
APP_CMD_STOP,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's insets have changed.
|
||||
*/
|
||||
APP_CMD_WINDOW_INSETS_CHANGED,
|
||||
/**
|
||||
* Command from main thread: the app's activity is being destroyed,
|
||||
* and waiting for the app thread to clean up and exit before proceeding.
|
||||
*/
|
||||
APP_CMD_DESTROY,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's insets have changed.
|
||||
*/
|
||||
APP_CMD_WINDOW_INSETS_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: an editor action has been triggered.
|
||||
*/
|
||||
APP_CMD_EDITOR_ACTION,
|
||||
|
||||
/**
|
||||
* Command from main thread: a keyboard event has been received.
|
||||
*/
|
||||
APP_CMD_KEY_EVENT,
|
||||
|
||||
/**
|
||||
* Command from main thread: a touch event has been received.
|
||||
*/
|
||||
APP_CMD_TOUCH_EVENT,
|
||||
|
||||
};
|
||||
|
||||
@@ -495,6 +528,16 @@ void android_app_set_key_event_filter(struct android_app* app,
|
||||
void android_app_set_motion_event_filter(struct android_app* app,
|
||||
android_motion_event_filter filter);
|
||||
|
||||
/**
|
||||
* You can send your custom events using the function below.
|
||||
*
|
||||
* Make sure your custom codes do not overlap with this library's ones.
|
||||
*
|
||||
* Values from 0 to 127 are reserved for this library; values from -128 to -1
|
||||
* can be used for custom user's events.
|
||||
*/
|
||||
void android_app_write_cmd(struct android_app* android_app, int8_t cmd);
|
||||
|
||||
/**
|
||||
* Determines if a looper wake up was due to new input becoming available
|
||||
*/
|
||||
|
||||
+1399
File diff suppressed because it is too large
Load Diff
+335
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <game-activity/GameActivityEvents.h>
|
||||
#include <game-activity/GameActivityLog.h>
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "GameActivityEvents_internal.h"
|
||||
#include "system_utils.h"
|
||||
|
||||
static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = {
|
||||
/* AMOTION_EVENT_AXIS_X */ true,
|
||||
/* AMOTION_EVENT_AXIS_Y */ true,
|
||||
// Disable all other axes by default (they can be enabled using
|
||||
// `GameActivityPointerAxes_enableAxis`).
|
||||
false};
|
||||
|
||||
extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabledAxes[axis] = true;
|
||||
}
|
||||
|
||||
float GameActivityPointerAxes_getAxisValue(
|
||||
const GameActivityPointerAxes *pointerInfo, int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!enabledAxes[axis]) {
|
||||
ALOGW("Axis %d must be enabled before it can be accessed.", axis);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return pointerInfo->axisValues[axis];
|
||||
}
|
||||
|
||||
extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabledAxes[axis] = false;
|
||||
}
|
||||
|
||||
float GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
const GameActivityMotionEvent *event, int axis, int pointerIndex,
|
||||
int historyPos) {
|
||||
if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
|
||||
ALOGE("Invalid axis %d", axis);
|
||||
return -1;
|
||||
}
|
||||
if (pointerIndex < 0 || pointerIndex >= event->pointerCount) {
|
||||
ALOGE("Invalid pointer index %d", pointerIndex);
|
||||
return -1;
|
||||
}
|
||||
if (historyPos < 0 || historyPos >= event->historySize) {
|
||||
ALOGE("Invalid history index %d", historyPos);
|
||||
return -1;
|
||||
}
|
||||
if (!enabledAxes[axis]) {
|
||||
ALOGW("Axis %d must be enabled before it can be accessed.", axis);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pointerOffset = pointerIndex * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
int historyValuesOffset =
|
||||
historyPos * event->pointerCount * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
return event
|
||||
->historicalAxisValues[historyValuesOffset + pointerOffset + axis];
|
||||
}
|
||||
|
||||
static struct {
|
||||
jmethodID getDeviceId;
|
||||
jmethodID getSource;
|
||||
jmethodID getAction;
|
||||
|
||||
jmethodID getEventTime;
|
||||
jmethodID getDownTime;
|
||||
|
||||
jmethodID getFlags;
|
||||
jmethodID getMetaState;
|
||||
|
||||
jmethodID getActionButton;
|
||||
jmethodID getButtonState;
|
||||
jmethodID getClassification;
|
||||
jmethodID getEdgeFlags;
|
||||
|
||||
jmethodID getHistorySize;
|
||||
jmethodID getHistoricalEventTime;
|
||||
|
||||
jmethodID getPointerCount;
|
||||
jmethodID getPointerId;
|
||||
|
||||
jmethodID getToolType;
|
||||
|
||||
jmethodID getRawX;
|
||||
jmethodID getRawY;
|
||||
jmethodID getXPrecision;
|
||||
jmethodID getYPrecision;
|
||||
jmethodID getAxisValue;
|
||||
|
||||
jmethodID getHistoricalAxisValue;
|
||||
} gMotionEventClassInfo;
|
||||
|
||||
extern "C" void GameActivityMotionEvent_destroy(
|
||||
GameActivityMotionEvent *c_event) {
|
||||
delete c_event->historicalAxisValues;
|
||||
delete c_event->historicalEventTimesMillis;
|
||||
delete c_event->historicalEventTimesNanos;
|
||||
}
|
||||
|
||||
static void initMotionEvents(JNIEnv *env) {
|
||||
int sdkVersion = gamesdk::GetSystemPropAsInt("ro.build.version.sdk");
|
||||
gMotionEventClassInfo = {0};
|
||||
jclass motionEventClass = env->FindClass("android/view/MotionEvent");
|
||||
gMotionEventClassInfo.getDeviceId =
|
||||
env->GetMethodID(motionEventClass, "getDeviceId", "()I");
|
||||
gMotionEventClassInfo.getSource =
|
||||
env->GetMethodID(motionEventClass, "getSource", "()I");
|
||||
gMotionEventClassInfo.getAction =
|
||||
env->GetMethodID(motionEventClass, "getAction", "()I");
|
||||
gMotionEventClassInfo.getEventTime =
|
||||
env->GetMethodID(motionEventClass, "getEventTime", "()J");
|
||||
gMotionEventClassInfo.getDownTime =
|
||||
env->GetMethodID(motionEventClass, "getDownTime", "()J");
|
||||
gMotionEventClassInfo.getFlags =
|
||||
env->GetMethodID(motionEventClass, "getFlags", "()I");
|
||||
gMotionEventClassInfo.getMetaState =
|
||||
env->GetMethodID(motionEventClass, "getMetaState", "()I");
|
||||
if (sdkVersion >= 23) {
|
||||
gMotionEventClassInfo.getActionButton =
|
||||
env->GetMethodID(motionEventClass, "getActionButton", "()I");
|
||||
}
|
||||
if (sdkVersion >= 14) {
|
||||
gMotionEventClassInfo.getButtonState =
|
||||
env->GetMethodID(motionEventClass, "getButtonState", "()I");
|
||||
}
|
||||
if (sdkVersion >= 29) {
|
||||
gMotionEventClassInfo.getClassification =
|
||||
env->GetMethodID(motionEventClass, "getClassification", "()I");
|
||||
}
|
||||
gMotionEventClassInfo.getEdgeFlags =
|
||||
env->GetMethodID(motionEventClass, "getEdgeFlags", "()I");
|
||||
|
||||
gMotionEventClassInfo.getHistorySize =
|
||||
env->GetMethodID(motionEventClass, "getHistorySize", "()I");
|
||||
gMotionEventClassInfo.getHistoricalEventTime =
|
||||
env->GetMethodID(motionEventClass, "getHistoricalEventTime", "(I)J");
|
||||
|
||||
gMotionEventClassInfo.getPointerCount =
|
||||
env->GetMethodID(motionEventClass, "getPointerCount", "()I");
|
||||
gMotionEventClassInfo.getPointerId =
|
||||
env->GetMethodID(motionEventClass, "getPointerId", "(I)I");
|
||||
gMotionEventClassInfo.getToolType =
|
||||
env->GetMethodID(motionEventClass, "getToolType", "(I)I");
|
||||
if (sdkVersion >= 29) {
|
||||
gMotionEventClassInfo.getRawX =
|
||||
env->GetMethodID(motionEventClass, "getRawX", "(I)F");
|
||||
gMotionEventClassInfo.getRawY =
|
||||
env->GetMethodID(motionEventClass, "getRawY", "(I)F");
|
||||
}
|
||||
gMotionEventClassInfo.getXPrecision =
|
||||
env->GetMethodID(motionEventClass, "getXPrecision", "()F");
|
||||
gMotionEventClassInfo.getYPrecision =
|
||||
env->GetMethodID(motionEventClass, "getYPrecision", "()F");
|
||||
gMotionEventClassInfo.getAxisValue =
|
||||
env->GetMethodID(motionEventClass, "getAxisValue", "(II)F");
|
||||
|
||||
gMotionEventClassInfo.getHistoricalAxisValue =
|
||||
env->GetMethodID(motionEventClass, "getHistoricalAxisValue", "(III)F");
|
||||
}
|
||||
|
||||
extern "C" void GameActivityMotionEvent_fromJava(
|
||||
JNIEnv *env, jobject motionEvent, GameActivityMotionEvent *out_event,
|
||||
int pointerCount, int historySize) {
|
||||
pointerCount =
|
||||
std::min(pointerCount, GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT);
|
||||
out_event->pointerCount = pointerCount;
|
||||
for (int i = 0; i < pointerCount; ++i) {
|
||||
out_event->pointers[i] = {
|
||||
/*id=*/env->CallIntMethod(motionEvent,
|
||||
gMotionEventClassInfo.getPointerId, i),
|
||||
/*toolType=*/
|
||||
env->CallIntMethod(motionEvent, gMotionEventClassInfo.getToolType, i),
|
||||
/*axisValues=*/{0},
|
||||
/*rawX=*/gMotionEventClassInfo.getRawX
|
||||
? env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getRawX,
|
||||
i)
|
||||
: 0,
|
||||
/*rawY=*/gMotionEventClassInfo.getRawY
|
||||
? env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getRawY,
|
||||
i)
|
||||
: 0,
|
||||
};
|
||||
|
||||
for (int axisIndex = 0; axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
++axisIndex) {
|
||||
if (enabledAxes[axisIndex]) {
|
||||
out_event->pointers[i].axisValues[axisIndex] = env->CallFloatMethod(
|
||||
motionEvent, gMotionEventClassInfo.getAxisValue, axisIndex, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out_event->historySize = historySize;
|
||||
out_event->historicalAxisValues =
|
||||
new float[historySize * pointerCount *
|
||||
GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
|
||||
out_event->historicalEventTimesMillis = new int64_t[historySize];
|
||||
out_event->historicalEventTimesNanos = new int64_t[historySize];
|
||||
|
||||
for (int historyIndex = 0; historyIndex < historySize; historyIndex++) {
|
||||
out_event->historicalEventTimesMillis[historyIndex] = env->CallLongMethod(
|
||||
motionEvent, gMotionEventClassInfo.getHistoricalEventTime,
|
||||
historyIndex);
|
||||
out_event->historicalEventTimesNanos[historyIndex] =
|
||||
out_event->historicalEventTimesMillis[historyIndex] * 1000000;
|
||||
for (int i = 0; i < pointerCount; ++i) {
|
||||
int pointerOffset = i * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
int historyAxisOffset =
|
||||
historyIndex * pointerCount * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
float *axisValues =
|
||||
&out_event->historicalAxisValues[historyAxisOffset + pointerOffset];
|
||||
for (int axisIndex = 0; axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
|
||||
++axisIndex) {
|
||||
if (enabledAxes[axisIndex]) {
|
||||
axisValues[axisIndex] = env->CallFloatMethod(
|
||||
motionEvent, gMotionEventClassInfo.getHistoricalAxisValue,
|
||||
axisIndex, i, historyIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct {
|
||||
jmethodID getDeviceId;
|
||||
jmethodID getSource;
|
||||
jmethodID getAction;
|
||||
|
||||
jmethodID getEventTime;
|
||||
jmethodID getDownTime;
|
||||
|
||||
jmethodID getFlags;
|
||||
jmethodID getMetaState;
|
||||
|
||||
jmethodID getModifiers;
|
||||
jmethodID getRepeatCount;
|
||||
jmethodID getKeyCode;
|
||||
jmethodID getScanCode;
|
||||
// jmethodID getUnicodeChar;
|
||||
} gKeyEventClassInfo;
|
||||
|
||||
static void initKeyEvents(JNIEnv *env) {
|
||||
int sdkVersion = gamesdk::GetSystemPropAsInt("ro.build.version.sdk");
|
||||
gKeyEventClassInfo = {0};
|
||||
jclass keyEventClass = env->FindClass("android/view/KeyEvent");
|
||||
gKeyEventClassInfo.getDeviceId =
|
||||
env->GetMethodID(keyEventClass, "getDeviceId", "()I");
|
||||
gKeyEventClassInfo.getSource =
|
||||
env->GetMethodID(keyEventClass, "getSource", "()I");
|
||||
gKeyEventClassInfo.getAction =
|
||||
env->GetMethodID(keyEventClass, "getAction", "()I");
|
||||
gKeyEventClassInfo.getEventTime =
|
||||
env->GetMethodID(keyEventClass, "getEventTime", "()J");
|
||||
gKeyEventClassInfo.getDownTime =
|
||||
env->GetMethodID(keyEventClass, "getDownTime", "()J");
|
||||
gKeyEventClassInfo.getFlags =
|
||||
env->GetMethodID(keyEventClass, "getFlags", "()I");
|
||||
gKeyEventClassInfo.getMetaState =
|
||||
env->GetMethodID(keyEventClass, "getMetaState", "()I");
|
||||
if (sdkVersion >= 13) {
|
||||
gKeyEventClassInfo.getModifiers =
|
||||
env->GetMethodID(keyEventClass, "getModifiers", "()I");
|
||||
}
|
||||
gKeyEventClassInfo.getRepeatCount =
|
||||
env->GetMethodID(keyEventClass, "getRepeatCount", "()I");
|
||||
gKeyEventClassInfo.getKeyCode =
|
||||
env->GetMethodID(keyEventClass, "getKeyCode", "()I");
|
||||
gKeyEventClassInfo.getScanCode =
|
||||
env->GetMethodID(keyEventClass, "getScanCode", "()I");
|
||||
//gKeyEventClassInfo.getUnicodeChar =
|
||||
//env->GetMethodID(keyEventClass, "getUnicodeChar", "()I");
|
||||
}
|
||||
|
||||
extern "C" void GameActivityKeyEvent_fromJava(JNIEnv *env, jobject keyEvent,
|
||||
GameActivityKeyEvent *out_event) {
|
||||
*out_event = {
|
||||
/*deviceId=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getDeviceId),
|
||||
/*source=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getSource),
|
||||
/*action=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getAction),
|
||||
// TODO: introduce a millisecondsToNanoseconds helper:
|
||||
/*eventTime=*/
|
||||
env->CallLongMethod(keyEvent, gKeyEventClassInfo.getEventTime) * 1000000,
|
||||
/*downTime=*/
|
||||
env->CallLongMethod(keyEvent, gKeyEventClassInfo.getDownTime) * 1000000,
|
||||
/*flags=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getFlags),
|
||||
/*metaState=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getMetaState),
|
||||
/*modifiers=*/gKeyEventClassInfo.getModifiers
|
||||
? env->CallIntMethod(keyEvent, gKeyEventClassInfo.getModifiers)
|
||||
: 0,
|
||||
/*repeatCount=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getRepeatCount),
|
||||
/*keyCode=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getKeyCode),
|
||||
/*scanCode=*/
|
||||
env->CallIntMethod(keyEvent, gKeyEventClassInfo.getScanCode)
|
||||
/*unicodeChar=*/
|
||||
// env->CallIntMethod(keyEvent, gKeyEventClassInfo.getUnicodeChar)
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" void GameActivityEventsInit(JNIEnv *env) {
|
||||
initMotionEvents(env);
|
||||
initKeyEvents(env);
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup GameActivity Game Activity Events Internal
|
||||
* These functions are internal details of Game Activity Events.
|
||||
* Please do not rely on anything in this file as this can be changed
|
||||
* without notice.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file GameActivityEvents_internal.h
|
||||
*/
|
||||
#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_INTERNAL_H
|
||||
#define ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_INTERNAL_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \brief Performs necessary initialization steps for GameActivityEvents.
|
||||
*
|
||||
* User must call this function before calling any other functions of this unit.
|
||||
* If you use GameActivity it will call this function for you.
|
||||
*/
|
||||
void GameActivityEventsInit(JNIEnv* env);
|
||||
|
||||
/**
|
||||
* \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.
|
||||
* Note that we pass as much information from Java Activity as possible
|
||||
* to avoid extra JNI calls.
|
||||
*/
|
||||
void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityMotionEvent* out_event,
|
||||
int pointerCount, int historySize);
|
||||
|
||||
/**
|
||||
* \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`.
|
||||
*
|
||||
* This is done automatically by the GameActivity: see `onKeyUp` and `onKeyDown`
|
||||
* to set a callback to consume the received events.
|
||||
* This function can be used if you re-implement events handling in your own
|
||||
* activity.
|
||||
* Ownership of out_event is maintained by the caller.
|
||||
*/
|
||||
void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityKeyEvent* out_event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_INTERNAL_H
|
||||
+765
@@ -0,0 +1,765 @@
|
||||
/*
|
||||
* 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-activity/native_app_glue/android_native_app_glue.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define NATIVE_APP_GLUE_MOTION_EVENTS_DEFAULT_BUF_SIZE 16
|
||||
#define NATIVE_APP_GLUE_KEY_EVENTS_DEFAULT_BUF_SIZE 4
|
||||
|
||||
#define LOGI(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
|
||||
#define LOGE(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__))
|
||||
#define LOGW(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_WARN, "threaded_app", __VA_ARGS__))
|
||||
#define LOGW_ONCE(...) \
|
||||
do { \
|
||||
static bool alogw_once##__FILE__##__LINE__##__ = true; \
|
||||
if (alogw_once##__FILE__##__LINE__##__) { \
|
||||
alogw_once##__FILE__##__LINE__##__ = false; \
|
||||
LOGW(__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* 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;
|
||||
int input_buf_idx = 0;
|
||||
|
||||
LOGV("android_app_entry called");
|
||||
android_app->config = AConfiguration_new();
|
||||
LOGV("android_app = %p", android_app);
|
||||
LOGV("config = %p", android_app->config);
|
||||
LOGV("activity = %p", android_app->activity);
|
||||
LOGV("assetmanager = %p", android_app->activity->assetManager);
|
||||
AConfiguration_fromAssetManager(android_app->config,
|
||||
android_app->activity->assetManager);
|
||||
|
||||
print_cur_config(android_app);
|
||||
|
||||
/* initialize event buffers */
|
||||
for (input_buf_idx = 0; input_buf_idx < NATIVE_APP_GLUE_MAX_INPUT_BUFFERS;
|
||||
input_buf_idx++) {
|
||||
struct android_input_buffer* buf =
|
||||
&android_app->inputBuffers[input_buf_idx];
|
||||
|
||||
buf->motionEventsBufferSize =
|
||||
NATIVE_APP_GLUE_MOTION_EVENTS_DEFAULT_BUF_SIZE;
|
||||
buf->motionEvents = (GameActivityMotionEvent*)malloc(
|
||||
sizeof(GameActivityMotionEvent) * buf->motionEventsBufferSize);
|
||||
|
||||
buf->keyEventsBufferSize = NATIVE_APP_GLUE_KEY_EVENTS_DEFAULT_BUF_SIZE;
|
||||
buf->keyEvents = (GameActivityKeyEvent*)malloc(
|
||||
sizeof(GameActivityKeyEvent) * buf->keyEventsBufferSize);
|
||||
}
|
||||
|
||||
android_app->cmdPollSource.id = LOOPER_ID_MAIN;
|
||||
android_app->cmdPollSource.app = android_app;
|
||||
android_app->cmdPollSource.process = process_cmd;
|
||||
|
||||
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
|
||||
ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN,
|
||||
ALOOPER_EVENT_INPUT, NULL, &android_app->cmdPollSource);
|
||||
android_app->looper = looper;
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->running = 1;
|
||||
pthread_cond_broadcast(&android_app->cond);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
_rust_glue_entry(android_app);
|
||||
|
||||
android_app_destroy(android_app);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Codes from https://developer.android.com/reference/android/view/KeyEvent
|
||||
#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) != 0;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
|
||||
void android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
|
||||
if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
|
||||
LOGE("Failure writing android_app cmd: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void android_app_set_window(struct android_app* android_app,
|
||||
ANativeWindow* window) {
|
||||
LOGV("android_app_set_window called");
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return;
|
||||
}
|
||||
if (android_app->pendingWindow != NULL) {
|
||||
android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW);
|
||||
}
|
||||
android_app->pendingWindow = window;
|
||||
if (window != NULL) {
|
||||
android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW);
|
||||
}
|
||||
while (android_app->window != android_app->pendingWindow) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_set_activity_state(struct android_app* android_app,
|
||||
int8_t cmd) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (!android_app->destroyed) {
|
||||
android_app_write_cmd(android_app, cmd);
|
||||
while (android_app->activityState != cmd) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void android_app_free(struct android_app* android_app) {
|
||||
int input_buf_idx = 0;
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// It's possible that onDestroy is called after we have already 'destroyed'
|
||||
// the app (via `android_app_destroy` due to `android_main` returning.
|
||||
//
|
||||
// In this case `->destroyed` will already be set (so we won't deadlock in
|
||||
// the loop below) but we still need to close the messaging fds and finish
|
||||
// freeing the android_app
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_DESTROY);
|
||||
while (!android_app->destroyed) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
for (input_buf_idx = 0; input_buf_idx < NATIVE_APP_GLUE_MAX_INPUT_BUFFERS;
|
||||
input_buf_idx++) {
|
||||
struct android_input_buffer* buf =
|
||||
&android_app->inputBuffers[input_buf_idx];
|
||||
|
||||
android_app_clear_motion_events(buf);
|
||||
free(buf->motionEvents);
|
||||
free(buf->keyEvents);
|
||||
}
|
||||
|
||||
close(android_app->msgread);
|
||||
close(android_app->msgwrite);
|
||||
pthread_cond_destroy(&android_app->cond);
|
||||
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);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
android_app->stateSaved = 0;
|
||||
android_app_write_cmd(android_app, APP_CMD_SAVE_STATE);
|
||||
while (!android_app->stateSaved) {
|
||||
pthread_cond_wait(&android_app->cond, &android_app->mutex);
|
||||
}
|
||||
|
||||
if (android_app->savedState != NULL) {
|
||||
// Tell the Java side about our state.
|
||||
recallback((const char*)android_app->savedState,
|
||||
android_app->savedStateSize, context);
|
||||
// Now we can free it.
|
||||
free(android_app->savedState);
|
||||
android_app->savedState = NULL;
|
||||
android_app->savedStateSize = 0;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void onPause(GameActivity* activity) {
|
||||
LOGV("Pause: %p", activity);
|
||||
android_app_set_activity_state(ToApp(activity), APP_CMD_PAUSE);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool android_app_input_available_wake_up(struct android_app* app) {
|
||||
pthread_mutex_lock(&app->mutex);
|
||||
bool available = app->inputAvailableWakeUp;
|
||||
app->inputAvailableWakeUp = false;
|
||||
pthread_mutex_unlock(&app->mutex);
|
||||
return available;
|
||||
}
|
||||
|
||||
// NB: should be called with the android_app->mutex held already
|
||||
static void notifyInput(struct android_app* android_app) {
|
||||
// Don't spam the mainloop with wake ups if we've already sent one
|
||||
if (android_app->inputSwapPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (android_app->looper != NULL) {
|
||||
// for the app thread to know why it received the wake() up
|
||||
android_app->inputAvailableWakeUp = true;
|
||||
android_app->inputSwapPending = true;
|
||||
ALooper_wake(android_app->looper);
|
||||
}
|
||||
}
|
||||
|
||||
static bool onTouchEvent(GameActivity* activity,
|
||||
const GameActivityMotionEvent* event) {
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (android_app->motionEventFilter != NULL &&
|
||||
!android_app->motionEventFilter(event)) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct android_input_buffer* inputBuffer =
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
|
||||
// Add to the list of active motion events
|
||||
if (inputBuffer->motionEventsCount >= inputBuffer->motionEventsBufferSize) {
|
||||
inputBuffer->motionEventsBufferSize *= 2;
|
||||
inputBuffer->motionEvents = (GameActivityMotionEvent*)realloc(
|
||||
inputBuffer->motionEvents,
|
||||
sizeof(GameActivityMotionEvent) * inputBuffer->motionEventsBufferSize);
|
||||
|
||||
if (inputBuffer->motionEvents == NULL) {
|
||||
LOGE("onTouchEvent: out of memory");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int new_ix = inputBuffer->motionEventsCount;
|
||||
memcpy(&inputBuffer->motionEvents[new_ix], event,
|
||||
sizeof(GameActivityMotionEvent));
|
||||
++inputBuffer->motionEventsCount;
|
||||
notifyInput(android_app);
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_TOUCH_EVENT);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct android_input_buffer* android_app_swap_input_buffers(
|
||||
struct android_app* android_app) {
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
|
||||
struct android_input_buffer* inputBuffer =
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
|
||||
if (inputBuffer->motionEventsCount == 0 && inputBuffer->keyEventsCount == 0) {
|
||||
inputBuffer = NULL;
|
||||
} else {
|
||||
android_app->currentInputBuffer = (android_app->currentInputBuffer + 1) %
|
||||
NATIVE_APP_GLUE_MAX_INPUT_BUFFERS;
|
||||
}
|
||||
|
||||
android_app->inputSwapPending = false;
|
||||
android_app->inputAvailableWakeUp = false;
|
||||
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
return inputBuffer;
|
||||
}
|
||||
|
||||
void android_app_clear_motion_events(struct android_input_buffer* inputBuffer) {
|
||||
// We do not need to lock here if the inputBuffer has already been swapped
|
||||
// as is handled by the game loop thread
|
||||
while (inputBuffer->motionEventsCount > 0) {
|
||||
GameActivityMotionEvent_destroy(
|
||||
&inputBuffer->motionEvents[inputBuffer->motionEventsCount - 1]);
|
||||
|
||||
inputBuffer->motionEventsCount--;
|
||||
}
|
||||
assert(inputBuffer->motionEventsCount == 0);
|
||||
}
|
||||
|
||||
void android_app_set_key_event_filter(struct android_app* app,
|
||||
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);
|
||||
|
||||
// NB: we have to consider that the native thread could have already
|
||||
// (gracefully) exit (setting android_app->destroyed) and so we need
|
||||
// to be careful to avoid a deadlock waiting for a thread that's
|
||||
// already exit.
|
||||
if (android_app->destroyed) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (android_app->keyEventFilter != NULL &&
|
||||
!android_app->keyEventFilter(event)) {
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct android_input_buffer* inputBuffer =
|
||||
&android_app->inputBuffers[android_app->currentInputBuffer];
|
||||
|
||||
// Add to the list of active key down events
|
||||
if (inputBuffer->keyEventsCount >= inputBuffer->keyEventsBufferSize) {
|
||||
inputBuffer->keyEventsBufferSize = inputBuffer->keyEventsBufferSize * 2;
|
||||
inputBuffer->keyEvents = (GameActivityKeyEvent*)realloc(
|
||||
inputBuffer->keyEvents,
|
||||
sizeof(GameActivityKeyEvent) * inputBuffer->keyEventsBufferSize);
|
||||
|
||||
if (inputBuffer->keyEvents == NULL) {
|
||||
LOGE("onKey: out of memory");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int new_ix = inputBuffer->keyEventsCount;
|
||||
memcpy(&inputBuffer->keyEvents[new_ix], event, sizeof(GameActivityKeyEvent));
|
||||
++inputBuffer->keyEventsCount;
|
||||
notifyInput(android_app);
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_KEY_EVENT);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
void android_app_clear_key_events(struct android_input_buffer* inputBuffer) {
|
||||
inputBuffer->keyEventsCount = 0;
|
||||
}
|
||||
|
||||
static void onTextInputEvent(GameActivity* activity,
|
||||
const GameTextInputState* state) {
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
if (!android_app->destroyed) {
|
||||
android_app->textInputState = 1;
|
||||
notifyInput(android_app);
|
||||
}
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void onWindowInsetsChanged(GameActivity* activity) {
|
||||
LOGV("WindowInsetsChanged: %p", activity);
|
||||
android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_INSETS_CHANGED);
|
||||
}
|
||||
|
||||
static void onContentRectChanged(GameActivity* activity, const ARect* rect) {
|
||||
LOGV("ContentRectChanged: %p -- (%d %d) (%d %d)", activity, rect->left,
|
||||
rect->top, rect->right, rect->bottom);
|
||||
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->contentRect = *rect;
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_CONTENT_RECT_CHANGED);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static void onSoftwareKeyboardVisibilityChanged(GameActivity* activity,
|
||||
bool visible) {
|
||||
LOGV("SoftwareKeyboardVisibilityChanged: %p -- %d", activity, (int)visible);
|
||||
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->softwareKeyboardVisible = visible;
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_SOFTWARE_KB_VIS_CHANGED);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
}
|
||||
|
||||
static bool onEditorAction(GameActivity* activity, int action) {
|
||||
LOGV("EditorAction: %p -- %d", activity, action);
|
||||
|
||||
struct android_app* android_app = ToApp(activity);
|
||||
|
||||
pthread_mutex_lock(&android_app->mutex);
|
||||
android_app->editorAction = action;
|
||||
|
||||
android_app_write_cmd(android_app, APP_CMD_EDITOR_ACTION);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
// XXX: This symbol is renamed with a _C suffix and then re-exported from
|
||||
// Rust because Rust/Cargo don't give us a way to directly export symbols
|
||||
// from C/C++ code: https://github.com/rust-lang/rfcs/issues/2771
|
||||
//
|
||||
JNIEXPORT
|
||||
void GameActivity_onCreate_C(GameActivity* activity, void* savedState,
|
||||
size_t savedStateSize) {
|
||||
LOGV("Creating: %p", activity);
|
||||
activity->callbacks->onDestroy = onDestroy;
|
||||
activity->callbacks->onStart = onStart;
|
||||
activity->callbacks->onResume = onResume;
|
||||
activity->callbacks->onSaveInstanceState = onSaveInstanceState;
|
||||
activity->callbacks->onPause = onPause;
|
||||
activity->callbacks->onStop = onStop;
|
||||
activity->callbacks->onTouchEvent = onTouchEvent;
|
||||
activity->callbacks->onKeyDown = onKey;
|
||||
activity->callbacks->onKeyUp = onKey;
|
||||
activity->callbacks->onTextInputEvent = onTextInputEvent;
|
||||
activity->callbacks->onConfigurationChanged = onConfigurationChanged;
|
||||
activity->callbacks->onTrimMemory = onTrimMemory;
|
||||
activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
|
||||
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
|
||||
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
|
||||
activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded;
|
||||
activity->callbacks->onNativeWindowResized = onNativeWindowResized;
|
||||
activity->callbacks->onWindowInsetsChanged = onWindowInsetsChanged;
|
||||
activity->callbacks->onContentRectChanged = onContentRectChanged;
|
||||
activity->callbacks->onSoftwareKeyboardVisibilityChanged =
|
||||
onSoftwareKeyboardVisibilityChanged;
|
||||
activity->callbacks->onEditorAction = onEditorAction;
|
||||
LOGV("Callbacks set: %p", activity->callbacks);
|
||||
|
||||
activity->instance = android_app_create(activity, savedState, savedStateSize);
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
-377
@@ -1,377 +0,0 @@
|
||||
/*
|
||||
* 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 restartInput();
|
||||
void setEventCallback(GameTextInputEventCallback callback, void *context);
|
||||
jobject stateToJava(const GameTextInputState &state) const;
|
||||
void stateFromJava(jobject textInputEvent,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context) const;
|
||||
void setImeInsetsCallback(GameTextInputImeInsetsCallback callback,
|
||||
void *context);
|
||||
void processImeInsets(const ARect *insets);
|
||||
const ARect &getImeInsets() const { return currentInsets_; }
|
||||
|
||||
private:
|
||||
// Copy string and set other fields
|
||||
void setStateInner(const GameTextInputState &state);
|
||||
static void processCallback(void *context, const GameTextInputState *state);
|
||||
JNIEnv *env_ = nullptr;
|
||||
// Cached at initialization from
|
||||
// com/google/androidgamesdk/gametextinput/State.
|
||||
jclass stateJavaClass_ = nullptr;
|
||||
// The latest text input update.
|
||||
GameTextInputState currentState_ = {};
|
||||
// An instance of gametextinput.InputConnection.
|
||||
jclass inputConnectionClass_ = nullptr;
|
||||
jobject inputConnection_ = nullptr;
|
||||
jmethodID inputConnectionSetStateMethod_;
|
||||
jmethodID setSoftKeyboardActiveMethod_;
|
||||
jmethodID restartInputMethod_;
|
||||
void (*eventCallback_)(void *context,
|
||||
const struct GameTextInputState *state) = nullptr;
|
||||
void *eventCallbackContext_ = nullptr;
|
||||
void (*insetsCallback_)(void *context,
|
||||
const struct ARect *insets) = nullptr;
|
||||
ARect currentInsets_ = {};
|
||||
void *insetsCallbackContext_ = nullptr;
|
||||
StateClassInfo stateClassInfo_ = {};
|
||||
// Constant-sized buffer used to store state text.
|
||||
std::vector<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_restartInput(struct GameTextInput *input) {
|
||||
input->restartInput();
|
||||
}
|
||||
|
||||
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");
|
||||
restartInputMethod_ =
|
||||
env_->GetMethodID(inputConnectionClass_, "restartInput", "()V");
|
||||
|
||||
stateClassInfo_.text =
|
||||
env_->GetFieldID(stateJavaClass_, "text", "Ljava/lang/String;");
|
||||
stateClassInfo_.selectionStart =
|
||||
env_->GetFieldID(stateJavaClass_, "selectionStart", "I");
|
||||
stateClassInfo_.selectionEnd =
|
||||
env_->GetFieldID(stateJavaClass_, "selectionEnd", "I");
|
||||
stateClassInfo_.composingRegionStart =
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionStart", "I");
|
||||
stateClassInfo_.composingRegionEnd =
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "I");
|
||||
}
|
||||
|
||||
GameTextInput::~GameTextInput() {
|
||||
if (stateJavaClass_ != NULL) {
|
||||
env_->DeleteGlobalRef(stateJavaClass_);
|
||||
stateJavaClass_ = NULL;
|
||||
}
|
||||
if (inputConnectionClass_ != NULL) {
|
||||
env_->DeleteGlobalRef(inputConnectionClass_);
|
||||
inputConnectionClass_ = NULL;
|
||||
}
|
||||
if (inputConnection_ != NULL) {
|
||||
env_->DeleteGlobalRef(inputConnection_);
|
||||
inputConnection_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
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_, ¤tState_);
|
||||
}
|
||||
}
|
||||
|
||||
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_, ¤tInsets_);
|
||||
}
|
||||
}
|
||||
|
||||
void GameTextInput::hideIme(uint32_t flags) {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, false,
|
||||
flags);
|
||||
}
|
||||
|
||||
void GameTextInput::restartInput() {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, restartInputMethod_, false);
|
||||
}
|
||||
|
||||
jobject GameTextInput::stateToJava(const GameTextInputState &state) const {
|
||||
static jmethodID constructor = nullptr;
|
||||
if (constructor == nullptr) {
|
||||
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);
|
||||
}
|
||||
+669
-45
@@ -27,19 +27,18 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "common/gamesdk_common.h"
|
||||
#include "gamecommon.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define GAMETEXTINPUT_MAJOR_VERSION 2
|
||||
#define GAMETEXTINPUT_MAJOR_VERSION 4
|
||||
#define GAMETEXTINPUT_MINOR_VERSION 0
|
||||
#define GAMETEXTINPUT_BUGFIX_VERSION 0
|
||||
#define GAMETEXTINPUT_PACKED_VERSION \
|
||||
ANDROID_GAMESDK_PACKED_VERSION(GAMETEXTINPUT_MAJOR_VERSION, \
|
||||
GAMETEXTINPUT_MINOR_VERSION, \
|
||||
GAMETEXTINPUT_BUGFIX_VERSION)
|
||||
#define GAMETEXTINPUT_PACKED_VERSION \
|
||||
ANDROID_GAMESDK_PACKED_VERSION(GAMETEXTINPUT_MAJOR_VERSION, \
|
||||
GAMETEXTINPUT_MINOR_VERSION, \
|
||||
GAMETEXTINPUT_BUGFIX_VERSION)
|
||||
|
||||
/**
|
||||
* This struct holds a span within a region of text from start (inclusive) to
|
||||
@@ -47,16 +46,16 @@ extern "C" {
|
||||
* start==end. An undefined span is specified with start = end = SPAN_UNDEFINED.
|
||||
*/
|
||||
typedef struct GameTextInputSpan {
|
||||
/** The start of the region (inclusive). */
|
||||
int32_t start;
|
||||
/** The end of the region (exclusive). */
|
||||
int32_t end;
|
||||
/** The start of the region (inclusive). */
|
||||
int32_t start;
|
||||
/** The end of the region (exclusive). */
|
||||
int32_t end;
|
||||
} GameTextInputSpan;
|
||||
|
||||
/**
|
||||
* Values with special meaning in a GameTextInputSpan.
|
||||
*/
|
||||
enum GameTextInputSpanFlag { SPAN_UNDEFINED = -1 };
|
||||
enum GameTextInputSpanFlag : int32_t { SPAN_UNDEFINED = -1 };
|
||||
|
||||
/**
|
||||
* This struct holds the state of an editable section of text.
|
||||
@@ -66,23 +65,23 @@ enum GameTextInputSpanFlag { SPAN_UNDEFINED = -1 };
|
||||
* GameTextInput_setState to read and modify the state that an IME is editing.
|
||||
*/
|
||||
typedef struct GameTextInputState {
|
||||
/**
|
||||
* Text owned by the state, as a modified UTF-8 string. Null-terminated.
|
||||
* https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
|
||||
*/
|
||||
const char *text_UTF8;
|
||||
/**
|
||||
* Length in bytes of text_UTF8, *not* including the null at end.
|
||||
*/
|
||||
int32_t text_length;
|
||||
/**
|
||||
* A selection defined on the text.
|
||||
*/
|
||||
GameTextInputSpan selection;
|
||||
/**
|
||||
* A composing region defined on the text.
|
||||
*/
|
||||
GameTextInputSpan composingRegion;
|
||||
/**
|
||||
* Text owned by the state, as a modified UTF-8 string. Null-terminated.
|
||||
* https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
|
||||
*/
|
||||
const char *text_UTF8;
|
||||
/**
|
||||
* Length in bytes of text_UTF8, *not* including the null at end.
|
||||
*/
|
||||
int32_t text_length;
|
||||
/**
|
||||
* A selection defined on the text.
|
||||
*/
|
||||
GameTextInputSpan selection;
|
||||
/**
|
||||
* A composing region defined on the text.
|
||||
*/
|
||||
GameTextInputSpan composingRegion;
|
||||
} GameTextInputState;
|
||||
|
||||
/**
|
||||
@@ -143,14 +142,14 @@ 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.
|
||||
enum ShowImeFlags : uint32_t {
|
||||
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.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -164,18 +163,18 @@ 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.
|
||||
enum HideImeFlags : uint32_t {
|
||||
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().
|
||||
* Hide 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
|
||||
@@ -229,6 +228,9 @@ typedef void (*GameTextInputEventCallback)(
|
||||
* @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.
|
||||
* <b>This function is deprecated. Don't perform any complex processing inside
|
||||
* the callback other than copying the state variable. Using any synchronization
|
||||
* primitives inside this callback may cause a deadlock.</b>
|
||||
*/
|
||||
void GameTextInput_setEventCallback(GameTextInput *input,
|
||||
GameTextInputEventCallback callback,
|
||||
@@ -298,6 +300,628 @@ void GameTextInputState_fromJava(const GameTextInput *input, jobject state,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context);
|
||||
|
||||
/**
|
||||
* Definitions for inputType argument of GameActivity_setImeEditorInfo()
|
||||
*
|
||||
* <pre>
|
||||
* |-------|-------|-------|-------|
|
||||
* 1111 TYPE_MASK_CLASS
|
||||
* 11111111 TYPE_MASK_VARIATION
|
||||
* 111111111111 TYPE_MASK_FLAGS
|
||||
* |-------|-------|-------|-------|
|
||||
* TYPE_NULL
|
||||
* |-------|-------|-------|-------|
|
||||
* 1 TYPE_CLASS_TEXT
|
||||
* 1 TYPE_TEXT_VARIATION_URI
|
||||
* 1 TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
* 11 TYPE_TEXT_VARIATION_EMAIL_SUBJECT
|
||||
* 1 TYPE_TEXT_VARIATION_SHORT_MESSAGE
|
||||
* 1 1 TYPE_TEXT_VARIATION_LONG_MESSAGE
|
||||
* 11 TYPE_TEXT_VARIATION_PERSON_NAME
|
||||
* 111 TYPE_TEXT_VARIATION_POSTAL_ADDRESS
|
||||
* 1 TYPE_TEXT_VARIATION_PASSWORD
|
||||
* 1 1 TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
* 1 1 TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
|
||||
* 1 11 TYPE_TEXT_VARIATION_FILTER
|
||||
* 11 TYPE_TEXT_VARIATION_PHONETIC
|
||||
* 11 1 TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS
|
||||
* 111 TYPE_TEXT_VARIATION_WEB_PASSWORD
|
||||
* 1 TYPE_TEXT_FLAG_CAP_CHARACTERS
|
||||
* 1 TYPE_TEXT_FLAG_CAP_WORDS
|
||||
* 1 TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
* 1 TYPE_TEXT_FLAG_AUTO_CORRECT
|
||||
* 1 TYPE_TEXT_FLAG_AUTO_COMPLETE
|
||||
* 1 TYPE_TEXT_FLAG_MULTI_LINE
|
||||
* 1 TYPE_TEXT_FLAG_IME_MULTI_LINE
|
||||
* 1 TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
* 1 TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS
|
||||
* |-------|-------|-------|-------|
|
||||
* 1 TYPE_CLASS_NUMBER
|
||||
* 1 TYPE_NUMBER_VARIATION_PASSWORD
|
||||
* 1 TYPE_NUMBER_FLAG_SIGNED
|
||||
* 1 TYPE_NUMBER_FLAG_DECIMAL
|
||||
* |-------|-------|-------|-------|
|
||||
* 11 TYPE_CLASS_PHONE
|
||||
* |-------|-------|-------|-------|
|
||||
* 1 TYPE_CLASS_DATETIME
|
||||
* 1 TYPE_DATETIME_VARIATION_DATE
|
||||
* 1 TYPE_DATETIME_VARIATION_TIME
|
||||
* |-------|-------|-------|-------|</pre>
|
||||
*/
|
||||
|
||||
enum GameTextInputType : uint32_t {
|
||||
/**
|
||||
* Mask of bits that determine the overall class
|
||||
* of text being given. Currently supported classes are:
|
||||
* {@link #TYPE_CLASS_TEXT}, {@link #TYPE_CLASS_NUMBER},
|
||||
* {@link #TYPE_CLASS_PHONE}, {@link #TYPE_CLASS_DATETIME}.
|
||||
* <p>IME authors: If the class is not one you
|
||||
* understand, assume {@link #TYPE_CLASS_TEXT} with NO variation
|
||||
* or flags.<p>
|
||||
*/
|
||||
TYPE_MASK_CLASS = 0x0000000f,
|
||||
|
||||
/**
|
||||
* Mask of bits that determine the variation of
|
||||
* the base content class.
|
||||
*/
|
||||
TYPE_MASK_VARIATION = 0x00000ff0,
|
||||
|
||||
/**
|
||||
* Mask of bits that provide addition bit flags
|
||||
* of options.
|
||||
*/
|
||||
TYPE_MASK_FLAGS = 0x00fff000,
|
||||
|
||||
/**
|
||||
* Special content type for when no explicit type has been specified.
|
||||
* This should be interpreted to mean that the target input connection
|
||||
* is not rich, it can not process and show things like candidate text nor
|
||||
* retrieve the current text, so the input method will need to run in a
|
||||
* limited "generate key events" mode, if it supports it. Note that some
|
||||
* input methods may not support it, for example a voice-based input
|
||||
* method will likely not be able to generate key events even if this
|
||||
* flag is set.
|
||||
*/
|
||||
TYPE_NULL = 0x00000000,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class for normal text. This class supports the following flags (only
|
||||
* one of which should be set):
|
||||
* {@link #TYPE_TEXT_FLAG_CAP_CHARACTERS},
|
||||
* {@link #TYPE_TEXT_FLAG_CAP_WORDS}, and.
|
||||
* {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}. It also supports the
|
||||
* following variations:
|
||||
* {@link #TYPE_TEXT_VARIATION_NORMAL}, and
|
||||
* {@link #TYPE_TEXT_VARIATION_URI}. If you do not recognize the
|
||||
* variation, normal should be assumed.
|
||||
*/
|
||||
TYPE_CLASS_TEXT = 0x00000001,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: capitalize all characters. Overrides
|
||||
* {@link #TYPE_TEXT_FLAG_CAP_WORDS} and
|
||||
* {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}. This value is explicitly defined
|
||||
* to be the same as {@link TextUtils#CAP_MODE_CHARACTERS}. Of course,
|
||||
* this only affects languages where there are upper-case and lower-case
|
||||
* letters.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_CAP_CHARACTERS = 0x00001000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: capitalize the first character of
|
||||
* every word. Overrides {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}. This
|
||||
* value is explicitly defined
|
||||
* to be the same as {@link TextUtils#CAP_MODE_WORDS}. Of course,
|
||||
* this only affects languages where there are upper-case and lower-case
|
||||
* letters.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_CAP_WORDS = 0x00002000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: capitalize the first character of
|
||||
* each sentence. This value is explicitly defined
|
||||
* to be the same as {@link TextUtils#CAP_MODE_SENTENCES}. For example
|
||||
* in English it means to capitalize after a period and a space (note that
|
||||
* other languages may have different characters for period, or not use
|
||||
* spaces, or use different grammatical rules). Of course, this only affects
|
||||
* languages where there are upper-case and lower-case letters.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_CAP_SENTENCES = 0x00004000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: the user is entering free-form
|
||||
* text that should have auto-correction applied to it. Without this flag,
|
||||
* the IME will not try to correct typos. You should always set this flag
|
||||
* unless you really expect users to type non-words in this field, for
|
||||
* example to choose a name for a character in a game.
|
||||
* Contrast this with {@link #TYPE_TEXT_FLAG_AUTO_COMPLETE} and
|
||||
* {@link #TYPE_TEXT_FLAG_NO_SUGGESTIONS}:
|
||||
* {@code TYPE_TEXT_FLAG_AUTO_CORRECT} means that the IME will try to
|
||||
* auto-correct typos as the user is typing, but does not define whether
|
||||
* the IME offers an interface to show suggestions.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_AUTO_CORRECT = 0x00008000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: the text editor (which means
|
||||
* the application) is performing auto-completion of the text being entered
|
||||
* based on its own semantics, which it will present to the user as they type.
|
||||
* This generally means that the input method should not be showing
|
||||
* candidates itself, but can expect the editor to supply its own
|
||||
* completions/candidates from
|
||||
* {@link android.view.inputmethod.InputMethodSession#displayCompletions
|
||||
* InputMethodSession.displayCompletions()} as a result of the editor calling
|
||||
* {@link android.view.inputmethod.InputMethodManager#displayCompletions
|
||||
* InputMethodManager.displayCompletions()}.
|
||||
* Note the contrast with {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} and
|
||||
* {@link #TYPE_TEXT_FLAG_NO_SUGGESTIONS}:
|
||||
* {@code TYPE_TEXT_FLAG_AUTO_COMPLETE} means the editor should show an
|
||||
* interface for displaying suggestions, but instead of supplying its own
|
||||
* it will rely on the Editor to pass completions/corrections.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_AUTO_COMPLETE = 0x00010000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: multiple lines of text can be
|
||||
* entered into the field. If this flag is not set, the text field
|
||||
* will be constrained to a single line. The IME may also choose not to
|
||||
* display an enter key when this flag is not set, as there should be no
|
||||
* need to create new lines.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_MULTI_LINE = 0x00020000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: the regular text view associated
|
||||
* with this should not be multi-line, but when a fullscreen input method
|
||||
* is providing text it should use multiple lines if it can.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_IME_MULTI_LINE = 0x00040000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: the input method does not need to
|
||||
* display any dictionary-based candidates. This is useful for text views that
|
||||
* do not contain words from the language and do not benefit from any
|
||||
* dictionary-based completions or corrections. It overrides the
|
||||
* {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} value when set.
|
||||
* Please avoid using this unless you are certain this is what you want.
|
||||
* Many input methods need suggestions to work well, for example the ones
|
||||
* based on gesture typing. Consider clearing
|
||||
* {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} instead if you just do not
|
||||
* want the IME to correct typos.
|
||||
* Note the contrast with {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} and
|
||||
* {@link #TYPE_TEXT_FLAG_AUTO_COMPLETE}:
|
||||
* {@code TYPE_TEXT_FLAG_NO_SUGGESTIONS} means the IME does not need to
|
||||
* show an interface to display suggestions. Most IMEs will also take this to
|
||||
* mean they do not need to try to auto-correct what the user is typing.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_NO_SUGGESTIONS = 0x00080000,
|
||||
|
||||
/**
|
||||
* Flag for {@link #TYPE_CLASS_TEXT}: Let the IME know the text conversion
|
||||
* suggestions are required by the application. Text conversion suggestion is
|
||||
* for the transliteration languages which has pronunciation characters and
|
||||
* target characters. When the user is typing the pronunciation charactes, the
|
||||
* IME could provide the possible target characters to the user. When this
|
||||
* flag is set, the IME should insert the text conversion suggestions through
|
||||
* {@link Builder#setTextConversionSuggestions(List)} and
|
||||
* the {@link TextAttribute} with initialized with the text conversion
|
||||
* suggestions is provided by the IME to the application. To receive the
|
||||
* additional information, the application needs to implement {@link
|
||||
* InputConnection#setComposingText(CharSequence, int, TextAttribute)},
|
||||
* {@link InputConnection#setComposingRegion(int, int, TextAttribute)}, and
|
||||
* {@link InputConnection#commitText(CharSequence, int, TextAttribute)}.
|
||||
*/
|
||||
TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS = 0x00100000,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Default variation of {@link #TYPE_CLASS_TEXT}: plain old normal text.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_NORMAL = 0x00000000,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering a URI.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_URI = 0x00000010,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering an e-mail address.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 0x00000020,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering the subject line of
|
||||
* an e-mail.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 0x00000030,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering a short, possibly informal
|
||||
* message such as an instant message or a text message.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_SHORT_MESSAGE = 0x00000040,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering the content of a long,
|
||||
* possibly formal message such as the body of an e-mail.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_LONG_MESSAGE = 0x00000050,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering the name of a person.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000060,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing address.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000070,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering a password.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_PASSWORD = 0x00000080,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering a password, which should
|
||||
* be visible to the user.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 0x00000090,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of a web form.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x000000a0,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering text to filter contents
|
||||
* of a list etc.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_FILTER = 0x000000b0,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering text for phonetic
|
||||
* pronunciation, such as a phonetic name field in contacts. This is mostly
|
||||
* useful for languages where one spelling may have several phonetic
|
||||
* readings, like Japanese.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_PHONETIC = 0x000000c0,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering e-mail address inside
|
||||
* of a web form. This was added in
|
||||
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}. An IME must target
|
||||
* this API version or later to see this input type; if it doesn't, a request
|
||||
* for this type will be seen as {@link #TYPE_TEXT_VARIATION_EMAIL_ADDRESS}
|
||||
* when passed through {@link
|
||||
* android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
* EditorInfo.makeCompatible(int)}.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = 0x000000d0,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_TEXT}: entering password inside
|
||||
* of a web form. This was added in
|
||||
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}. An IME must target
|
||||
* this API version or later to see this input type; if it doesn't, a request
|
||||
* for this type will be seen as {@link #TYPE_TEXT_VARIATION_PASSWORD}
|
||||
* when passed through {@link
|
||||
* android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
* EditorInfo.makeCompatible(int)}.
|
||||
*/
|
||||
TYPE_TEXT_VARIATION_WEB_PASSWORD = 0x000000e0,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class for numeric text. This class supports the following flags:
|
||||
* {@link #TYPE_NUMBER_FLAG_SIGNED} and
|
||||
* {@link #TYPE_NUMBER_FLAG_DECIMAL}. It also supports the following
|
||||
* variations: {@link #TYPE_NUMBER_VARIATION_NORMAL} and
|
||||
* {@link #TYPE_NUMBER_VARIATION_PASSWORD}.
|
||||
* <p>IME authors: If you do not recognize
|
||||
* the variation, normal should be assumed.</p>
|
||||
*/
|
||||
TYPE_CLASS_NUMBER = 0x00000002,
|
||||
|
||||
/**
|
||||
* Flag of {@link #TYPE_CLASS_NUMBER}: the number is signed, allowing
|
||||
* a positive or negative sign at the start.
|
||||
*/
|
||||
TYPE_NUMBER_FLAG_SIGNED = 0x00001000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #TYPE_CLASS_NUMBER}: the number is decimal, allowing
|
||||
* a decimal point to provide fractional values.
|
||||
*/
|
||||
TYPE_NUMBER_FLAG_DECIMAL = 0x00002000,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Default variation of {@link #TYPE_CLASS_NUMBER}: plain normal
|
||||
* numeric text. This was added in
|
||||
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}. An IME must target
|
||||
* this API version or later to see this input type; if it doesn't, a request
|
||||
* for this type will be dropped when passed through
|
||||
* {@link android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
* EditorInfo.makeCompatible(int)}.
|
||||
*/
|
||||
TYPE_NUMBER_VARIATION_NORMAL = 0x00000000,
|
||||
|
||||
/**
|
||||
* Variation of {@link #TYPE_CLASS_NUMBER}: entering a numeric password.
|
||||
* This was added in {@link android.os.Build.VERSION_CODES#HONEYCOMB}. An
|
||||
* IME must target this API version or later to see this input type; if it
|
||||
* doesn't, a request for this type will be dropped when passed
|
||||
* through {@link android.view.inputmethod.EditorInfo#makeCompatible(int)
|
||||
* EditorInfo.makeCompatible(int)}.
|
||||
*/
|
||||
TYPE_NUMBER_VARIATION_PASSWORD = 0x00000010,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
/**
|
||||
* Class for a phone number. This class currently supports no variations
|
||||
* or flags.
|
||||
*/
|
||||
TYPE_CLASS_PHONE = 0x00000003,
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class for dates and times. It supports the
|
||||
* following variations:
|
||||
* {@link #TYPE_DATETIME_VARIATION_NORMAL}
|
||||
* {@link #TYPE_DATETIME_VARIATION_DATE}, and
|
||||
* {@link #TYPE_DATETIME_VARIATION_TIME}.
|
||||
*/
|
||||
TYPE_CLASS_DATETIME = 0x00000004,
|
||||
|
||||
/**
|
||||
* Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
|
||||
* both a date and time.
|
||||
*/
|
||||
TYPE_DATETIME_VARIATION_NORMAL = 0x00000000,
|
||||
|
||||
/**
|
||||
* Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
|
||||
* only a date.
|
||||
*/
|
||||
TYPE_DATETIME_VARIATION_DATE = 0x00000010,
|
||||
|
||||
/**
|
||||
* Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
|
||||
* only a time.
|
||||
*/
|
||||
TYPE_DATETIME_VARIATION_TIME = 0x00000020,
|
||||
};
|
||||
|
||||
/**
|
||||
* actionId and imeOptions argument of GameActivity_setImeEditorInfo().
|
||||
*
|
||||
* <pre>
|
||||
* |-------|-------|-------|-------|
|
||||
* 1111 IME_MASK_ACTION
|
||||
* |-------|-------|-------|-------|
|
||||
* IME_ACTION_UNSPECIFIED
|
||||
* 1 IME_ACTION_NONE
|
||||
* 1 IME_ACTION_GO
|
||||
* 11 IME_ACTION_SEARCH
|
||||
* 1 IME_ACTION_SEND
|
||||
* 1 1 IME_ACTION_NEXT
|
||||
* 11 IME_ACTION_DONE
|
||||
* 111 IME_ACTION_PREVIOUS
|
||||
* 1 IME_FLAG_NO_PERSONALIZED_LEARNING
|
||||
* 1 IME_FLAG_NO_FULLSCREEN
|
||||
* 1 IME_FLAG_NAVIGATE_PREVIOUS
|
||||
* 1 IME_FLAG_NAVIGATE_NEXT
|
||||
* 1 IME_FLAG_NO_EXTRACT_UI
|
||||
* 1 IME_FLAG_NO_ACCESSORY_ACTION
|
||||
* 1 IME_FLAG_NO_ENTER_ACTION
|
||||
* 1 IME_FLAG_FORCE_ASCII
|
||||
* |-------|-------|-------|-------|</pre>
|
||||
*/
|
||||
|
||||
enum GameTextInputActionType : uint32_t {
|
||||
/**
|
||||
* Set of bits in {@link #imeOptions} that provide alternative actions
|
||||
* associated with the "enter" key. This both helps the IME provide
|
||||
* better feedback about what the enter key will do, and also allows it
|
||||
* to provide alternative mechanisms for providing that command.
|
||||
*/
|
||||
IME_MASK_ACTION = 0x000000ff,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: no specific action has been
|
||||
* associated with this editor, let the editor come up with its own if
|
||||
* it can.
|
||||
*/
|
||||
IME_ACTION_UNSPECIFIED = 0x00000000,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: there is no available action.
|
||||
*/
|
||||
IME_ACTION_NONE = 0x00000001,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "go"
|
||||
* operation to take the user to the target of the text they typed.
|
||||
* Typically used, for example, when entering a URL.
|
||||
*/
|
||||
IME_ACTION_GO = 0x00000002,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "search"
|
||||
* operation, taking the user to the results of searching for the text
|
||||
* they have typed (in whatever context is appropriate).
|
||||
*/
|
||||
IME_ACTION_SEARCH = 0x00000003,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "send"
|
||||
* operation, delivering the text to its target. This is typically used
|
||||
* when composing a message in IM or SMS where sending is immediate.
|
||||
*/
|
||||
IME_ACTION_SEND = 0x00000004,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "next"
|
||||
* operation, taking the user to the next field that will accept text.
|
||||
*/
|
||||
IME_ACTION_NEXT = 0x00000005,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "done"
|
||||
* operation, typically meaning there is nothing more to input and the
|
||||
* IME will be closed.
|
||||
*/
|
||||
IME_ACTION_DONE = 0x00000006,
|
||||
|
||||
/**
|
||||
* Bits of {@link #IME_MASK_ACTION}: like {@link #IME_ACTION_NEXT}, but
|
||||
* for moving to the previous field. This will normally not be used to
|
||||
* specify an action (since it precludes {@link #IME_ACTION_NEXT}), but
|
||||
* can be returned to the app if it sets {@link #IME_FLAG_NAVIGATE_PREVIOUS}.
|
||||
*/
|
||||
IME_ACTION_PREVIOUS = 0x00000007,
|
||||
};
|
||||
|
||||
enum GameTextInputImeOptions : uint32_t {
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used to request that the IME should not update
|
||||
* any personalized data such as typing history and personalized language
|
||||
* model based on what the user typed on this text editing object. Typical
|
||||
* use cases are: <ul> <li>When the application is in a special mode, where
|
||||
* user's activities are expected to be not recorded in the application's
|
||||
* history. Some web browsers and chat applications may have this kind of
|
||||
* modes.</li> <li>When storing typing history does not make much sense.
|
||||
* Specifying this flag in typing games may help to avoid typing history from
|
||||
* being filled up with words that the user is less likely to type in their
|
||||
* daily life. Another example is that when the application already knows
|
||||
* that the expected input is not a valid word (e.g. a promotion code that is
|
||||
* not a valid word in any natural language).</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Applications need to be aware that the flag is not a guarantee, and some
|
||||
* IMEs may not respect it.</p>
|
||||
*/
|
||||
IME_FLAG_NO_PERSONALIZED_LEARNING = 0x1000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used to request that the IME never go
|
||||
* into fullscreen mode.
|
||||
* By default, IMEs may go into full screen mode when they think
|
||||
* it's appropriate, for example on small screens in landscape
|
||||
* orientation where displaying a software keyboard may occlude
|
||||
* such a large portion of the screen that the remaining part is
|
||||
* too small to meaningfully display the application UI.
|
||||
* If this flag is set, compliant IMEs will never go into full screen mode,
|
||||
* and always leave some space to display the application UI.
|
||||
* Applications need to be aware that the flag is not a guarantee, and
|
||||
* some IMEs may ignore it.
|
||||
*/
|
||||
IME_FLAG_NO_FULLSCREEN = 0x2000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: like {@link #IME_FLAG_NAVIGATE_NEXT}, but
|
||||
* specifies there is something interesting that a backward navigation
|
||||
* can focus on. If the user selects the IME's facility to backward
|
||||
* navigate, this will show up in the application as an {@link
|
||||
* #IME_ACTION_PREVIOUS} at {@link InputConnection#performEditorAction(int)
|
||||
* InputConnection.performEditorAction(int)}.
|
||||
*/
|
||||
IME_FLAG_NAVIGATE_PREVIOUS = 0x4000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used to specify that there is something
|
||||
* interesting that a forward navigation can focus on. This is like using
|
||||
* {@link #IME_ACTION_NEXT}, except allows the IME to be multiline (with
|
||||
* an enter key) as well as provide forward navigation. Note that some
|
||||
* IMEs may not be able to do this, especially when running on a small
|
||||
* screen where there is little space. In that case it does not need to
|
||||
* present a UI for this option. Like {@link #IME_ACTION_NEXT}, if the
|
||||
* user selects the IME's facility to forward navigate, this will show up
|
||||
* in the application at {@link InputConnection#performEditorAction(int)
|
||||
* InputConnection.performEditorAction(int)}.
|
||||
*/
|
||||
IME_FLAG_NAVIGATE_NEXT = 0x8000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used to specify that the IME does not need
|
||||
* to show its extracted text UI. For input methods that may be fullscreen,
|
||||
* often when in landscape mode, this allows them to be smaller and let part
|
||||
* of the application be shown behind, through transparent UI parts in the
|
||||
* fullscreen IME. The part of the UI visible to the user may not be
|
||||
* responsive to touch because the IME will receive touch events, which may
|
||||
* confuse the user; use {@link #IME_FLAG_NO_FULLSCREEN} instead for a better
|
||||
* experience. Using this flag is discouraged and it may become deprecated in
|
||||
* the future. Its meaning is unclear in some situations and it may not work
|
||||
* appropriately on older versions of the platform.
|
||||
*/
|
||||
IME_FLAG_NO_EXTRACT_UI = 0x10000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used in conjunction with one of the actions
|
||||
* masked by {@link #IME_MASK_ACTION}, this indicates that the action
|
||||
* should not be available as an accessory button on the right of the
|
||||
* extracted text when the input method is full-screen. Note that by setting
|
||||
* this flag, there can be cases where the action is simply never available to
|
||||
* the user. Setting this generally means that you think that in fullscreen
|
||||
* mode, where there is little space to show the text, it's not worth taking
|
||||
* some screen real estate to display the action and it should be used instead
|
||||
* to show more text.
|
||||
*/
|
||||
IME_FLAG_NO_ACCESSORY_ACTION = 0x20000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used in conjunction with one of the actions
|
||||
* masked by {@link #IME_MASK_ACTION}. If this flag is not set, IMEs will
|
||||
* normally replace the "enter" key with the action supplied. This flag
|
||||
* indicates that the action should not be available in-line as a replacement
|
||||
* for the "enter" key. Typically this is because the action has such a
|
||||
* significant impact or is not recoverable enough that accidentally hitting
|
||||
* it should be avoided, such as sending a message. Note that
|
||||
* {@link android.widget.TextView} will automatically set this flag for you
|
||||
* on multi-line text views.
|
||||
*/
|
||||
IME_FLAG_NO_ENTER_ACTION = 0x40000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #imeOptions}: used to request an IME that is capable of
|
||||
* inputting ASCII characters. The intention of this flag is to ensure that
|
||||
* the user can type Roman alphabet characters in a {@link
|
||||
* android.widget.TextView}. It is typically used for an account ID or
|
||||
* password input. A lot of the time, IMEs are already able to input ASCII
|
||||
* even without being told so (such IMEs already respect this flag in a
|
||||
* sense), but there are cases when this is not the default. For instance,
|
||||
* users of languages using a different script like Arabic, Greek, Hebrew or
|
||||
* Russian typically have a keyboard that can't input ASCII characters by
|
||||
* default. Applications need to be aware that the flag is not a guarantee,
|
||||
* and some IMEs may not respect it. However, it is strongly recommended for
|
||||
* IME authors to respect this flag especially when their IME could end up
|
||||
* with a state where only languages using non-ASCII are enabled.
|
||||
*/
|
||||
IME_FLAG_FORCE_ASCII = 0x80000000,
|
||||
|
||||
/**
|
||||
* Flag of {@link #internalImeOptions}: flag is set when app window containing
|
||||
* this
|
||||
* {@link EditorInfo} is using {@link Configuration#ORIENTATION_PORTRAIT}
|
||||
* mode.
|
||||
* @hide
|
||||
*/
|
||||
IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT = 0x00000001,
|
||||
|
||||
/**
|
||||
* Generic unspecified type for {@link #imeOptions}.
|
||||
*/
|
||||
IME_NULL = 0x00000000,
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* 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 <mutex>
|
||||
#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);
|
||||
GameTextInputState getState() const {
|
||||
std::lock_guard<std::mutex> lock(currentStateMutex_);
|
||||
return currentState_;
|
||||
}
|
||||
void setInputConnection(jobject inputConnection);
|
||||
void processEvent(jobject textInputEvent);
|
||||
void showIme(uint32_t flags);
|
||||
void hideIme(uint32_t flags);
|
||||
void restartInput();
|
||||
void setEventCallback(GameTextInputEventCallback callback, void *context);
|
||||
jobject stateToJava(const GameTextInputState &state) const;
|
||||
void stateFromJava(jobject textInputEvent,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context) const;
|
||||
void setImeInsetsCallback(GameTextInputImeInsetsCallback callback,
|
||||
void *context);
|
||||
void processImeInsets(const ARect *insets);
|
||||
const ARect &getImeInsets() const { return currentInsets_; }
|
||||
|
||||
private:
|
||||
// Copy string and set other fields
|
||||
void setStateInner(const GameTextInputState &state);
|
||||
static void processCallback(void *context, const GameTextInputState *state);
|
||||
JNIEnv *env_ = nullptr;
|
||||
// Cached at initialization from
|
||||
// com/google/androidgamesdk/gametextinput/State.
|
||||
jclass stateJavaClass_ = nullptr;
|
||||
// The latest text input update.
|
||||
GameTextInputState currentState_ = {};
|
||||
// A mutex to protect currentState_.
|
||||
mutable std::mutex currentStateMutex_;
|
||||
// An instance of gametextinput.InputConnection.
|
||||
jclass inputConnectionClass_ = nullptr;
|
||||
jobject inputConnection_ = nullptr;
|
||||
jmethodID inputConnectionSetStateMethod_;
|
||||
jmethodID setSoftKeyboardActiveMethod_;
|
||||
jmethodID restartInputMethod_;
|
||||
void (*eventCallback_)(void *context,
|
||||
const struct GameTextInputState *state) = nullptr;
|
||||
void *eventCallbackContext_ = nullptr;
|
||||
void (*insetsCallback_)(void *context, const struct ARect *insets) = nullptr;
|
||||
ARect currentInsets_ = {};
|
||||
void *insetsCallbackContext_ = nullptr;
|
||||
StateClassInfo stateClassInfo_ = {};
|
||||
// Constant-sized buffer used to store state text.
|
||||
std::vector<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) {
|
||||
GameTextInputState state = input->getState();
|
||||
callback(context, &state);
|
||||
}
|
||||
|
||||
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_restartInput(struct GameTextInput *input) {
|
||||
input->restartInput();
|
||||
}
|
||||
|
||||
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");
|
||||
restartInputMethod_ =
|
||||
env_->GetMethodID(inputConnectionClass_, "restartInput", "()V");
|
||||
|
||||
stateClassInfo_.text =
|
||||
env_->GetFieldID(stateJavaClass_, "text", "Ljava/lang/String;");
|
||||
stateClassInfo_.selectionStart =
|
||||
env_->GetFieldID(stateJavaClass_, "selectionStart", "I");
|
||||
stateClassInfo_.selectionEnd =
|
||||
env_->GetFieldID(stateJavaClass_, "selectionEnd", "I");
|
||||
stateClassInfo_.composingRegionStart =
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionStart", "I");
|
||||
stateClassInfo_.composingRegionEnd =
|
||||
env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "I");
|
||||
}
|
||||
|
||||
GameTextInput::~GameTextInput() {
|
||||
if (stateJavaClass_ != NULL) {
|
||||
env_->DeleteGlobalRef(stateJavaClass_);
|
||||
stateJavaClass_ = NULL;
|
||||
}
|
||||
if (inputConnectionClass_ != NULL) {
|
||||
env_->DeleteGlobalRef(inputConnectionClass_);
|
||||
inputConnectionClass_ = NULL;
|
||||
}
|
||||
if (inputConnection_ != NULL) {
|
||||
env_->DeleteGlobalRef(inputConnection_);
|
||||
inputConnection_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
std::lock_guard<std::mutex> lock(currentStateMutex_);
|
||||
|
||||
// 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_) {
|
||||
std::lock_guard<std::mutex> lock(currentStateMutex_);
|
||||
eventCallback_(eventCallbackContext_, ¤tState_);
|
||||
}
|
||||
}
|
||||
|
||||
void GameTextInput::showIme(uint32_t flags) {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, true,
|
||||
static_cast<jint>(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_, ¤tInsets_);
|
||||
}
|
||||
}
|
||||
|
||||
void GameTextInput::hideIme(uint32_t flags) {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, false,
|
||||
static_cast<jint>(flags));
|
||||
}
|
||||
|
||||
void GameTextInput::restartInput() {
|
||||
if (inputConnection_ == nullptr) return;
|
||||
env_->CallVoidMethod(inputConnection_, restartInputMethod_);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -31,11 +31,12 @@
|
||||
|
||||
// There are separate versions for each GameSDK component that use this format:
|
||||
#define ANDROID_GAMESDK_PACKED_VERSION(MAJOR, MINOR, BUGFIX) \
|
||||
((MAJOR << 16) | (MINOR << 8) | (BUGFIX))
|
||||
((MAJOR << 16) | (MINOR << 8) | (BUGFIX))
|
||||
// Accessors
|
||||
#define ANDROID_GAMESDK_MAJOR_VERSION(PACKED) ((PACKED) >> 16)
|
||||
#define ANDROID_GAMESDK_MINOR_VERSION(PACKED) (((PACKED) >> 8) & 0xff)
|
||||
#define ANDROID_GAMESDK_BUGFIX_VERSION(PACKED) ((PACKED) & 0xff)
|
||||
|
||||
#define AGDK_STRING_VERSION(MAJOR, MINOR, BUGFIX, GIT) \
|
||||
#MAJOR "." #MINOR "." #BUGFIX "." #GIT
|
||||
#define AGDK_STRINGIFY(NUMBER) #NUMBER
|
||||
#define AGDK_STRING_VERSION(MAJOR, MINOR, BUGFIX) \
|
||||
AGDK_STRINGIFY(MAJOR) "." AGDK_STRINGIFY(MINOR) "." AGDK_STRINGIFY(BUGFIX)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 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 "system_utils.h"
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
namespace gamesdk {
|
||||
|
||||
#if __ANDROID_API__ >= 26
|
||||
std::string getSystemPropViaCallback(const char* key,
|
||||
const char* default_value = "") {
|
||||
const prop_info* prop = __system_property_find(key);
|
||||
if (prop == nullptr) {
|
||||
return default_value;
|
||||
}
|
||||
std::string return_value;
|
||||
auto thunk = [](void* cookie, const char* /*name*/, const char* value,
|
||||
uint32_t /*serial*/) {
|
||||
if (value != nullptr) {
|
||||
std::string* r = static_cast<std::string*>(cookie);
|
||||
*r = value;
|
||||
}
|
||||
};
|
||||
__system_property_read_callback(prop, thunk, &return_value);
|
||||
return return_value;
|
||||
}
|
||||
#else
|
||||
std::string getSystemPropViaGet(const char* key,
|
||||
const char* default_value = "") {
|
||||
char buffer[PROP_VALUE_MAX + 1] = ""; // +1 for terminator
|
||||
int bufferLen = __system_property_get(key, buffer);
|
||||
if (bufferLen > 0)
|
||||
return buffer;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string GetSystemProp(const char* key, const char* default_value) {
|
||||
#if __ANDROID_API__ >= 26
|
||||
return getSystemPropViaCallback(key, default_value);
|
||||
#else
|
||||
return getSystemPropViaGet(key, default_value);
|
||||
#endif
|
||||
}
|
||||
|
||||
int GetSystemPropAsInt(const char* key, int default_value) {
|
||||
std::string prop = GetSystemProp(key);
|
||||
return prop == "" ? default_value : strtoll(prop.c_str(), nullptr, 10);
|
||||
}
|
||||
|
||||
bool GetSystemPropAsBool(const char* key, bool default_value) {
|
||||
return GetSystemPropAsInt(key, default_value) != 0;
|
||||
}
|
||||
|
||||
} // namespace gamesdk
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 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 "string"
|
||||
|
||||
namespace gamesdk {
|
||||
|
||||
// Get the value of the given system property
|
||||
std::string GetSystemProp(const char* key, const char* default_value = "");
|
||||
|
||||
// Get the value of the given system property as an integer
|
||||
int GetSystemPropAsInt(const char* key, int default_value = 0);
|
||||
|
||||
// Get the value of the given system property as a bool
|
||||
bool GetSystemPropAsBool(const char* key, bool default_value = false);
|
||||
|
||||
} // namespace gamesdk
|
||||
+58
-22
@@ -1,50 +1,86 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
fn build_glue_for_game_activity() {
|
||||
let activity_basepath = "android-games-sdk/game-activity/prefab-src/modules/game-activity/include";
|
||||
let textinput_basepath = "android-games-sdk/game-text-input/prefab-src/modules/game-text-input/include";
|
||||
let activity_path = |src_inc, name| {
|
||||
format!("android-games-sdk/game-activity/prefab-src/modules/game-activity/{src_inc}/game-activity/{name}")
|
||||
};
|
||||
let textinput_path = |src_inc, name| {
|
||||
format!("android-games-sdk/game-text-input/prefab-src/modules/game-text-input/{src_inc}/game-text-input/{name}")
|
||||
};
|
||||
|
||||
for f in ["GameActivity.cpp", "GameActivityEvents.cpp"] {
|
||||
println!("cargo:rerun-if-changed={}", activity_path("src", f));
|
||||
}
|
||||
|
||||
for f in [
|
||||
"GameActivity.h",
|
||||
"GameActivity.cpp",
|
||||
"GameActivityEvents.h",
|
||||
"GameActivityEvents.cpp",
|
||||
"GameActivityLog.h",
|
||||
"GameActivityEvents_internal.h",
|
||||
] {
|
||||
println!("cargo:rerun-if-changed={activity_basepath}/game-activity/{f}");
|
||||
println!("cargo:rerun-if-changed={}", activity_path("include", f));
|
||||
}
|
||||
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.include("android-games-sdk/src/common")
|
||||
.file("android-games-sdk/src/common/system_utils.cpp")
|
||||
.extra_warnings(false)
|
||||
.cpp_link_stdlib("c++_static")
|
||||
.compile("libgame_common.a");
|
||||
|
||||
println!("cargo:rerun-if-changed=android-games-sdk/src/common/system_utils.cpp");
|
||||
println!("cargo:rerun-if-changed=android-games-sdk/src/common/system_utils.h");
|
||||
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.include("android-games-sdk/src/common")
|
||||
.include("android-games-sdk/include")
|
||||
.include(activity_basepath)
|
||||
.include(textinput_basepath)
|
||||
.file(format!("{activity_basepath}/game-activity/GameActivity.cpp"))
|
||||
.file(format!("{activity_basepath}/game-activity/GameActivityEvents.cpp"))
|
||||
.include("android-games-sdk/game-activity/prefab-src/modules/game-activity/include")
|
||||
.include("android-games-sdk/game-text-input/prefab-src/modules/game-text-input/include")
|
||||
.file(activity_path("src", "GameActivity.cpp"))
|
||||
.file(activity_path("src", "GameActivityEvents.cpp"))
|
||||
.extra_warnings(false)
|
||||
.cpp_link_stdlib("c++_static")
|
||||
.compile("libgame_activity.a");
|
||||
|
||||
for f in ["gamecommon.h", "gametextinput.h", "gametextinput.cpp"] {
|
||||
println!("cargo:rerun-if-changed={textinput_basepath}/game-text-input/{f}");
|
||||
}
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
textinput_path("include", "gametextinput.h")
|
||||
);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
textinput_path("src", "gametextinput.cpp")
|
||||
);
|
||||
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.include("android-games-sdk/src/common")
|
||||
.include("android-games-sdk/include")
|
||||
.include(textinput_basepath)
|
||||
.file(format!("{textinput_basepath}/game-text-input/gametextinput.cpp"))
|
||||
.include("android-games-sdk/game-text-input/prefab-src/modules/game-text-input/include")
|
||||
.file(textinput_path("src", "gametextinput.cpp"))
|
||||
.cpp_link_stdlib("c++_static")
|
||||
.compile("libgame_text_input.a");
|
||||
|
||||
for f in ["android_native_app_glue.h", "android_native_app_glue.c"] {
|
||||
println!("cargo:rerun-if-changed={activity_basepath}/game-activity/native_app_glue/{f}");
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
activity_path("src", "native_app_glue/android_native_app_glue.c")
|
||||
);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
activity_path("include", "native_app_glue/android_native_app_glue.h")
|
||||
);
|
||||
|
||||
cc::Build::new()
|
||||
.include("android-games-sdk/src/common")
|
||||
.include("android-games-sdk/include")
|
||||
.include(activity_basepath)
|
||||
.include(textinput_basepath)
|
||||
.include(format!("{activity_basepath}/game-activity/native_app_glue"))
|
||||
.file(format!("{activity_basepath}/game-activity/native_app_glue/android_native_app_glue.c"))
|
||||
.include("android-games-sdk/game-activity/prefab-src/modules/game-activity/include")
|
||||
.include("android-games-sdk/game-text-input/prefab-src/modules/game-text-input/include")
|
||||
.include(activity_path("include", ""))
|
||||
.file(activity_path(
|
||||
"src",
|
||||
"native_app_glue/android_native_app_glue.c",
|
||||
))
|
||||
.extra_warnings(false)
|
||||
.cpp_link_stdlib("c++_static")
|
||||
.compile("libnative_app_glue.a");
|
||||
|
||||
Reference in New Issue
Block a user