mirror of
https://github.com/rust-mobile/android-activity.git
synced 2026-07-12 17:48:55 +00:00
switch to android-game-sdk-rs grafted repo at v2.0.2
Also includes our patches on top (branch android-activity-2.0.2). This is mainly to test to make sure everything is still working. We will switch to the android-activity-4.0.0 branch when we're done Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
committed by
Robert Bragg
parent
976e9d06af
commit
49f2b86424
@@ -0,0 +1,40 @@
|
||||
# android-games-sdk-rs
|
||||
|
||||
This is a grafted version of [android-games-sdk-rs][agdk].
|
||||
|
||||
The purpose of android-games-sdk-rs is to track our patches on top of the Android Game Development Kit (AGDK) so that it is easier to update to new upstream versions.
|
||||
|
||||
## Updating to new agdk version checklist
|
||||
|
||||
This is a basic checklist for things that need to be done before updating to a new agdk version:
|
||||
|
||||
- [ ] 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.
|
||||
- [ ] 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)
|
||||
|
||||
## Backporting patches
|
||||
|
||||
Changes made to these files must be backported to [android-games-sdk-rs][agdk], otherwise they will be lost when updating to newer upstream versions. This can be done like so (running from the project root):
|
||||
|
||||
```bash
|
||||
git format-patch -o ~/agdk-patches last-import.. -- android-activity/android-games-sdk
|
||||
```
|
||||
|
||||
When applying on the [android-games-sdk-rs][agdk] side:
|
||||
|
||||
```bash
|
||||
git checkout android-activity-4.0.0
|
||||
git am -p3 ~/agdk-patches/*.patch
|
||||
```
|
||||
|
||||
Once these are applied on top of the current imported agdk version (in this example 4.0.0). Then they can be rebased onto future agdk versions easily.
|
||||
|
||||
## Copying files from agdk
|
||||
|
||||
When updating to a new version of upstream agdk properly, you can use the [copy_files](./copy_files) script.
|
||||
|
||||
This script takes in a list of files from [file_list.txt](./file_list.txt), and grafts those files into the local directory. This ensures that pathes are aligned so that patching is easier.
|
||||
|
||||
[agdk]: https://github.com/rust-mobile/android-games-sdk-rs
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copies files from android-games-sdk-rs so that we don't have to use
|
||||
# it has a submodule (see https://github.com/rust-mobile/android-activity/issues/190)
|
||||
|
||||
# Usage: ./copy_files source_dir dest_dir file_list.txt
|
||||
# file_list.txt should contain one filename or path pattern per line
|
||||
|
||||
# Check if correct number of arguments
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $0 <android-games-sdk-rs dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR="$1"
|
||||
DEST_DIR="${2:-.}"
|
||||
FILE_LIST="${3:-file_list.txt}"
|
||||
|
||||
# Check if source directory exists
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "Error: Source directory '$SOURCE_DIR' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if file list exists
|
||||
if [ ! -f "$FILE_LIST" ]; then
|
||||
echo "Error: File list '$FILE_LIST' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create destination directory if it doesn't exist
|
||||
mkdir -p "$DEST_DIR"
|
||||
|
||||
# Read file list and copy each file
|
||||
while IFS= read -r file || [ -n "$file" ]; do
|
||||
# Skip empty lines and comments
|
||||
if [ -z "$file" ] || [[ "$file" =~ ^# ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# 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")
|
||||
|
||||
# Create destination directory structure
|
||||
mkdir -p "$dest_dir"
|
||||
|
||||
# Copy the file
|
||||
cp "$file" "$dest_file"
|
||||
echo "Copied: $rel_path"
|
||||
done
|
||||
done < "$FILE_LIST"
|
||||
|
||||
echo "Copy operation completed."
|
||||
@@ -0,0 +1,11 @@
|
||||
GameActivity.h
|
||||
GameActivity.cpp
|
||||
GameActivityEvents.h
|
||||
GameActivityEvents.cpp
|
||||
GameActivityLog.h
|
||||
gametextinput.cpp
|
||||
gametextinput.h
|
||||
gamecommon.h
|
||||
gamesdk_common.h
|
||||
android_native_app_glue.h
|
||||
android_native_app_glue.c
|
||||
+1147
File diff suppressed because it is too large
Load Diff
+636
@@ -0,0 +1,636 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup GameActivity Game Activity
|
||||
* The interface to use GameActivity.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file GameActivity.h
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_H
|
||||
#define ANDROID_GAME_SDK_GAME_ACTIVITY_H
|
||||
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/input.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/rect.h>
|
||||
#include <jni.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "common/gamesdk_common.h"
|
||||
#include "game-activity/GameActivityEvents.h"
|
||||
#include "game-text-input/gametextinput.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define GAMEACTIVITY_MAJOR_VERSION 2
|
||||
#define GAMEACTIVITY_MINOR_VERSION 0
|
||||
#define GAMEACTIVITY_BUGFIX_VERSION 2
|
||||
#define GAMEACTIVITY_PACKED_VERSION \
|
||||
ANDROID_GAMESDK_PACKED_VERSION(GAMEACTIVITY_MAJOR_VERSION, \
|
||||
GAMEACTIVITY_MINOR_VERSION, \
|
||||
GAMEACTIVITY_BUGFIX_VERSION)
|
||||
|
||||
/**
|
||||
* {@link GameActivityCallbacks}
|
||||
*/
|
||||
struct GameActivityCallbacks;
|
||||
|
||||
/**
|
||||
* This structure defines the native side of an android.app.GameActivity.
|
||||
* It is created by the framework, and handed to the application's native
|
||||
* code as it is being launched.
|
||||
*/
|
||||
typedef struct GameActivity {
|
||||
/**
|
||||
* Pointer to the callback function table of the native application.
|
||||
* You can set the functions here to your own callbacks. The callbacks
|
||||
* pointer itself here should not be changed; it is allocated and managed
|
||||
* for you by the framework.
|
||||
*/
|
||||
struct GameActivityCallbacks* callbacks;
|
||||
|
||||
/**
|
||||
* The global handle on the process's Java VM.
|
||||
*/
|
||||
JavaVM* vm;
|
||||
|
||||
/**
|
||||
* JNI context for the main thread of the app. Note that this field
|
||||
* can ONLY be used from the main thread of the process; that is, the
|
||||
* thread that calls into the GameActivityCallbacks.
|
||||
*/
|
||||
JNIEnv* env;
|
||||
|
||||
/**
|
||||
* The GameActivity object handle.
|
||||
*/
|
||||
jobject javaGameActivity;
|
||||
|
||||
/**
|
||||
* Path to this application's internal data directory.
|
||||
*/
|
||||
const char* internalDataPath;
|
||||
|
||||
/**
|
||||
* Path to this application's external (removable/mountable) data directory.
|
||||
*/
|
||||
const char* externalDataPath;
|
||||
|
||||
/**
|
||||
* The platform's SDK version code.
|
||||
*/
|
||||
int32_t sdkVersion;
|
||||
|
||||
/**
|
||||
* This is the native instance of the application. It is not used by
|
||||
* the framework, but can be set by the application to its own instance
|
||||
* state.
|
||||
*/
|
||||
void* instance;
|
||||
|
||||
/**
|
||||
* Pointer to the Asset Manager instance for the application. The
|
||||
* application uses this to access binary assets bundled inside its own .apk
|
||||
* file.
|
||||
*/
|
||||
AAssetManager* assetManager;
|
||||
|
||||
/**
|
||||
* Available starting with Honeycomb: path to the directory containing
|
||||
* the application's OBB files (if any). If the app doesn't have any
|
||||
* OBB files, this directory may not exist.
|
||||
*/
|
||||
const char* obbPath;
|
||||
} GameActivity;
|
||||
|
||||
/**
|
||||
* A function the user should call from their callback with the data, its length
|
||||
* and the library- supplied context.
|
||||
*/
|
||||
typedef void (*SaveInstanceStateRecallback)(const char* bytes, int len,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* These are the callbacks the framework makes into a native application.
|
||||
* All of these callbacks happen on the main thread of the application.
|
||||
* By default, all callbacks are NULL; set to a pointer to your own function
|
||||
* to have it called.
|
||||
*/
|
||||
typedef struct GameActivityCallbacks {
|
||||
/**
|
||||
* GameActivity has started. See Java documentation for Activity.onStart()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onStart)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* GameActivity has resumed. See Java documentation for Activity.onResume()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onResume)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* The framework is asking GameActivity to save its current instance state.
|
||||
* See the Java documentation for Activity.onSaveInstanceState() for more
|
||||
* information. The user should call the recallback with their data, its
|
||||
* length and the provided context; they retain ownership of the data. Note
|
||||
* that the saved state will be persisted, so it can not contain any active
|
||||
* entities (pointers to memory, file descriptors, etc).
|
||||
*/
|
||||
void (*onSaveInstanceState)(GameActivity* activity,
|
||||
SaveInstanceStateRecallback recallback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* GameActivity has paused. See Java documentation for Activity.onPause()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onPause)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* GameActivity has stopped. See Java documentation for Activity.onStop()
|
||||
* for more information.
|
||||
*/
|
||||
void (*onStop)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* GameActivity is being destroyed. See Java documentation for
|
||||
* Activity.onDestroy() for more information.
|
||||
*/
|
||||
void (*onDestroy)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Focus has changed in this GameActivity's window. This is often used,
|
||||
* for example, to pause a game when it loses input focus.
|
||||
*/
|
||||
void (*onWindowFocusChanged)(GameActivity* activity, bool hasFocus);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity has been created. You
|
||||
* can use the given native window object to start drawing.
|
||||
*/
|
||||
void (*onNativeWindowCreated)(GameActivity* activity,
|
||||
ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity has been resized. You should
|
||||
* retrieve the new size from the window and ensure that your rendering in
|
||||
* it now matches.
|
||||
*/
|
||||
void (*onNativeWindowResized)(GameActivity* activity, ANativeWindow* window,
|
||||
int32_t newWidth, int32_t newHeight);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity needs to be redrawn. To
|
||||
* avoid transient artifacts during screen changes (such resizing after
|
||||
* rotation), applications should not return from this function until they
|
||||
* have finished drawing their window in its current state.
|
||||
*/
|
||||
void (*onNativeWindowRedrawNeeded)(GameActivity* activity,
|
||||
ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* The drawing window for this native activity is going to be destroyed.
|
||||
* You MUST ensure that you do not touch the window object after returning
|
||||
* from this function: in the common case of drawing to the window from
|
||||
* another thread, that means the implementation of this callback must
|
||||
* properly synchronize with the other thread to stop its drawing before
|
||||
* returning from here.
|
||||
*/
|
||||
void (*onNativeWindowDestroyed)(GameActivity* activity,
|
||||
ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* The current device AConfiguration has changed. The new configuration can
|
||||
* be retrieved from assetManager.
|
||||
*/
|
||||
void (*onConfigurationChanged)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* The system is running low on memory. Use this callback to release
|
||||
* resources you do not need, to help the system avoid killing more
|
||||
* important processes.
|
||||
*/
|
||||
void (*onTrimMemory)(GameActivity* activity, int level);
|
||||
|
||||
/**
|
||||
* Callback called for every MotionEvent done on the GameActivity
|
||||
* SurfaceView. Ownership of `event` is maintained by the library and it is
|
||||
* only valid during the callback.
|
||||
*/
|
||||
bool (*onTouchEvent)(GameActivity* activity,
|
||||
const GameActivityMotionEvent* event);
|
||||
|
||||
/**
|
||||
* Callback called for every key down event on the GameActivity SurfaceView.
|
||||
* Ownership of `event` is maintained by the library and it is only valid
|
||||
* during the callback.
|
||||
*/
|
||||
bool (*onKeyDown)(GameActivity* activity,
|
||||
const GameActivityKeyEvent* event);
|
||||
|
||||
/**
|
||||
* Callback called for every key up event on the GameActivity SurfaceView.
|
||||
* Ownership of `event` is maintained by the library and it is only valid
|
||||
* during the callback.
|
||||
*/
|
||||
bool (*onKeyUp)(GameActivity* activity, const GameActivityKeyEvent* event);
|
||||
|
||||
/**
|
||||
* Callback called for every soft-keyboard text input event.
|
||||
* Ownership of `state` is maintained by the library and it is only valid
|
||||
* during the callback.
|
||||
*/
|
||||
void (*onTextInputEvent)(GameActivity* activity,
|
||||
const GameTextInputState* state);
|
||||
|
||||
/**
|
||||
* Callback called when WindowInsets of the main app window have changed.
|
||||
* Call GameActivity_getWindowInsets to retrieve the insets themselves.
|
||||
*/
|
||||
void (*onWindowInsetsChanged)(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Callback called when the rectangle in the window where the content
|
||||
* should be placed has changed.
|
||||
*/
|
||||
void (*onContentRectChanged)(GameActivity *activity, const ARect *rect);
|
||||
} GameActivityCallbacks;
|
||||
|
||||
/**
|
||||
* This is the function that must be in the native code to instantiate the
|
||||
* application's native activity. It is called with the activity instance (see
|
||||
* above); if the code is being instantiated from a previously saved instance,
|
||||
* the savedState will be non-NULL and point to the saved data. You must make
|
||||
* any copy of this data you need -- it will be released after you return from
|
||||
* this function.
|
||||
*/
|
||||
typedef void GameActivity_createFunc(GameActivity* activity, void* savedState,
|
||||
size_t savedStateSize);
|
||||
|
||||
/**
|
||||
* The name of the function that NativeInstance looks for when launching its
|
||||
* native code. This is the default function that is used, you can specify
|
||||
* "android.app.func_name" string meta-data in your manifest to use a different
|
||||
* function.
|
||||
*/
|
||||
extern GameActivity_createFunc GameActivity_onCreate_C;
|
||||
|
||||
/**
|
||||
* Finish the given activity. Its finish() method will be called, causing it
|
||||
* to be stopped and destroyed. Note that this method can be called from
|
||||
* *any* thread; it will send a message to the main thread of the process
|
||||
* where the Java finish call will take place.
|
||||
*/
|
||||
void GameActivity_finish(GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Flags for GameActivity_setWindowFlags,
|
||||
* as per the Java API at android.view.WindowManager.LayoutParams.
|
||||
*/
|
||||
enum GameActivitySetWindowFlags {
|
||||
/**
|
||||
* As long as this window is visible to the user, allow the lock
|
||||
* screen to activate while the screen is on. This can be used
|
||||
* independently, or in combination with {@link
|
||||
* GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} and/or {@link
|
||||
* GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
|
||||
/** Everything behind this window will be dimmed. */
|
||||
GAMEACTIVITY_FLAG_DIM_BEHIND = 0x00000002,
|
||||
/**
|
||||
* Blur everything behind this window.
|
||||
* @deprecated Blurring is no longer supported.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_BLUR_BEHIND = 0x00000004,
|
||||
/**
|
||||
* This window won't ever get key input focus, so the
|
||||
* user can not send key or other button events to it. Those will
|
||||
* instead go to whatever focusable window is behind it. This flag
|
||||
* will also enable {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL} whether or not
|
||||
* that is explicitly set.
|
||||
*
|
||||
* Setting this flag also implies that the window will not need to
|
||||
* interact with
|
||||
* a soft input method, so it will be Z-ordered and positioned
|
||||
* independently of any active input method (typically this means it
|
||||
* gets Z-ordered on top of the input method, so it can use the full
|
||||
* screen for its content and cover the input method if needed. You
|
||||
* can use {@link GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM} to modify this
|
||||
* behavior.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_NOT_FOCUSABLE = 0x00000008,
|
||||
/** This window can never receive touch events. */
|
||||
GAMEACTIVITY_FLAG_NOT_TOUCHABLE = 0x00000010,
|
||||
/**
|
||||
* Even when this window is focusable (its
|
||||
* {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set), allow any pointer
|
||||
* events outside of the window to be sent to the windows behind it.
|
||||
* Otherwise it will consume all pointer events itself, regardless of
|
||||
* whether they are inside of the window.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL = 0x00000020,
|
||||
/**
|
||||
* When set, if the device is asleep when the touch
|
||||
* screen is pressed, you will receive this first touch event. Usually
|
||||
* the first touch event is consumed by the system since the user can
|
||||
* not see what they are pressing on.
|
||||
*
|
||||
* @deprecated This flag has no effect.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
|
||||
/**
|
||||
* As long as this window is visible to the user, keep
|
||||
* the device's screen turned on and bright.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_KEEP_SCREEN_ON = 0x00000080,
|
||||
/**
|
||||
* Place the window within the entire screen, ignoring
|
||||
* decorations around the border (such as the status bar). The
|
||||
* window must correctly position its contents to take the screen
|
||||
* decoration into account.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN = 0x00000100,
|
||||
/** Allows the window to extend outside of the screen. */
|
||||
GAMEACTIVITY_FLAG_LAYOUT_NO_LIMITS = 0x00000200,
|
||||
/**
|
||||
* Hide all screen decorations (such as the status
|
||||
* bar) while this window is displayed. This allows the window to
|
||||
* use the entire display space for itself -- the status bar will
|
||||
* be hidden when an app window with this flag set is on the top
|
||||
* layer. A fullscreen window will ignore a value of {@link
|
||||
* GAMEACTIVITY_SOFT_INPUT_ADJUST_RESIZE}; the window will stay
|
||||
* fullscreen and will not resize.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_FULLSCREEN = 0x00000400,
|
||||
/**
|
||||
* Override {@link GAMEACTIVITY_FLAG_FULLSCREEN} and force the
|
||||
* screen decorations (such as the status bar) to be shown.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_FORCE_NOT_FULLSCREEN = 0x00000800,
|
||||
/**
|
||||
* Turn on dithering when compositing this window to
|
||||
* the screen.
|
||||
* @deprecated This flag is no longer used.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_DITHER = 0x00001000,
|
||||
/**
|
||||
* Treat the content of the window as secure, preventing
|
||||
* it from appearing in screenshots or from being viewed on non-secure
|
||||
* displays.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SECURE = 0x00002000,
|
||||
/**
|
||||
* A special mode where the layout parameters are used
|
||||
* to perform scaling of the surface when it is composited to the
|
||||
* screen.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SCALED = 0x00004000,
|
||||
/**
|
||||
* Intended for windows that will often be used when the user is
|
||||
* holding the screen against their face, it will aggressively
|
||||
* filter the event stream to prevent unintended presses in this
|
||||
* situation that may not be desired for a particular window, when
|
||||
* such an event stream is detected, the application will receive
|
||||
* a {@link AMOTION_EVENT_ACTION_CANCEL} to indicate this so
|
||||
* applications can handle this accordingly by taking no action on
|
||||
* the event until the finger is released.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_IGNORE_CHEEK_PRESSES = 0x00008000,
|
||||
/**
|
||||
* A special option only for use in combination with
|
||||
* {@link GAMEACTIVITY_FLAG_LAYOUT_IN_SCREEN}. When requesting layout in
|
||||
* the screen your window may appear on top of or behind screen decorations
|
||||
* such as the status bar. By also including this flag, the window
|
||||
* manager will report the inset rectangle needed to ensure your
|
||||
* content is not covered by screen decorations.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_LAYOUT_INSET_DECOR = 0x00010000,
|
||||
/**
|
||||
* Invert the state of {@link GAMEACTIVITY_FLAG_NOT_FOCUSABLE} with
|
||||
* respect to how this window interacts with the current method.
|
||||
* That is, if FLAG_NOT_FOCUSABLE is set and this flag is set,
|
||||
* then the window will behave as if it needs to interact with the
|
||||
* input method and thus be placed behind/away from it; if {@link
|
||||
* GAMEACTIVITY_FLAG_NOT_FOCUSABLE} is not set and this flag is set,
|
||||
* then the window will behave as if it doesn't need to interact
|
||||
* with the input method and can be placed to use more space and
|
||||
* cover the input method.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_ALT_FOCUSABLE_IM = 0x00020000,
|
||||
/**
|
||||
* If you have set {@link GAMEACTIVITY_FLAG_NOT_TOUCH_MODAL}, you
|
||||
* can set this flag to receive a single special MotionEvent with
|
||||
* the action
|
||||
* {@link AMOTION_EVENT_ACTION_OUTSIDE} for
|
||||
* touches that occur outside of your window. Note that you will not
|
||||
* receive the full down/move/up gesture, only the location of the
|
||||
* first down as an {@link AMOTION_EVENT_ACTION_OUTSIDE}.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
|
||||
/**
|
||||
* Special flag to let windows be shown when the screen
|
||||
* is locked. This will let application windows take precedence over
|
||||
* key guard or any other lock screens. Can be used with
|
||||
* {@link GAMEACTIVITY_FLAG_KEEP_SCREEN_ON} to turn screen on and display
|
||||
* windows directly before showing the key guard window. Can be used with
|
||||
* {@link GAMEACTIVITY_FLAG_DISMISS_KEYGUARD} to automatically fully
|
||||
* dismisss non-secure keyguards. This flag only applies to the top-most
|
||||
* full-screen window.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED = 0x00080000,
|
||||
/**
|
||||
* Ask that the system wallpaper be shown behind
|
||||
* your window. The window surface must be translucent to be able
|
||||
* to actually see the wallpaper behind it; this flag just ensures
|
||||
* that the wallpaper surface will be there if this window actually
|
||||
* has translucent regions.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_SHOW_WALLPAPER = 0x00100000,
|
||||
/**
|
||||
* When set as a window is being added or made
|
||||
* visible, once the window has been shown then the system will
|
||||
* poke the power manager's user activity (as if the user had woken
|
||||
* up the device) to turn the screen on.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_TURN_SCREEN_ON = 0x00200000,
|
||||
/**
|
||||
* When set the window will cause the keyguard to
|
||||
* be dismissed, only if it is not a secure lock keyguard. Because such
|
||||
* a keyguard is not needed for security, it will never re-appear if
|
||||
* the user navigates to another window (in contrast to
|
||||
* {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED}, which will only temporarily
|
||||
* hide both secure and non-secure keyguards but ensure they reappear
|
||||
* when the user moves to another UI that doesn't hide them).
|
||||
* If the keyguard is currently active and is secure (requires an
|
||||
* unlock pattern) than the user will still need to confirm it before
|
||||
* seeing this window, unless {@link GAMEACTIVITY_FLAG_SHOW_WHEN_LOCKED} has
|
||||
* also been set.
|
||||
*/
|
||||
GAMEACTIVITY_FLAG_DISMISS_KEYGUARD = 0x00400000,
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the window flags of the given activity. Calls getWindow().setFlags()
|
||||
* of the given activity.
|
||||
* Note that some flags must be set before the window decoration is created,
|
||||
* see
|
||||
* https://developer.android.com/reference/android/view/Window#setFlags(int,%20int).
|
||||
* Note also that this method can be called from
|
||||
* *any* thread; it will send a message to the main thread of the process
|
||||
* where the Java finish call will take place.
|
||||
*/
|
||||
void GameActivity_setWindowFlags(GameActivity* activity, uint32_t addFlags,
|
||||
uint32_t removeFlags);
|
||||
|
||||
/**
|
||||
* Flags for GameActivity_showSoftInput; see the Java InputMethodManager
|
||||
* API for documentation.
|
||||
*/
|
||||
enum GameActivityShowSoftInputFlags {
|
||||
/**
|
||||
* Implicit request to show the input window, not as the result
|
||||
* of a direct request by the user.
|
||||
*/
|
||||
GAMEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,
|
||||
|
||||
/**
|
||||
* The user has forced the input method open (such as by
|
||||
* long-pressing menu) so it should not be closed until they
|
||||
* explicitly do so.
|
||||
*/
|
||||
GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the IME while in the given activity. Calls
|
||||
* InputMethodManager.showSoftInput() for the given activity. Note that this
|
||||
* method can be called from *any* thread; it will send a message to the main
|
||||
* thread of the process where the Java call will take place.
|
||||
*/
|
||||
void GameActivity_showSoftInput(GameActivity* activity, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Set the text entry state (see documentation of the GameTextInputState struct
|
||||
* in the Game Text Input library reference).
|
||||
*
|
||||
* Ownership of the state is maintained by the caller.
|
||||
*/
|
||||
void GameActivity_setTextInputState(GameActivity* activity,
|
||||
const GameTextInputState* state);
|
||||
|
||||
/**
|
||||
* Get the last-received text entry state (see documentation of the
|
||||
* GameTextInputState struct in the Game Text Input library reference).
|
||||
*
|
||||
*/
|
||||
void GameActivity_getTextInputState(GameActivity* activity,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* Get a pointer to the GameTextInput library instance.
|
||||
*/
|
||||
GameTextInput* GameActivity_getTextInput(const GameActivity* activity);
|
||||
|
||||
/**
|
||||
* Flags for GameActivity_hideSoftInput; see the Java InputMethodManager
|
||||
* API for documentation.
|
||||
*/
|
||||
enum GameActivityHideSoftInputFlags {
|
||||
/**
|
||||
* The soft input window should only be hidden if it was not
|
||||
* explicitly shown by the user.
|
||||
*/
|
||||
GAMEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,
|
||||
/**
|
||||
* The soft input window should normally be hidden, unless it was
|
||||
* originally shown with {@link GAMEACTIVITY_SHOW_SOFT_INPUT_FORCED}.
|
||||
*/
|
||||
GAMEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the IME while in the given activity. Calls
|
||||
* InputMethodManager.hideSoftInput() for the given activity. Note that this
|
||||
* method can be called from *any* thread; it will send a message to the main
|
||||
* thread of the process where the Java finish call will take place.
|
||||
*/
|
||||
void GameActivity_hideSoftInput(GameActivity* activity, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Get the current window insets of the particular component. See
|
||||
* https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type
|
||||
* for more details.
|
||||
* You can use these insets to influence what you show on the screen.
|
||||
*/
|
||||
void GameActivity_getWindowInsets(GameActivity* activity,
|
||||
GameCommonInsetsType type, ARect* insets);
|
||||
|
||||
/**
|
||||
* Set options on how the IME behaves when it is requested for text input.
|
||||
* See
|
||||
* https://developer.android.com/reference/android/view/inputmethod/EditorInfo
|
||||
* for the meaning of inputType, actionId and imeOptions.
|
||||
*
|
||||
* Note that this function will attach the current thread to the JVM if it is
|
||||
* not already attached, so the caller must detach the thread from the JVM
|
||||
* before the thread is destroyed using DetachCurrentThread.
|
||||
*/
|
||||
void GameActivity_setImeEditorInfo(GameActivity* activity, int inputType,
|
||||
int actionId, int imeOptions);
|
||||
|
||||
/**
|
||||
* These are getters for Configuration class members. They may be called from
|
||||
* any thread.
|
||||
*/
|
||||
int GameActivity_getOrientation(GameActivity* activity);
|
||||
int GameActivity_getColorMode(GameActivity* activity);
|
||||
int GameActivity_getDensityDpi(GameActivity* activity);
|
||||
float GameActivity_getFontScale(GameActivity* activity);
|
||||
int GameActivity_getFontWeightAdjustment(GameActivity* activity);
|
||||
int GameActivity_getHardKeyboardHidden(GameActivity* activity);
|
||||
int GameActivity_getKeyboard(GameActivity* activity);
|
||||
int GameActivity_getKeyboardHidden(GameActivity* activity);
|
||||
int GameActivity_getMcc(GameActivity* activity);
|
||||
int GameActivity_getMnc(GameActivity* activity);
|
||||
int GameActivity_getNavigation(GameActivity* activity);
|
||||
int GameActivity_getNavigationHidden(GameActivity* activity);
|
||||
int GameActivity_getOrientation(GameActivity* activity);
|
||||
int GameActivity_getScreenHeightDp(GameActivity* activity);
|
||||
int GameActivity_getScreenLayout(GameActivity* activity);
|
||||
int GameActivity_getScreenWidthDp(GameActivity* activity);
|
||||
int GameActivity_getSmallestScreenWidthDp(GameActivity* activity);
|
||||
int GameActivity_getTouchscreen(GameActivity* activity);
|
||||
int GameActivity_getUIMode(GameActivity* activity);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_H
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* 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)
|
||||
};
|
||||
}
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup GameActivity Game Activity Events
|
||||
* The interface to use Game Activity Events.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file GameActivityEvents.h
|
||||
*/
|
||||
#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
|
||||
#define ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
|
||||
|
||||
#include <android/input.h>
|
||||
#include <jni.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The maximum number of axes supported in an Android MotionEvent.
|
||||
* See https://developer.android.com/ndk/reference/group/input.
|
||||
*/
|
||||
#define GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48
|
||||
|
||||
/**
|
||||
* \brief Describe information about a pointer, found in a
|
||||
* GameActivityMotionEvent.
|
||||
*
|
||||
* You can read values directly from this structure, or use helper functions
|
||||
* (`GameActivityPointerAxes_getX`, `GameActivityPointerAxes_getY` and
|
||||
* `GameActivityPointerAxes_getAxisValue`).
|
||||
*
|
||||
* The X axis and Y axis are enabled by default but any other axis that you want
|
||||
* to read **must** be enabled first, using
|
||||
* `GameActivityPointerAxes_enableAxis`.
|
||||
*
|
||||
* \see GameActivityMotionEvent
|
||||
*/
|
||||
typedef struct GameActivityPointerAxes {
|
||||
int32_t id;
|
||||
int32_t toolType;
|
||||
float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
|
||||
float rawX;
|
||||
float rawY;
|
||||
} GameActivityPointerAxes;
|
||||
|
||||
/** \brief Get the toolType of the pointer. */
|
||||
inline int32_t GameActivityPointerAxes_getToolType(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->toolType;
|
||||
}
|
||||
|
||||
/** \brief Get the current X coordinate of the pointer. */
|
||||
inline float GameActivityPointerAxes_getX(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X];
|
||||
}
|
||||
|
||||
/** \brief Get the current Y coordinate of the pointer. */
|
||||
inline float GameActivityPointerAxes_getY(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y];
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Enable the specified axis, so that its value is reported in the
|
||||
* GameActivityPointerAxes structures stored in a motion event.
|
||||
*
|
||||
* You must enable any axis that you want to read, apart from
|
||||
* `AMOTION_EVENT_AXIS_X` and `AMOTION_EVENT_AXIS_Y` that are enabled by
|
||||
* default.
|
||||
*
|
||||
* If the axis index is out of range, nothing is done.
|
||||
*/
|
||||
void GameActivityPointerAxes_enableAxis(int32_t axis);
|
||||
|
||||
/**
|
||||
* \brief Disable the specified axis. Its value won't be reported in the
|
||||
* GameActivityPointerAxes structures stored in a motion event anymore.
|
||||
*
|
||||
* Apart from X and Y, any axis that you want to read **must** be enabled first,
|
||||
* using `GameActivityPointerAxes_enableAxis`.
|
||||
*
|
||||
* If the axis index is out of range, nothing is done.
|
||||
*/
|
||||
void GameActivityPointerAxes_disableAxis(int32_t axis);
|
||||
|
||||
/**
|
||||
* \brief Get the value of the requested axis.
|
||||
*
|
||||
* Apart from X and Y, any axis that you want to read **must** be enabled first,
|
||||
* using `GameActivityPointerAxes_enableAxis`.
|
||||
*
|
||||
* Find the valid enums for the axis (`AMOTION_EVENT_AXIS_X`,
|
||||
* `AMOTION_EVENT_AXIS_Y`, `AMOTION_EVENT_AXIS_PRESSURE`...)
|
||||
* in https://developer.android.com/ndk/reference/group/input.
|
||||
*
|
||||
* @param pointerInfo The structure containing information about the pointer,
|
||||
* obtained from GameActivityMotionEvent.
|
||||
* @param axis The axis to get the value from
|
||||
* @return The value of the axis, or 0 if the axis is invalid or was not
|
||||
* enabled.
|
||||
*/
|
||||
float GameActivityPointerAxes_getAxisValue(
|
||||
const GameActivityPointerAxes* pointerInfo, int32_t axis);
|
||||
|
||||
inline float GameActivityPointerAxes_getPressure(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_PRESSURE);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getSize(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_SIZE);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getTouchMajor(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOUCH_MAJOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getTouchMinor(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOUCH_MINOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getToolMajor(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOOL_MAJOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getToolMinor(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_TOOL_MINOR);
|
||||
}
|
||||
|
||||
inline float GameActivityPointerAxes_getOrientation(
|
||||
const GameActivityPointerAxes* pointerInfo) {
|
||||
return GameActivityPointerAxes_getAxisValue(pointerInfo,
|
||||
AMOTION_EVENT_AXIS_ORIENTATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of pointers returned inside a motion event.
|
||||
*/
|
||||
#if (defined GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE)
|
||||
#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT \
|
||||
GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE
|
||||
#else
|
||||
#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Describe a motion event that happened on the GameActivity SurfaceView.
|
||||
*
|
||||
* This is 1:1 mapping to the information contained in a Java `MotionEvent`
|
||||
* (see https://developer.android.com/reference/android/view/MotionEvent).
|
||||
*/
|
||||
typedef struct GameActivityMotionEvent {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
|
||||
int32_t actionButton;
|
||||
int32_t buttonState;
|
||||
int32_t classification;
|
||||
int32_t edgeFlags;
|
||||
|
||||
uint32_t pointerCount;
|
||||
GameActivityPointerAxes
|
||||
pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT];
|
||||
|
||||
int historySize;
|
||||
int64_t* historicalEventTimesMillis;
|
||||
int64_t* historicalEventTimesNanos;
|
||||
float* historicalAxisValues;
|
||||
|
||||
float precisionX;
|
||||
float precisionY;
|
||||
} GameActivityMotionEvent;
|
||||
|
||||
float GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
const GameActivityMotionEvent* event, int axis, int pointerIndex,
|
||||
int historyPos);
|
||||
|
||||
inline int GameActivityMotionEvent_getHistorySize(
|
||||
const GameActivityMotionEvent* event) {
|
||||
return event->historySize;
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalX(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_X, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalY(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_Y, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalPressure(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalSize(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_SIZE, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalTouchMajor(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalTouchMinor(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalToolMajor(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalToolMinor(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
inline float GameActivityMotionEvent_getHistoricalOrientation(
|
||||
const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
|
||||
return GameActivityMotionEvent_getHistoricalAxisValue(
|
||||
event, AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historyPos);
|
||||
}
|
||||
|
||||
/** \brief Handle the freeing of the GameActivityMotionEvent struct. */
|
||||
void GameActivityMotionEvent_destroy(GameActivityMotionEvent* c_event);
|
||||
|
||||
/**
|
||||
* \brief Convert a Java `MotionEvent` to a `GameActivityMotionEvent`.
|
||||
*
|
||||
* This is done automatically by the GameActivity: see `onTouchEvent` to set
|
||||
* a callback to consume the received events.
|
||||
* This function can be used if you re-implement events handling in your own
|
||||
* activity.
|
||||
* Ownership of out_event is maintained by the caller.
|
||||
*/
|
||||
void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityMotionEvent* out_event);
|
||||
|
||||
/**
|
||||
* \brief Describe a key event that happened on the GameActivity SurfaceView.
|
||||
*
|
||||
* This is 1:1 mapping to the information contained in a Java `KeyEvent`
|
||||
* (see https://developer.android.com/reference/android/view/KeyEvent).
|
||||
* The only exception is the event times, which are reported as
|
||||
* nanoseconds in this struct.
|
||||
*/
|
||||
typedef struct GameActivityKeyEvent {
|
||||
int32_t deviceId;
|
||||
int32_t source;
|
||||
int32_t action;
|
||||
|
||||
int64_t eventTime;
|
||||
int64_t downTime;
|
||||
|
||||
int32_t flags;
|
||||
int32_t metaState;
|
||||
|
||||
int32_t modifiers;
|
||||
int32_t repeatCount;
|
||||
int32_t keyCode;
|
||||
int32_t scanCode;
|
||||
//int32_t unicodeChar;
|
||||
} GameActivityKeyEvent;
|
||||
|
||||
/**
|
||||
* \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`.
|
||||
*
|
||||
* This is done automatically by the GameActivity: see `onKeyUp` and `onKeyDown`
|
||||
* to set a callback to consume the received events.
|
||||
* This function can be used if you re-implement events handling in your own
|
||||
* activity.
|
||||
* Ownership of out_event is maintained by the caller.
|
||||
*/
|
||||
void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent,
|
||||
GameActivityKeyEvent* out_event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
|
||||
#define ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
|
||||
|
||||
#define LOG_TAG "GameActivity"
|
||||
#include <android/log.h>
|
||||
|
||||
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);
|
||||
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);
|
||||
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);
|
||||
#ifdef NDEBUG
|
||||
#define ALOGV(...)
|
||||
#else
|
||||
#define ALOGV(...) \
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
|
||||
#endif
|
||||
|
||||
/* Returns 2nd arg. Used to substitute default value if caller's vararg list
|
||||
* is empty.
|
||||
*/
|
||||
#define __android_second(first, second, ...) second
|
||||
|
||||
/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
|
||||
* returns nothing.
|
||||
*/
|
||||
#define __android_rest(first, ...) , ##__VA_ARGS__
|
||||
|
||||
#define android_printAssert(cond, tag, fmt...) \
|
||||
__android_log_assert(cond, tag, \
|
||||
__android_second(0, ##fmt, NULL) __android_rest(fmt))
|
||||
|
||||
#define CONDITION(cond) (__builtin_expect((cond) != 0, 0))
|
||||
|
||||
#ifndef LOG_ALWAYS_FATAL_IF
|
||||
#define LOG_ALWAYS_FATAL_IF(cond, ...) \
|
||||
((CONDITION(cond)) \
|
||||
? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \
|
||||
: (void)0)
|
||||
#endif
|
||||
|
||||
#ifndef LOG_ALWAYS_FATAL
|
||||
#define LOG_ALWAYS_FATAL(...) \
|
||||
(((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Simplified macro to send a warning system log message using current LOG_TAG.
|
||||
*/
|
||||
#ifndef SLOGW
|
||||
#define SLOGW(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
|
||||
#endif
|
||||
|
||||
#ifndef SLOGW_IF
|
||||
#define SLOGW_IF(cond, ...) \
|
||||
((__predict_false(cond)) \
|
||||
? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
|
||||
: (void)0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
|
||||
* are stripped out of release builds.
|
||||
*/
|
||||
#if LOG_NDEBUG
|
||||
|
||||
#ifndef LOG_FATAL_IF
|
||||
#define LOG_FATAL_IF(cond, ...) ((void)0)
|
||||
#endif
|
||||
#ifndef LOG_FATAL
|
||||
#define LOG_FATAL(...) ((void)0)
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifndef LOG_FATAL_IF
|
||||
#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__)
|
||||
#endif
|
||||
#ifndef LOG_FATAL
|
||||
#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Assertion that generates a log message when the assertion fails.
|
||||
* Stripped out of release builds. Uses the current LOG_TAG.
|
||||
*/
|
||||
#ifndef ALOG_ASSERT
|
||||
#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#define LOG_TRACE(...)
|
||||
|
||||
#endif // ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
|
||||
+732
@@ -0,0 +1,732 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
+507
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @addtogroup android_native_app_glue Native App Glue library
|
||||
* The glue library to interface your game loop with GameActivity.
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include <android/configuration.h>
|
||||
#include <android/looper.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
|
||||
#include "game-activity/GameActivity.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The GameActivity interface provided by <game-activity/GameActivity.h>
|
||||
* is based on a set of application-provided callbacks that will be called
|
||||
* by the Activity's main thread when certain events occur.
|
||||
*
|
||||
* This means that each one of this callbacks _should_ _not_ block, or they
|
||||
* risk having the system force-close the application. This programming
|
||||
* model is direct, lightweight, but constraining.
|
||||
*
|
||||
* The 'android_native_app_glue' static library is used to provide a different
|
||||
* execution model where the application can implement its own main event
|
||||
* loop in a different thread instead. Here's how it works:
|
||||
*
|
||||
* 1/ The application must provide a function named "android_main()" that
|
||||
* will be called when the activity is created, in a new thread that is
|
||||
* distinct from the activity's main thread.
|
||||
*
|
||||
* 2/ android_main() receives a pointer to a valid "android_app" structure
|
||||
* that contains references to other important objects, e.g. the
|
||||
* GameActivity obejct instance the application is running in.
|
||||
*
|
||||
* 3/ the "android_app" object holds an ALooper instance that already
|
||||
* listens to activity lifecycle events (e.g. "pause", "resume").
|
||||
* See APP_CMD_XXX declarations below.
|
||||
*
|
||||
* This corresponds to an ALooper identifier returned by
|
||||
* ALooper_pollOnce with value LOOPER_ID_MAIN.
|
||||
*
|
||||
* Your application can use the same ALooper to listen to additional
|
||||
* file-descriptors. They can either be callback based, or with return
|
||||
* identifiers starting with LOOPER_ID_USER.
|
||||
*
|
||||
* 4/ Whenever you receive a LOOPER_ID_MAIN event,
|
||||
* the returned data will point to an android_poll_source structure. You
|
||||
* can call the process() function on it, and fill in android_app->onAppCmd
|
||||
* to be called for your own processing of the event.
|
||||
*
|
||||
* Alternatively, you can call the low-level functions to read and process
|
||||
* the data directly... look at the process_cmd() and process_input()
|
||||
* implementations in the glue to see how to do this.
|
||||
*
|
||||
* See the sample named "native-activity" that comes with the NDK with a
|
||||
* full usage example. Also look at the documentation of GameActivity.
|
||||
*/
|
||||
|
||||
struct android_app;
|
||||
|
||||
/**
|
||||
* Data associated with an ALooper fd that will be returned as the "outData"
|
||||
* when that source has data ready.
|
||||
*/
|
||||
struct android_poll_source {
|
||||
/**
|
||||
* The identifier of this source. May be LOOPER_ID_MAIN or
|
||||
* LOOPER_ID_INPUT.
|
||||
*/
|
||||
int32_t id;
|
||||
|
||||
/** The android_app this ident is associated with. */
|
||||
struct android_app* app;
|
||||
|
||||
/**
|
||||
* Function to call to perform the standard processing of data from
|
||||
* this source.
|
||||
*/
|
||||
void (*process)(struct android_app* app,
|
||||
struct android_poll_source* source);
|
||||
};
|
||||
|
||||
struct android_input_buffer {
|
||||
/**
|
||||
* Pointer to a read-only array of GameActivityMotionEvent.
|
||||
* Only the first motionEventsCount events are valid.
|
||||
*/
|
||||
GameActivityMotionEvent *motionEvents;
|
||||
|
||||
/**
|
||||
* The number of valid motion events in `motionEvents`.
|
||||
*/
|
||||
uint64_t motionEventsCount;
|
||||
|
||||
/**
|
||||
* The size of the `motionEvents` buffer.
|
||||
*/
|
||||
uint64_t motionEventsBufferSize;
|
||||
|
||||
/**
|
||||
* Pointer to a read-only array of GameActivityKeyEvent.
|
||||
* Only the first keyEventsCount events are valid.
|
||||
*/
|
||||
GameActivityKeyEvent *keyEvents;
|
||||
|
||||
/**
|
||||
* The number of valid "Key" events in `keyEvents`.
|
||||
*/
|
||||
uint64_t keyEventsCount;
|
||||
|
||||
/**
|
||||
* The size of the `keyEvents` buffer.
|
||||
*/
|
||||
uint64_t keyEventsBufferSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function pointer declaration for the filtering of key events.
|
||||
* A function with this signature should be passed to
|
||||
* android_app_set_key_event_filter and return false for any events that should
|
||||
* not be handled by android_native_app_glue. These events will be handled by
|
||||
* the system instead.
|
||||
*/
|
||||
typedef bool (*android_key_event_filter)(const GameActivityKeyEvent*);
|
||||
|
||||
/**
|
||||
* Function pointer definition for the filtering of motion events.
|
||||
* A function with this signature should be passed to
|
||||
* android_app_set_motion_event_filter and return false for any events that
|
||||
* should not be handled by android_native_app_glue. These events will be
|
||||
* handled by the system instead.
|
||||
*/
|
||||
typedef bool (*android_motion_event_filter)(const GameActivityMotionEvent*);
|
||||
|
||||
/**
|
||||
* This is the interface for the standard glue code of a threaded
|
||||
* application. In this model, the application's code is running
|
||||
* in its own thread separate from the main thread of the process.
|
||||
* It is not required that this thread be associated with the Java
|
||||
* VM, although it will need to be in order to make JNI calls any
|
||||
* Java objects.
|
||||
*/
|
||||
struct android_app {
|
||||
/**
|
||||
* An optional pointer to application-defined state.
|
||||
*/
|
||||
void* userData;
|
||||
|
||||
/**
|
||||
* A required callback for processing main app commands (`APP_CMD_*`).
|
||||
* This is called each frame if there are app commands that need processing.
|
||||
*/
|
||||
void (*onAppCmd)(struct android_app* app, int32_t cmd);
|
||||
|
||||
/** The GameActivity object instance that this app is running in. */
|
||||
GameActivity* activity;
|
||||
|
||||
/** The current configuration the app is running in. */
|
||||
AConfiguration* config;
|
||||
|
||||
/**
|
||||
* The last activity saved state, as provided at creation time.
|
||||
* It is NULL if there was no state. You can use this as you need; the
|
||||
* memory will remain around until you call android_app_exec_cmd() for
|
||||
* APP_CMD_RESUME, at which point it will be freed and savedState set to
|
||||
* NULL. These variables should only be changed when processing a
|
||||
* APP_CMD_SAVE_STATE, at which point they will be initialized to NULL and
|
||||
* you can malloc your state and place the information here. In that case
|
||||
* the memory will be freed for you later.
|
||||
*/
|
||||
void* savedState;
|
||||
|
||||
/**
|
||||
* The size of the activity saved state. It is 0 if `savedState` is NULL.
|
||||
*/
|
||||
size_t savedStateSize;
|
||||
|
||||
/** The ALooper associated with the app's thread. */
|
||||
ALooper* looper;
|
||||
|
||||
/** When non-NULL, this is the window surface that the app can draw in. */
|
||||
ANativeWindow* window;
|
||||
|
||||
/**
|
||||
* Current content rectangle of the window; this is the area where the
|
||||
* window's content should be placed to be seen by the user.
|
||||
*/
|
||||
ARect contentRect;
|
||||
|
||||
/**
|
||||
* Current state of the app's activity. May be either APP_CMD_START,
|
||||
* APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP.
|
||||
*/
|
||||
int activityState;
|
||||
|
||||
/**
|
||||
* This is non-zero when the application's GameActivity is being
|
||||
* destroyed and waiting for the app thread to complete.
|
||||
*/
|
||||
int destroyRequested;
|
||||
|
||||
#define NATIVE_APP_GLUE_MAX_INPUT_BUFFERS 2
|
||||
|
||||
/**
|
||||
* This is used for buffering input from GameActivity. Once ready, the
|
||||
* application thread switches the buffers and processes what was
|
||||
* accumulated.
|
||||
*/
|
||||
struct android_input_buffer inputBuffers[NATIVE_APP_GLUE_MAX_INPUT_BUFFERS];
|
||||
|
||||
int currentInputBuffer;
|
||||
|
||||
/**
|
||||
* 0 if no text input event is outstanding, 1 if it is.
|
||||
* Use `GameActivity_getTextInputState` to get information
|
||||
* about the text entered by the user.
|
||||
*/
|
||||
int textInputState;
|
||||
|
||||
// Below are "private" implementation of the glue code.
|
||||
/** @cond INTERNAL */
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
|
||||
int msgread;
|
||||
int msgwrite;
|
||||
|
||||
pthread_t thread;
|
||||
|
||||
struct android_poll_source cmdPollSource;
|
||||
|
||||
int running;
|
||||
int stateSaved;
|
||||
int destroyed;
|
||||
int redrawNeeded;
|
||||
ANativeWindow* pendingWindow;
|
||||
ARect pendingContentRect;
|
||||
|
||||
android_key_event_filter keyEventFilter;
|
||||
android_motion_event_filter motionEventFilter;
|
||||
|
||||
// When new input is received we set both of these flags and use the looper to
|
||||
// wake up the application mainloop.
|
||||
//
|
||||
// To avoid spamming the mainloop with wake ups from lots of input though we
|
||||
// don't sent a wake up if the inputSwapPending flag is already set. (i.e.
|
||||
// we already expect input to be processed in a finite amount of time due to
|
||||
// our previous wake up)
|
||||
//
|
||||
// When a wake up is received then we will check this flag (clearing it
|
||||
// at the same time). If it was set then an InputAvailable event is sent to
|
||||
// the application - which should lead to all input being processed within
|
||||
// a finite amount of time.
|
||||
//
|
||||
// The next time android_app_swap_input_buffers is called, both flags will be
|
||||
// cleared.
|
||||
//
|
||||
// NB: both of these should only be read with the app mutex held
|
||||
bool inputAvailableWakeUp;
|
||||
bool inputSwapPending;
|
||||
|
||||
/** @endcond */
|
||||
};
|
||||
|
||||
/**
|
||||
* Looper ID of commands coming from the app's main thread, an AInputQueue or
|
||||
* user-defined sources.
|
||||
*/
|
||||
enum NativeAppGlueLooperId {
|
||||
/**
|
||||
* Looper data ID of commands coming from the app's main thread, which
|
||||
* is returned as an identifier from ALooper_pollOnce(). The data for this
|
||||
* identifier is a pointer to an android_poll_source structure.
|
||||
* These can be retrieved and processed with android_app_read_cmd()
|
||||
* and android_app_exec_cmd().
|
||||
*/
|
||||
LOOPER_ID_MAIN = 1,
|
||||
|
||||
/**
|
||||
* Unused. Reserved for future use when usage of AInputQueue will be
|
||||
* supported.
|
||||
*/
|
||||
LOOPER_ID_INPUT = 2,
|
||||
|
||||
/**
|
||||
* Start of user-defined ALooper identifiers.
|
||||
*/
|
||||
LOOPER_ID_USER = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Commands passed from the application's main Java thread to the game's thread.
|
||||
*/
|
||||
enum NativeAppGlueAppCmd {
|
||||
/**
|
||||
* Unused. Reserved for future use when usage of AInputQueue will be
|
||||
* supported.
|
||||
*/
|
||||
UNUSED_APP_CMD_INPUT_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: a new ANativeWindow is ready for use. Upon
|
||||
* receiving this command, android_app->window will contain the new window
|
||||
* surface.
|
||||
*/
|
||||
APP_CMD_INIT_WINDOW,
|
||||
|
||||
/**
|
||||
* Command from main thread: the existing ANativeWindow needs to be
|
||||
* terminated. Upon receiving this command, android_app->window still
|
||||
* contains the existing window; after calling android_app_exec_cmd
|
||||
* it will be set to NULL.
|
||||
*/
|
||||
APP_CMD_TERM_WINDOW,
|
||||
|
||||
/**
|
||||
* Command from main thread: the current ANativeWindow has been resized.
|
||||
* Please redraw with its new size.
|
||||
*/
|
||||
APP_CMD_WINDOW_RESIZED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the system needs that the current ANativeWindow
|
||||
* be redrawn. You should redraw the window before handing this to
|
||||
* android_app_exec_cmd() in order to avoid transient drawing glitches.
|
||||
*/
|
||||
APP_CMD_WINDOW_REDRAW_NEEDED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the content area of the window has changed,
|
||||
* such as from the soft input window being shown or hidden. You can
|
||||
* find the new content rect in android_app::contentRect.
|
||||
*/
|
||||
APP_CMD_CONTENT_RECT_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity window has gained
|
||||
* input focus.
|
||||
*/
|
||||
APP_CMD_GAINED_FOCUS,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity window has lost
|
||||
* input focus.
|
||||
*/
|
||||
APP_CMD_LOST_FOCUS,
|
||||
|
||||
/**
|
||||
* Command from main thread: the current device configuration has changed.
|
||||
*/
|
||||
APP_CMD_CONFIG_CHANGED,
|
||||
|
||||
/**
|
||||
* Command from main thread: the system is running low on memory.
|
||||
* Try to reduce your memory use.
|
||||
*/
|
||||
APP_CMD_LOW_MEMORY,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been started.
|
||||
*/
|
||||
APP_CMD_START,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been resumed.
|
||||
*/
|
||||
APP_CMD_RESUME,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app should generate a new saved state
|
||||
* for itself, to restore from later if needed. If you have saved state,
|
||||
* allocate it with malloc and place it in android_app.savedState with
|
||||
* the size in android_app.savedStateSize. The will be freed for you
|
||||
* later.
|
||||
*/
|
||||
APP_CMD_SAVE_STATE,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been paused.
|
||||
*/
|
||||
APP_CMD_PAUSE,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity has been stopped.
|
||||
*/
|
||||
APP_CMD_STOP,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's activity is being destroyed,
|
||||
* and waiting for the app thread to clean up and exit before proceeding.
|
||||
*/
|
||||
APP_CMD_DESTROY,
|
||||
|
||||
/**
|
||||
* Command from main thread: the app's insets have changed.
|
||||
*/
|
||||
APP_CMD_WINDOW_INSETS_CHANGED,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next
|
||||
* app command message.
|
||||
*/
|
||||
int8_t android_app_read_cmd(struct android_app* android_app);
|
||||
|
||||
/**
|
||||
* Call with the command returned by android_app_read_cmd() to do the
|
||||
* initial pre-processing of the given command. You can perform your own
|
||||
* actions for the command after calling this function.
|
||||
*/
|
||||
void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd);
|
||||
|
||||
/**
|
||||
* Call with the command returned by android_app_read_cmd() to do the
|
||||
* final post-processing of the given command. You must have done your own
|
||||
* actions for the command before calling this function.
|
||||
*/
|
||||
void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd);
|
||||
|
||||
/**
|
||||
* Call this before processing input events to get the events buffer.
|
||||
* The function returns NULL if there are no events to process.
|
||||
*/
|
||||
struct android_input_buffer* android_app_swap_input_buffers(
|
||||
struct android_app* android_app);
|
||||
|
||||
/**
|
||||
* Clear the array of motion events that were waiting to be handled, and release
|
||||
* each of them.
|
||||
*
|
||||
* This method should be called after you have processed the motion events in
|
||||
* your game loop. You should handle events at each iteration of your game loop.
|
||||
*/
|
||||
void android_app_clear_motion_events(struct android_input_buffer* inputBuffer);
|
||||
|
||||
/**
|
||||
* Clear the array of key events that were waiting to be handled, and release
|
||||
* each of them.
|
||||
*
|
||||
* This method should be called after you have processed the key up events in
|
||||
* your game loop. You should handle events at each iteration of your game loop.
|
||||
*/
|
||||
void android_app_clear_key_events(struct android_input_buffer* inputBuffer);
|
||||
|
||||
/**
|
||||
* This is a springboard into the Rust glue layer that wraps calling the
|
||||
* main entry for the app itself.
|
||||
*/
|
||||
extern void _rust_glue_entry(struct android_app* app);
|
||||
|
||||
/**
|
||||
* Set the filter to use when processing key events.
|
||||
* Any events for which the filter returns false will be ignored by
|
||||
* android_native_app_glue. If filter is set to NULL, no filtering is done.
|
||||
*
|
||||
* The default key filter will filter out volume and camera button presses.
|
||||
*/
|
||||
void android_app_set_key_event_filter(struct android_app* app,
|
||||
android_key_event_filter filter);
|
||||
|
||||
/**
|
||||
* Set the filter to use when processing touch and motion events.
|
||||
* Any events for which the filter returns false will be ignored by
|
||||
* android_native_app_glue. If filter is set to NULL, no filtering is done.
|
||||
*
|
||||
* Note that the default motion event filter will only allow touchscreen events
|
||||
* through, in order to mimic NativeActivity's behaviour, so for controller
|
||||
* events to be passed to the app, set the filter to NULL.
|
||||
*/
|
||||
void android_app_set_motion_event_filter(struct android_app* app,
|
||||
android_motion_event_filter filter);
|
||||
|
||||
/**
|
||||
* Determines if a looper wake up was due to new input becoming available
|
||||
*/
|
||||
bool android_app_input_available_wake_up(struct android_app* app);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup game_common Game Common
|
||||
* Common structures and functions used within AGDK
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* The type of a component for which to retrieve insets. See
|
||||
* https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type
|
||||
*/
|
||||
typedef enum GameCommonInsetsType {
|
||||
GAMECOMMON_INSETS_TYPE_CAPTION_BAR = 0,
|
||||
GAMECOMMON_INSETS_TYPE_DISPLAY_CUTOUT,
|
||||
GAMECOMMON_INSETS_TYPE_IME,
|
||||
GAMECOMMON_INSETS_TYPE_MANDATORY_SYSTEM_GESTURES,
|
||||
GAMECOMMON_INSETS_TYPE_NAVIGATION_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_STATUS_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_SYSTEM_BARS,
|
||||
GAMECOMMON_INSETS_TYPE_SYSTEM_GESTURES,
|
||||
GAMECOMMON_INSETS_TYPE_TAPABLE_ELEMENT,
|
||||
GAMECOMMON_INSETS_TYPE_WATERFALL,
|
||||
GAMECOMMON_INSETS_TYPE_COUNT
|
||||
} GameCommonInsetsType;
|
||||
+377
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup game_text_input Game Text Input
|
||||
* The interface to use GameTextInput.
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android/rect.h>
|
||||
#include <jni.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "common/gamesdk_common.h"
|
||||
#include "gamecommon.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define GAMETEXTINPUT_MAJOR_VERSION 2
|
||||
#define GAMETEXTINPUT_MINOR_VERSION 0
|
||||
#define GAMETEXTINPUT_BUGFIX_VERSION 0
|
||||
#define GAMETEXTINPUT_PACKED_VERSION \
|
||||
ANDROID_GAMESDK_PACKED_VERSION(GAMETEXTINPUT_MAJOR_VERSION, \
|
||||
GAMETEXTINPUT_MINOR_VERSION, \
|
||||
GAMETEXTINPUT_BUGFIX_VERSION)
|
||||
|
||||
/**
|
||||
* This struct holds a span within a region of text from start (inclusive) to
|
||||
* end (exclusive). An empty span or cursor position is specified with
|
||||
* start==end. An undefined span is specified with start = end = SPAN_UNDEFINED.
|
||||
*/
|
||||
typedef struct GameTextInputSpan {
|
||||
/** The start of the region (inclusive). */
|
||||
int32_t start;
|
||||
/** The end of the region (exclusive). */
|
||||
int32_t end;
|
||||
} GameTextInputSpan;
|
||||
|
||||
/**
|
||||
* Values with special meaning in a GameTextInputSpan.
|
||||
*/
|
||||
enum GameTextInputSpanFlag { SPAN_UNDEFINED = -1 };
|
||||
|
||||
/**
|
||||
* This struct holds the state of an editable section of text.
|
||||
* The text can have a selection and a composing region defined on it.
|
||||
* A composing region is used by IMEs that allow input using multiple steps to
|
||||
* compose a glyph or word. Use functions GameTextInput_getState and
|
||||
* GameTextInput_setState to read and modify the state that an IME is editing.
|
||||
*/
|
||||
typedef struct GameTextInputState {
|
||||
/**
|
||||
* Text owned by the state, as a modified UTF-8 string. Null-terminated.
|
||||
* https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
|
||||
*/
|
||||
const char *text_UTF8;
|
||||
/**
|
||||
* Length in bytes of text_UTF8, *not* including the null at end.
|
||||
*/
|
||||
int32_t text_length;
|
||||
/**
|
||||
* A selection defined on the text.
|
||||
*/
|
||||
GameTextInputSpan selection;
|
||||
/**
|
||||
* A composing region defined on the text.
|
||||
*/
|
||||
GameTextInputSpan composingRegion;
|
||||
} GameTextInputState;
|
||||
|
||||
/**
|
||||
* A callback called by GameTextInput_getState.
|
||||
* @param context User-defined context.
|
||||
* @param state State, owned by the library, that will be valid for the duration
|
||||
* of the callback.
|
||||
*/
|
||||
typedef void (*GameTextInputGetStateCallback)(
|
||||
void *context, const struct GameTextInputState *state);
|
||||
|
||||
/**
|
||||
* Opaque handle to the GameTextInput API.
|
||||
*/
|
||||
typedef struct GameTextInput GameTextInput;
|
||||
|
||||
/**
|
||||
* Initialize the GameTextInput library.
|
||||
* If called twice without GameTextInput_destroy being called, the same pointer
|
||||
* will be returned and a warning will be issued.
|
||||
* @param env A JNI env valid on the calling thread.
|
||||
* @param max_string_size The maximum length of a string that can be edited. If
|
||||
* zero, the maximum defaults to 65536 bytes. A buffer of this size is allocated
|
||||
* at initialization.
|
||||
* @return A handle to the library.
|
||||
*/
|
||||
GameTextInput *GameTextInput_init(JNIEnv *env, uint32_t max_string_size);
|
||||
|
||||
/**
|
||||
* When using GameTextInput, you need to create a gametextinput.InputConnection
|
||||
* on the Java side and pass it using this function to the library, unless using
|
||||
* GameActivity in which case this will be done for you. See the GameActivity
|
||||
* source code or GameTextInput samples for examples of usage.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param inputConnection A gametextinput.InputConnection object.
|
||||
*/
|
||||
void GameTextInput_setInputConnection(GameTextInput *input,
|
||||
jobject inputConnection);
|
||||
|
||||
/**
|
||||
* Unless using GameActivity, it is required to call this function from your
|
||||
* Java gametextinput.Listener.stateChanged method to convert eventState and
|
||||
* trigger any event callbacks. When using GameActivity, this does not need to
|
||||
* be called as event processing is handled by the Activity.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param eventState A Java gametextinput.State object.
|
||||
*/
|
||||
void GameTextInput_processEvent(GameTextInput *input, jobject eventState);
|
||||
|
||||
/**
|
||||
* Free any resources owned by the GameTextInput library.
|
||||
* Any subsequent calls to the library will fail until GameTextInput_init is
|
||||
* called again.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
*/
|
||||
void GameTextInput_destroy(GameTextInput *input);
|
||||
|
||||
/**
|
||||
* Flags to be passed to GameTextInput_showIme.
|
||||
*/
|
||||
enum ShowImeFlags {
|
||||
SHOW_IME_UNDEFINED = 0, // Default value.
|
||||
SHOW_IMPLICIT =
|
||||
1, // Indicates that the user has forced the input method open so it
|
||||
// should not be closed until they explicitly do so.
|
||||
SHOW_FORCED = 2 // Indicates that this is an implicit request to show the
|
||||
// input window, not as the result of a direct request by
|
||||
// the user. The window may not be shown in this case.
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the IME. Calls InputMethodManager.showSoftInput().
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param flags Defined in ShowImeFlags above. For more information see:
|
||||
* https://developer.android.com/reference/android/view/inputmethod/InputMethodManager
|
||||
*/
|
||||
void GameTextInput_showIme(GameTextInput *input, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Flags to be passed to GameTextInput_hideIme.
|
||||
*/
|
||||
enum HideImeFlags {
|
||||
HIDE_IME_UNDEFINED = 0, // Default value.
|
||||
HIDE_IMPLICIT_ONLY =
|
||||
1, // Indicates that the soft input window should only be hidden if it
|
||||
// was not explicitly shown by the user.
|
||||
HIDE_NOT_ALWAYS =
|
||||
2, // Indicates that the soft input window should normally be hidden,
|
||||
// unless it was originally shown with SHOW_FORCED.
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the IME. Calls InputMethodManager.hideSoftInputFromWindow().
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param flags Defined in HideImeFlags above. For more information see:
|
||||
* https://developer.android.com/reference/android/view/inputmethod/InputMethodManager
|
||||
*/
|
||||
void GameTextInput_hideIme(GameTextInput *input, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Restarts the input method. Calls InputMethodManager.restartInput().
|
||||
* @param input A valid GameTextInput library handle.
|
||||
*/
|
||||
void GameTextInput_restartInput(GameTextInput *input);
|
||||
|
||||
/**
|
||||
* Call a callback with the current GameTextInput state, which may have been
|
||||
* modified by changes in the IME and calls to GameTextInput_setState. We use a
|
||||
* callback rather than returning the state in order to simplify ownership of
|
||||
* text_UTF8 strings. These strings are only valid during the calling of the
|
||||
* callback.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param callback A function that will be called with valid state.
|
||||
* @param context Context used by the callback.
|
||||
*/
|
||||
void GameTextInput_getState(GameTextInput *input,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context);
|
||||
|
||||
/**
|
||||
* Set the current GameTextInput state. This state is reflected to any active
|
||||
* IME.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param state The state to set. Ownership is maintained by the caller and must
|
||||
* remain valid for the duration of the call.
|
||||
*/
|
||||
void GameTextInput_setState(GameTextInput *input,
|
||||
const GameTextInputState *state);
|
||||
|
||||
/**
|
||||
* Type of the callback needed by GameTextInput_setEventCallback that will be
|
||||
* called every time the IME state changes.
|
||||
* @param context User-defined context set in GameTextInput_setEventCallback.
|
||||
* @param current_state Current IME state, owned by the library and valid during
|
||||
* the callback.
|
||||
*/
|
||||
typedef void (*GameTextInputEventCallback)(
|
||||
void *context, const GameTextInputState *current_state);
|
||||
|
||||
/**
|
||||
* Optionally set a callback to be called whenever the IME state changes.
|
||||
* Not necessary if you are using GameActivity, which handles these callbacks
|
||||
* for you.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param callback Called by the library when the IME state changes.
|
||||
* @param context Context passed as first argument to the callback.
|
||||
*/
|
||||
void GameTextInput_setEventCallback(GameTextInput *input,
|
||||
GameTextInputEventCallback callback,
|
||||
void *context);
|
||||
|
||||
/**
|
||||
* Type of the callback needed by GameTextInput_setImeInsetsCallback that will
|
||||
* be called every time the IME window insets change.
|
||||
* @param context User-defined context set in
|
||||
* GameTextInput_setImeWIndowInsetsCallback.
|
||||
* @param current_insets Current IME insets, owned by the library and valid
|
||||
* during the callback.
|
||||
*/
|
||||
typedef void (*GameTextInputImeInsetsCallback)(void *context,
|
||||
const ARect *current_insets);
|
||||
|
||||
/**
|
||||
* Optionally set a callback to be called whenever the IME insets change.
|
||||
* Not necessary if you are using GameActivity, which handles these callbacks
|
||||
* for you.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param callback Called by the library when the IME insets change.
|
||||
* @param context Context passed as first argument to the callback.
|
||||
*/
|
||||
void GameTextInput_setImeInsetsCallback(GameTextInput *input,
|
||||
GameTextInputImeInsetsCallback callback,
|
||||
void *context);
|
||||
|
||||
/**
|
||||
* Get the current window insets for the IME.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param insets Filled with the current insets by this function.
|
||||
*/
|
||||
void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets);
|
||||
|
||||
/**
|
||||
* Unless using GameActivity, it is required to call this function from your
|
||||
* Java gametextinput.Listener.onImeInsetsChanged method to
|
||||
* trigger any event callbacks. When using GameActivity, this does not need to
|
||||
* be called as insets processing is handled by the Activity.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param eventState A Java gametextinput.State object.
|
||||
*/
|
||||
void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets);
|
||||
|
||||
/**
|
||||
* Convert a GameTextInputState struct to a Java gametextinput.State object.
|
||||
* Don't forget to delete the returned Java local ref when you're done.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param state Input state to convert.
|
||||
* @return A Java object of class gametextinput.State. The caller is required to
|
||||
* delete this local reference.
|
||||
*/
|
||||
jobject GameTextInputState_toJava(const GameTextInput *input,
|
||||
const GameTextInputState *state);
|
||||
|
||||
/**
|
||||
* Convert from a Java gametextinput.State object into a C GameTextInputState
|
||||
* struct.
|
||||
* @param input A valid GameTextInput library handle.
|
||||
* @param state A Java gametextinput.State object.
|
||||
* @param callback A function called with the C struct, valid for the duration
|
||||
* of the call.
|
||||
* @param context Context passed to the callback.
|
||||
*/
|
||||
void GameTextInputState_fromJava(const GameTextInput *input, jobject state,
|
||||
GameTextInputGetStateCallback callback,
|
||||
void *context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is the main interface to the Android Performance Tuner library, also
|
||||
* known as Tuning Fork.
|
||||
*
|
||||
* It is part of the Android Games SDK and produces best results when integrated
|
||||
* with the Swappy Frame Pacing Library.
|
||||
*
|
||||
* See the documentation at
|
||||
* https://developer.android.com/games/sdk/performance-tuner/custom-engine for
|
||||
* more information on using this library in a native Android game.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// There are separate versions for each GameSDK component that use this format:
|
||||
#define ANDROID_GAMESDK_PACKED_VERSION(MAJOR, MINOR, BUGFIX) \
|
||||
((MAJOR << 16) | (MINOR << 8) | (BUGFIX))
|
||||
// Accessors
|
||||
#define ANDROID_GAMESDK_MAJOR_VERSION(PACKED) ((PACKED) >> 16)
|
||||
#define ANDROID_GAMESDK_MINOR_VERSION(PACKED) (((PACKED) >> 8) & 0xff)
|
||||
#define ANDROID_GAMESDK_BUGFIX_VERSION(PACKED) ((PACKED) & 0xff)
|
||||
|
||||
#define AGDK_STRING_VERSION(MAJOR, MINOR, BUGFIX, GIT) \
|
||||
#MAJOR "." #MINOR "." #BUGFIX "." #GIT
|
||||
+20
-11
@@ -1,6 +1,9 @@
|
||||
#![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";
|
||||
|
||||
for f in [
|
||||
"GameActivity.h",
|
||||
"GameActivity.cpp",
|
||||
@@ -8,34 +11,40 @@ fn build_glue_for_game_activity() {
|
||||
"GameActivityEvents.cpp",
|
||||
"GameActivityLog.h",
|
||||
] {
|
||||
println!("cargo:rerun-if-changed=game-activity-csrc/game-activity/{f}");
|
||||
println!("cargo:rerun-if-changed={activity_basepath}/game-activity/{f}");
|
||||
}
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.include("game-activity-csrc")
|
||||
.file("game-activity-csrc/game-activity/GameActivity.cpp")
|
||||
.file("game-activity-csrc/game-activity/GameActivityEvents.cpp")
|
||||
.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"))
|
||||
.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=game-activity-csrc/game-text-input/{f}");
|
||||
println!("cargo:rerun-if-changed={textinput_basepath}/game-text-input/{f}");
|
||||
}
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.include("game-activity-csrc")
|
||||
.file("game-activity-csrc/game-text-input/gametextinput.cpp")
|
||||
.include("android-games-sdk/include")
|
||||
.include(textinput_basepath)
|
||||
.file(format!("{textinput_basepath}/game-text-input/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=game-activity-csrc/native_app_glue/{f}");
|
||||
println!("cargo:rerun-if-changed={activity_basepath}/game-activity/native_app_glue/{f}");
|
||||
}
|
||||
|
||||
cc::Build::new()
|
||||
.include("game-activity-csrc")
|
||||
.include("game-activity-csrc/game-activity/native_app_glue")
|
||||
.file("game-activity-csrc/game-activity/native_app_glue/android_native_app_glue.c")
|
||||
.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"))
|
||||
.extra_warnings(false)
|
||||
.cpp_link_stdlib("c++_static")
|
||||
.compile("libnative_app_glue.a");
|
||||
|
||||
Reference in New Issue
Block a user