├── GameActivity ├── consumer-rules.pro ├── prefab-src │ ├── prefab.json │ └── modules │ │ └── game-activity │ │ ├── module.json │ │ └── include │ │ ├── game-text-input │ │ ├── gamecommon.h │ │ ├── gametextinput.h │ │ └── gametextinput.cpp │ │ └── game-activity │ │ ├── system_utils.h │ │ ├── system_utils.cpp │ │ ├── GameActivityEvents.h │ │ └── GameActivityEvents.cpp ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── google │ │ │ └── androidgamesdk │ │ │ ├── gametextinput │ │ │ └── GameActivity.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro ├── build.gradle └── README.md ├── GameController ├── consumer-rules.pro ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── cpp │ │ │ ├── GameControllerLog.h │ │ │ ├── InternalControllerTable.h │ │ │ ├── GameControllerInternalConstants.h │ │ │ ├── GameControllerDeviceInfo.cpp │ │ │ ├── ThreadUtil.h │ │ │ ├── Log.h │ │ │ ├── CMakeLists.txt │ │ │ ├── GameControllerMappingUtils.h │ │ │ ├── GameControllerDeviceInfo.h │ │ │ ├── GameControllerGameActivityMirror.h │ │ │ ├── GameControllerLog.cpp │ │ │ ├── GameController.h │ │ │ ├── paddleboat_c.cpp │ │ │ ├── GameControllerMappingUtils.cpp │ │ │ └── GameControllerManager.h │ │ └── java │ │ │ └── com │ │ │ └── google │ │ │ └── android │ │ │ └── games │ │ │ └── paddleboat │ │ │ ├── GuardedBy.java │ │ │ ├── GameControllerThread.java │ │ │ ├── GameControllerInfo.java │ │ │ └── GameControllerListener.java │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── google │ │ │ └── android │ │ │ └── games │ │ │ └── paddleboat │ │ │ └── ExampleInstrumentedTest.java │ └── test │ │ └── cpp │ │ └── CMakeLists.txt ├── proguard-rules.pro └── build.gradle ├── GameTextInput ├── consumer-rules.pro ├── prefab-src │ ├── prefab.json │ └── modules │ │ └── game-text-input │ │ ├── module.json │ │ └── include │ │ └── game-text-input │ │ ├── gamecommon.h │ │ ├── gametextinput.h │ │ └── gametextinput.cpp ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── google │ │ └── androidgamesdk │ │ └── gametextinput │ │ ├── Settings.java │ │ ├── State.java │ │ ├── Listener.java │ │ └── GameTextInput.java ├── README.md ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── lib-game-sdk ├── AndroidManifest.xml └── build.gradle ├── src └── extras │ ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── google │ │ └── androidgamesdk │ │ ├── ChoreographerCallback.java │ │ ├── GameSdkDeviceInfoJni.java │ │ └── SwappyDisplayManager.java │ ├── lib-proguard-rules.txt │ └── build.gradle ├── .gitignore ├── settings.gradle ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /GameActivity/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /GameController/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /GameTextInput/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /GameActivity/prefab-src/prefab.json: -------------------------------------------------------------------------------- 1 | {"name":"game-activity","schema_version":1,"dependencies":[],"version":"0.0.1"} -------------------------------------------------------------------------------- /GameTextInput/prefab-src/prefab.json: -------------------------------------------------------------------------------- 1 | {"name":"game-text-input","schema_version":1,"dependencies":[],"version":"0.0.1"} -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocos/google-game-sdk/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /GameActivity/prefab-src/modules/game-activity/module.json: -------------------------------------------------------------------------------- 1 | {"export_libraries":[],"library_name":null,"android":{"export_libraries":null,"library_name":null}} -------------------------------------------------------------------------------- /GameActivity/src/main/java/com/google/androidgamesdk/gametextinput: -------------------------------------------------------------------------------- 1 | ../../../../../../../GameTextInput/src/main/java/com/google/androidgamesdk/gametextinput -------------------------------------------------------------------------------- /GameTextInput/prefab-src/modules/game-text-input/module.json: -------------------------------------------------------------------------------- 1 | {"export_libraries":[],"library_name":null,"android":{"export_libraries":null,"library_name":null}} -------------------------------------------------------------------------------- /GameTextInput/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib-game-sdk/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/extras/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /GameActivity/prefab-src/modules/game-activity/include/game-text-input/gamecommon.h: -------------------------------------------------------------------------------- 1 | ../../../../../../GameTextInput/prefab-src/modules/game-text-input/include/game-text-input/gamecommon.h -------------------------------------------------------------------------------- /GameActivity/prefab-src/modules/game-activity/include/game-text-input/gametextinput.h: -------------------------------------------------------------------------------- 1 | ../../../../../../GameTextInput/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.h -------------------------------------------------------------------------------- /GameActivity/prefab-src/modules/game-activity/include/game-text-input/gametextinput.cpp: -------------------------------------------------------------------------------- 1 | ../../../../../../GameTextInput/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.cpp -------------------------------------------------------------------------------- /GameActivity/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/extras/lib-proguard-rules.txt: -------------------------------------------------------------------------------- 1 | -keep public class com.google.androidgamesdk.ChoreographerCallback { *; } 2 | -keep public class com.google.androidgamesdk.SwappyDisplayManager { *; } 3 | -keep public class com.google.androidgamesdk.DeviceInfoJni { *; } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /GameController/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/ 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /lib-game-sdk/build 13 | /build 14 | /captures 15 | .externalNativeBuild 16 | .cxx 17 | local.properties 18 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | jcenter() // Warning: this repository is going to shut down soon 7 | } 8 | } 9 | 10 | 11 | rootProject.name = "GameSdk" 12 | //include ':app' 13 | include ':lib-game-sdk' 14 | -------------------------------------------------------------------------------- /GameTextInput/README.md: -------------------------------------------------------------------------------- 1 | # Game Text Input library 2 | 3 | Game Text Input is providing a simple API to show and hide the soft keyboard, set and get the currently editing text and receive notifications when the text is changed. It is not meant for fully-fledged text editor applications but still provides selection and composing region support for typical game use-case. 4 | 5 | ## Build the GameTextInput AAR from sources 6 | 7 | From the "gamesdk" directory: 8 | 9 | * `./gradlew packageLocalZip -Plibraries=game_text_input` 10 | 11 | The AAR is output in the `package` directory. 12 | 13 | ## Integrate GameTextInput AAR to your build 14 | 15 | Refer to the integration guide for now. Link to it and other documentation will be added here later. 16 | -------------------------------------------------------------------------------- /GameActivity/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /GameTextInput/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /GameController/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /GameController/src/main/cpp/GameControllerLog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #include 22 | 23 | namespace paddleboat { 24 | const char *LogGetInputSourceString(const int32_t eventSource); 25 | 26 | void LogInputEvent(const AInputEvent *event); 27 | } // namespace paddleboat 28 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/InternalControllerTable.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #include "paddleboat.h" 22 | 23 | namespace paddleboat { 24 | const Paddleboat_Controller_Mapping_Data *GetInternalControllerData(); 25 | 26 | int32_t GetInternalControllerDataCount(); 27 | } // namespace paddleboat -------------------------------------------------------------------------------- /GameController/src/main/java/com/google/android/games/paddleboat/GuardedBy.java: -------------------------------------------------------------------------------- 1 | package com.google.android.games.paddleboat; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Denotes that the annotated method or field can only be accessed when holding the referenced lock. 10 | *

11 | * Example: 12 | *

13 |  * final Object objectLock = new Object();
14 |  *
15 |  * {@literal @}GuardedBy("objectLock")
16 |  * volatile Object object;
17 |  *
18 |  * Object getObject() {
19 |  *     synchronized (objectLock) {
20 |  *         if (object == null) {
21 |  *             object = new Object();
22 |  *         }
23 |  *     }
24 |  *     return object;
25 |  * }
26 | */ 27 | @Target({ElementType.FIELD, ElementType.METHOD}) 28 | @Retention(RetentionPolicy.CLASS) 29 | public @interface GuardedBy { 30 | String value(); 31 | } 32 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true -------------------------------------------------------------------------------- /GameTextInput/src/main/java/com/google/androidgamesdk/gametextinput/Settings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.androidgamesdk.gametextinput; 17 | 18 | import android.view.inputmethod.EditorInfo; 19 | 20 | // Settings for InputConnection 21 | public final class Settings { 22 | EditorInfo mEditorInfo; 23 | boolean mForwardKeyEvents; 24 | 25 | public Settings(EditorInfo editorInfo, boolean forwardKeyEvents) { 26 | mEditorInfo = editorInfo; 27 | mForwardKeyEvents = forwardKeyEvents; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /GameActivity/prefab-src/modules/game-activity/include/game-activity/system_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "string" 20 | 21 | namespace gamesdk { 22 | 23 | // Get the value of the given system property 24 | std::string GetSystemProp(const char* key, const char* default_value = ""); 25 | 26 | // Get the value of the given system property as an integer 27 | int GetSystemPropAsInt(const char* key, int default_value = 0); 28 | 29 | // Get the value of the given system property as a bool 30 | bool GetSystemPropAsBool(const char* key, bool default_value = false); 31 | 32 | } // namespace gamesdk -------------------------------------------------------------------------------- /GameController/src/main/cpp/GameControllerInternalConstants.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | namespace paddleboat { 22 | inline constexpr size_t MAX_AXIS_COUNT = 48; 23 | // Must match GameControllerManager.DEVICEINFO_ARRAY_SIZE 24 | inline constexpr size_t DEVICEINFO_ARRAY_SIZE = 7; 25 | inline constexpr size_t DEVICEINFO_ARRAY_BYTESIZE = 26 | sizeof(int32_t) * DEVICEINFO_ARRAY_SIZE; 27 | inline constexpr size_t DEVICEINFO_MAX_NAME_LENGTH = 128; 28 | 29 | inline constexpr int32_t IGNORED_EVENT = 0; 30 | inline constexpr int32_t HANDLED_EVENT = 1; 31 | } // namespace paddleboat 32 | -------------------------------------------------------------------------------- /src/extras/build.gradle: -------------------------------------------------------------------------------- 1 | project('aar') { 2 | apply plugin: 'com.android.library' 3 | } 4 | 5 | project('apk') { 6 | apply plugin: 'com.android.application' 7 | } 8 | 9 | subprojects { 10 | 11 | android { 12 | compileSdkVersion 28 13 | defaultConfig { 14 | minSdkVersion 16 15 | targetSdkVersion 28 16 | versionCode 1 17 | versionName "1.0" 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | externalNativeBuild { 20 | cmake { 21 | cppFlags "" 22 | } 23 | } 24 | consumerProguardFiles 'lib-proguard-rules.txt' 25 | sourceSets { 26 | main { 27 | manifest.srcFile '../src/main/AndroidManifest.xml' 28 | java.srcDirs = ['../src/main/java'] 29 | } 30 | } 31 | } 32 | buildTypes { 33 | release { 34 | minifyEnabled false 35 | } 36 | } 37 | lintOptions { 38 | abortOnError false 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation fileTree(dir: 'libs', include: ['*.jar']) 44 | testImplementation 'junit:junit:4.12' 45 | } 46 | 47 | } 48 | 49 | task assembleRelease { 50 | dependsOn 'apk:assembleRelease' 51 | } 52 | -------------------------------------------------------------------------------- /GameTextInput/src/main/java/com/google/androidgamesdk/gametextinput/State.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.androidgamesdk.gametextinput; 17 | 18 | // The state of an editable text region. 19 | public final class State { 20 | public State(String text_in, int selectionStart_in, int selectionEnd_in, 21 | int composingRegionStart_in, int composingRegionEnd_in) { 22 | text = text_in; 23 | selectionStart = selectionStart_in; 24 | selectionEnd = selectionEnd_in; 25 | composingRegionStart = composingRegionStart_in; 26 | composingRegionEnd = composingRegionEnd_in; 27 | } 28 | 29 | public String text; 30 | public int selectionStart; 31 | public int selectionEnd; 32 | public int composingRegionStart; 33 | public int composingRegionEnd; 34 | } 35 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/GameControllerDeviceInfo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "GameControllerDeviceInfo.h" 17 | 18 | #include 19 | 20 | namespace paddleboat { 21 | GameControllerDeviceInfo::GameControllerDeviceInfo() { 22 | mName[0] = '\0'; 23 | mInfo.mDeviceId = -1; 24 | mInfo.mProductId = -1; 25 | mInfo.mVendorId = -1; 26 | mInfo.mAxisBitsLow = 0; 27 | mInfo.mAxisBitsHigh = 0; 28 | mInfo.mControllerNumber = -1; 29 | mInfo.mControllerFlags = 0; 30 | 31 | for (size_t i = 0; i < paddleboat::MAX_AXIS_COUNT; ++i) { 32 | mAxisMinArray[i] = 0.0f; 33 | mAxisMaxArray[i] = 0.0f; 34 | mAxisFlatArray[i] = 0.0f; 35 | mAxisFuzzArray[i] = 0.0f; 36 | } 37 | } 38 | 39 | void GameControllerDeviceInfo::setName(const char *name) { 40 | strncpy(mName, name, DEVICEINFO_MAX_NAME_LENGTH); 41 | } 42 | } // namespace paddleboat -------------------------------------------------------------------------------- /GameController/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'com.android.library' 19 | } 20 | 21 | buildDir="../../out_paddleboat" 22 | 23 | android { 24 | 25 | defaultConfig { 26 | minSdkVersion 16 27 | compileSdkVersion 31 28 | targetSdkVersion 31 29 | versionCode 1 30 | versionName "1.1" 31 | 32 | consumerProguardFiles "consumer-rules.pro" 33 | } 34 | 35 | buildTypes { 36 | release { 37 | minifyEnabled false 38 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 39 | } 40 | } 41 | 42 | compileOptions { 43 | sourceCompatibility JavaVersion.VERSION_1_8 44 | targetCompatibility JavaVersion.VERSION_1_8 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation "androidx.annotation:annotation:1.3.0" 50 | } 51 | repositories { 52 | mavenCentral() 53 | } 54 | -------------------------------------------------------------------------------- /GameTextInput/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | buildDir="../../out_game_text_input" 6 | 7 | android { 8 | compileSdkVersion 28 9 | 10 | defaultConfig { 11 | minSdkVersion 14 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles "consumer-rules.pro" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | // TODO(florianrival@) Enabling this will trigger "Cannot query the value of this property because it has no value available." error. 31 | // buildFeatures { 32 | // prefabPublishing true 33 | // } 34 | // prefab { 35 | // gametextinput { 36 | // headers "src/main/cpp/include" 37 | // libraryName "game-text-input" 38 | // } 39 | // } 40 | } 41 | 42 | dependencies { 43 | implementation 'androidx.appcompat:appcompat:1.2.0' 44 | implementation 'androidx.core:core:1.5.0' 45 | implementation 'com.google.android.material:material:1.1.0' 46 | testImplementation 'junit:junit:4.+' 47 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 49 | } 50 | -------------------------------------------------------------------------------- /GameActivity/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | buildDir = "../../out_game_activity" 6 | 7 | android { 8 | compileSdkVersion 28 9 | 10 | defaultConfig { 11 | minSdkVersion 16 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles "consumer-rules.pro" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | 31 | // TODO(florianrival@) Enabling this will trigger "Cannot query the value of this property because it has no value available." error. 32 | // buildFeatures { 33 | // prefabPublishing true 34 | // } 35 | // prefab { 36 | // gameactivity { 37 | // headers "src/main/cpp/include" 38 | // } 39 | // } 40 | } 41 | 42 | dependencies { 43 | implementation 'androidx.appcompat:appcompat:1.2.0' 44 | implementation 'androidx.core:core:1.5.0' 45 | implementation 'com.google.android.material:material:1.2.1' 46 | testImplementation 'junit:junit:4.+' 47 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 49 | } 50 | repositories { 51 | mavenCentral() 52 | } 53 | -------------------------------------------------------------------------------- /GameController/src/androidTest/java/com/google/android/games/paddleboat/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Android Open Source Project 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.google.android.games.paddleboat; 15 | 16 | import android.content.Context; 17 | import android.support.test.InstrumentationRegistry; 18 | import android.support.test.runner.AndroidJUnit4; 19 | 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | 23 | import static org.junit.Assert.*; 24 | 25 | /** 26 | * Instrumented test, which will execute on an Android device. 27 | * 28 | * @see Testing documentation 29 | */ 30 | @RunWith(AndroidJUnit4.class) 31 | public class ExampleInstrumentedTest { 32 | @Test 33 | public void useAppContext() { 34 | // Context of the app under test. 35 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 36 | assertEquals("com.google.android.games.paddleboat.test", appContext.getPackageName()); 37 | } 38 | } -------------------------------------------------------------------------------- /GameTextInput/src/main/java/com/google/androidgamesdk/gametextinput/Listener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.androidgamesdk.gametextinput; 17 | 18 | import androidx.core.graphics.Insets; 19 | 20 | /** 21 | * Listener interface for text, selection and composing region changes. 22 | * Also a listener for window insets changes. 23 | */ 24 | public interface Listener { 25 | 26 | /* 27 | * Called when the IME text, selection or composing region has changed. 28 | * 29 | * @param newState The updated state 30 | * @param dismmissed Whether the IME has been dismissed by the user 31 | */ 32 | void stateChanged(State newState, boolean dismissed); 33 | 34 | /* 35 | * Called when the IME window insets change, i.e. the IME moves into or out of view. 36 | * 37 | * @param insets The new window insets, i.e. the offsets of top, bottom, left and right 38 | * relative to the window 39 | */ 40 | void onImeInsetsChanged(Insets insets); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /GameTextInput/prefab-src/modules/game-text-input/include/game-text-input/gamecommon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * @defgroup game_common Game Common 19 | * Common structures and functions used within AGDK 20 | * @{ 21 | */ 22 | 23 | #pragma once 24 | 25 | /** 26 | * The type of a component for which to retrieve insets. See 27 | * https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat.Type 28 | */ 29 | typedef enum GameCommonInsetsType { 30 | GAMECOMMON_INSETS_TYPE_CAPTION_BAR = 0, 31 | GAMECOMMON_INSETS_TYPE_DISPLAY_CUTOUT, 32 | GAMECOMMON_INSETS_TYPE_IME, 33 | GAMECOMMON_INSETS_TYPE_MANDATORY_SYSTEM_GESTURES, 34 | GAMECOMMON_INSETS_TYPE_NAVIGATION_BARS, 35 | GAMECOMMON_INSETS_TYPE_STATUS_BARS, 36 | GAMECOMMON_INSETS_TYPE_SYSTEM_BARS, 37 | GAMECOMMON_INSETS_TYPE_SYSTEM_GESTURES, 38 | GAMECOMMON_INSETS_TYPE_TAPABLE_ELEMENT, 39 | GAMECOMMON_INSETS_TYPE_WATERFALL, 40 | GAMECOMMON_INSETS_TYPE_COUNT 41 | } GameCommonInsetsType; 42 | -------------------------------------------------------------------------------- /GameActivity/README.md: -------------------------------------------------------------------------------- 1 | # GameActivity 2 | 3 | GameActivity is an Android `Activity` modeled on the API of `NativeActivity`, but with a few changes to make it a better starting point for game developers. 4 | 5 | * It inherits from `androidx.appcompat.app.AppCompatActivity` - allowing you to use Jetpack components architecture (and still use some of the newer platform features on older Android devices). 6 | * It renders into a `SurfaceView` that allows you to interface with any other Android UI element. 7 | * It handles *events* like a Java activity would do, allowing any Android UI element (like a text edit, a webview, an ad or a form) to work as usual, but still exposes the events to your game using a C interface that makes them easy to consume in your game loop. 8 | * It offers a C API similar to `NativeActivity`, and to the *android_native_app_glue* library. 9 | 10 | ## Build the GameActivity AAR from sources 11 | 12 | From the `gamesdk` directory: 13 | 14 | * `./gradlew packageLocalZip -Plibraries=game_activity` 15 | 16 | The AAR is output in the `package` directory. It contains GameActivity, both the Java class and its C++ implementation, and the "android_native_app_glue" library. 17 | 18 | ## Integrate GameActivity AAR to your build 19 | 20 | Refer to the integration guide for now. Link to it and other documentation will be added here later. 21 | 22 | Note that the library has a dependency on androidx.core version 1.5.0 or later. You should add the following to your app's build.gradle file: 23 | ` 24 | dependencies { 25 | ... 26 | implementation "androidx.core:core:1.5.0" 27 | ... 28 | ` 29 | Not adding this will cause a JNI crash at startup. 30 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/ThreadUtil.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | // Enable thread safety attributes only with clang. 24 | // The attributes can be safely erased when compiling with other compilers. 25 | #if defined(__clang__) && (!defined(SWIG)) 26 | #define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) 27 | #else 28 | #define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op 29 | #endif 30 | 31 | #if !defined GAMESDK_THREAD_CHECKS 32 | #define GAMESDK_THREAD_CHECKS 1 33 | #endif 34 | 35 | #if GAMESDK_THREAD_CHECKS 36 | #define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) 37 | 38 | #define REQUIRES(...) \ 39 | THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) 40 | 41 | #define NO_THREAD_SAFETY_ANALYSIS \ 42 | THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) 43 | #else 44 | #define GUARDED_BY(x) 45 | #define REQUIRES(...) 46 | #define NO_THREAD_SAFETY_ANALYSIS 47 | #endif 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Source Code 2 | 3 | This project use google game sdk source code. Current dependency info: 4 | 5 | - You can download from [github](https://android.googlesource.com/platform/frameworks/opt/gamesdk). 6 | - **Branch**: android-games-sdk-release 7 | - **CommitID**: 551ee6b30de541fabceb8424abfb3818b5037283 8 | 9 | ## How to use cloned code 10 | 11 | - Copy 'GameActivity', 'GameController', 'GameTextInput' and 'src/extras' directories from cloned directories. 12 | - First, modify cpp files and CMakeLists.txt, and then copy these files to the directory 'sources/Android-gamesdk' in the GIT [repository](https://github.com/cocos/cocos-engine-external.git) 13 | - Modify onCreate function of GameActivity 14 | 15 | ``` Java 16 | String libname = "main"; 17 | // following code is needed to be added 18 | if (null != getIntent().getStringExtra(META_DATA_LIB_NAME)) { 19 | libname = getIntent().getStringExtra(META_DATA_LIB_NAME); 20 | } 21 | ``` 22 | 23 | ## How to Build 24 | 25 | - Use gradle command. gradlew :lib-game-sdk::assembleRelease 26 | 27 | ## How to use classes.jar 28 | 29 | - Copy ***classes.jar*** from the 'lib-game-sdk/build/intermediates/aar_main_jar/release' directory to the 'native\cocos\platform\android\java\libs' directory of the git [repository](https://github.com/cocos/cocos-engine.git) 30 | - rename classes.jar to ***game-sdk.jar*** 31 | 32 | ## Environment requirements 33 | 34 | - ***JAVA_HOME*** configuration to jdk11 can not be higher than jdk14, 35 | start a new command line window and make sure that the Java version number is the set JDK version. You may need to put ***$JAVA_HOME/bin*** to the system variable path. 36 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/Log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #include 22 | 23 | #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 24 | #define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 25 | #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 26 | #define ALOGW_ONCE_IF(cond, ...) \ 27 | do { \ 28 | static bool alogw_once##__FILE__##__LINE__##__ = true; \ 29 | if (cond && alogw_once##__FILE__##__LINE__##__) { \ 30 | alogw_once##__FILE__##__LINE__##__ = false; \ 31 | ALOGW(__VA_ARGS__); \ 32 | } \ 33 | } while (0) 34 | 35 | #ifndef NDEBUG 36 | #define ALOGV(...) \ 37 | __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 38 | #else 39 | #define ALOGV(...) 40 | #endif 41 | -------------------------------------------------------------------------------- /src/extras/src/main/java/com/google/androidgamesdk/ChoreographerCallback.java: -------------------------------------------------------------------------------- 1 | package com.google.androidgamesdk; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.view.Choreographer; 6 | import android.util.Log; 7 | 8 | 9 | public class ChoreographerCallback implements Choreographer.FrameCallback { 10 | private static final String LOG_TAG = "ChoreographerCallback"; 11 | private long mCookie; 12 | private LooperThread mLooper; 13 | 14 | private class LooperThread extends Thread { 15 | public Handler mHandler; 16 | 17 | public void run() { 18 | Log.i(LOG_TAG, "Starting looper thread"); 19 | Looper.prepare(); 20 | mHandler = new Handler(); 21 | Looper.loop(); 22 | Log.i(LOG_TAG, "Terminating looper thread"); 23 | } 24 | } 25 | 26 | public ChoreographerCallback(long cookie) { 27 | mCookie = cookie; 28 | mLooper = new LooperThread(); 29 | mLooper.start(); 30 | } 31 | 32 | public void postFrameCallback() { 33 | mLooper.mHandler.post(new Runnable() { 34 | @Override 35 | public void run() { 36 | Choreographer.getInstance().postFrameCallback(ChoreographerCallback.this); 37 | } 38 | }); 39 | } 40 | 41 | public void postFrameCallbackDelayed(long delayMillis) { 42 | Choreographer.getInstance().postFrameCallbackDelayed(this, delayMillis); 43 | } 44 | 45 | public void terminate() { 46 | mLooper.mHandler.getLooper().quit(); 47 | } 48 | 49 | @Override 50 | public void doFrame(long frameTimeNanos) { 51 | nOnChoreographer(mCookie, frameTimeNanos); 52 | } 53 | 54 | public native void nOnChoreographer(long cookie, long frameTimeNanos); 55 | 56 | } 57 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2021 The Android Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | project(paddleboat C CXX) 17 | 18 | include_directories(${CMAKE_CURRENT_LIST_DIR}/paddleboat/include) 19 | 20 | set( PADDLEBOAT_SRCS 21 | ${CMAKE_CURRENT_LIST_DIR}/InternalControllerTable.cpp 22 | ${CMAKE_CURRENT_LIST_DIR}/GameController.cpp 23 | ${CMAKE_CURRENT_LIST_DIR}/GameControllerDeviceInfo.cpp 24 | ${CMAKE_CURRENT_LIST_DIR}/GameControllerLog.cpp 25 | ${CMAKE_CURRENT_LIST_DIR}/GameControllerManager.cpp 26 | ${CMAKE_CURRENT_LIST_DIR}/GameControllerMappingUtils.cpp 27 | ${CMAKE_CURRENT_LIST_DIR}/paddleboat_c.cpp) 28 | 29 | # set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Os") 30 | # set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") 31 | # set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g0") 32 | 33 | add_library(paddleboat_static STATIC ${PADDLEBOAT_SRCS}) 34 | 35 | set_target_properties( paddleboat_static PROPERTIES 36 | LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/build ) 37 | 38 | add_library(paddleboat SHARED ${CMAKE_CURRENT_LIST_DIR}/paddleboat_c.cpp) 39 | 40 | target_link_libraries(paddleboat 41 | paddleboat_static 42 | android 43 | atomic 44 | log) 45 | -------------------------------------------------------------------------------- /lib-game-sdk/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'com.android.library' 19 | } 20 | 21 | 22 | android { 23 | 24 | defaultConfig { 25 | minSdkVersion 18 26 | compileSdkVersion 31 27 | targetSdkVersion 31 28 | versionCode 1 29 | versionName "1.1" 30 | 31 | consumerProguardFiles "consumer-rules.pro" 32 | sourceSets { 33 | main { 34 | manifest.srcFile 'AndroidManifest.xml' 35 | java.srcDirs = ['../src/extras/src/main/java', '../GameActivity/src/main/java', '../GameController/src/main/java'] 36 | } 37 | } 38 | } 39 | 40 | buildTypes { 41 | release { 42 | minifyEnabled false 43 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 44 | } 45 | } 46 | 47 | compileOptions { 48 | sourceCompatibility JavaVersion.VERSION_1_8 49 | targetCompatibility JavaVersion.VERSION_1_8 50 | } 51 | } 52 | 53 | dependencies { 54 | // implementation "androidx.annotation:annotation:1.3.0" 55 | // implementation 'androidx.appcompat:appcompat:1.2.0' 56 | // implementation 'androidx.core:core:1.5.0' 57 | // implementation 'com.google.android.material:material:1.2.1' 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/extras/src/main/java/com/google/androidgamesdk/GameSdkDeviceInfoJni.java: -------------------------------------------------------------------------------- 1 | package com.google.androidgamesdk; 2 | 3 | /** JNI api for getting device information */ 4 | public class GameSdkDeviceInfoJni { 5 | private static Throwable initializationExceptionOrError; 6 | 7 | static { 8 | try { 9 | System.loadLibrary("game_sdk_device_info_jni"); 10 | } catch(Exception exception) { 11 | // Catch SecurityException, NullPointerException (or any potential unchecked exception) 12 | // as we don't want to crash the app if the library failed to load. 13 | initializationExceptionOrError = exception; 14 | } catch(Error error) { 15 | // Catch UnsatisfiedLinkError (or any potential unchecked error) 16 | // as we don't want to crash the app if the library failed to load. 17 | initializationExceptionOrError = error; 18 | } 19 | } 20 | 21 | /** 22 | * Returns a byte array, which is a serialized proto containing device information, or 23 | * null if the native library "game_sdk_device_info_jni" could not be loaded. 24 | * 25 | * @return Optional with the serialized byte array, representing game sdk device info with errors, 26 | * or null. 27 | */ 28 | public static byte[] tryGetProtoSerialized() { 29 | if (initializationExceptionOrError != null) { 30 | return null; 31 | } 32 | 33 | return getProtoSerialized(); 34 | } 35 | 36 | 37 | /** 38 | * Returns the exception or error that was caught when trying to load the library, if any. 39 | * Otherwise, returns null. 40 | * 41 | * @return The caught Throwable or null. 42 | */ 43 | public static Throwable getInitializationExceptionOrError() { 44 | return initializationExceptionOrError; 45 | } 46 | 47 | /** 48 | * Returns a byte array, which is a serialized proto. 49 | * 50 | * @return serialized byte array, representing game sdk device info with errors. 51 | */ 52 | private static native byte[] getProtoSerialized(); 53 | 54 | private GameSdkDeviceInfoJni() {} 55 | } 56 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/GameControllerMappingUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #ifndef PADDLEBOAT_H 22 | #include "paddleboat.h" 23 | #endif 24 | 25 | namespace paddleboat { 26 | class MappingTableSearch { 27 | public: 28 | MappingTableSearch(); 29 | 30 | MappingTableSearch(Paddleboat_Controller_Mapping_Data *mapRoot, 31 | int32_t entryCount); 32 | 33 | void initSearchParameters(const int32_t newVendorId, 34 | const int32_t newProductId, 35 | const int32_t newMinApi, const int32_t newMaxApi); 36 | 37 | Paddleboat_Controller_Mapping_Data *mappingRoot; 38 | int32_t vendorId; 39 | int32_t productId; 40 | int32_t minApi; 41 | int32_t maxApi; 42 | int32_t tableIndex; 43 | int32_t mapEntryCount; 44 | int32_t tableEntryCount; 45 | int32_t tableMaxEntryCount; 46 | }; 47 | 48 | class GameControllerMappingUtils { 49 | public: 50 | static bool findMatchingMapEntry(MappingTableSearch *searchEntry); 51 | 52 | static bool insertMapEntry( 53 | const Paddleboat_Controller_Mapping_Data *mappingData, 54 | MappingTableSearch *searchEntry); 55 | 56 | static const Paddleboat_Controller_Mapping_Data *validateMapTable( 57 | const Paddleboat_Controller_Mapping_Data *mappingRoot, 58 | const int32_t tableEntryCount); 59 | }; 60 | } // namespace paddleboat 61 | -------------------------------------------------------------------------------- /GameController/src/test/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2021 The Android Open Source Project 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | set(TEST_ABI_LIST ${TEST_ABIS}) 18 | 19 | if (${ANDROID_ABI} IN_LIST TEST_ABI_LIST) 20 | if (${RUN_CPP_TESTS} MATCHES "true") 21 | set(TEST_LIB_NAME paddleboat-tests-lib) 22 | set(GTEST_DIR ${ANDROID_NDK}/sources/third_party/googletest) 23 | add_library(gtest STATIC ${GTEST_DIR}/src/gtest_main.cc ${GTEST_DIR}/src/gtest-all.cc) 24 | target_include_directories(gtest PRIVATE ${GTEST_DIR}) 25 | target_include_directories(gtest PUBLIC ${GTEST_DIR}/include) 26 | 27 | add_executable(${TEST_LIB_NAME} 28 | ${TEST_SRC_DIR}/paddleboat_tests.cpp) 29 | 30 | target_link_libraries(${TEST_LIB_NAME} paddleboat gtest) 31 | 32 | set(TARGET_TEST_DIR /data/local/tmp/${TEST_LIB_NAME}) 33 | set(TARGET_TEST_LIB_DIR ${TARGET_TEST_DIR}/${ANDROID_ABI}) 34 | find_program(ADB NAMES adb PATHS ${ANDROID_SDK_ROOT}/platform-tools) 35 | 36 | message(STATUS "Running Paddleboat tests for ABI ${ANDROID_ABI} SDK ${ANDROID_SDK_ROOT} RUN_CPP_TESTS ${RUN_CPP_TESTS}") 37 | 38 | add_custom_command(TARGET ${TEST_LIB_NAME} POST_BUILD 39 | COMMAND ${ADB} shell mkdir -p ${TARGET_TEST_LIB_DIR} 40 | COMMAND ${ADB} push $ ${TARGET_TEST_LIB_DIR}/ 41 | COMMAND ${ADB} push $ ${TARGET_TEST_LIB_DIR}/ 42 | COMMAND ${ADB} shell \"export LD_LIBRARY_PATH=${TARGET_TEST_LIB_DIR}\; ${TARGET_TEST_LIB_DIR}/${TEST_LIB_NAME}\") 43 | endif () # RUN_CPP_TESTS 44 | else() 45 | message(STATUS "Skipping Paddleboat tests for ABI ${ANDROID_ABI} SDK ${ANDROID_SDK_ROOT} RUN_CPP_TESTS ${RUN_CPP_TESTS}") 46 | endif () # ANDROID_ABI in TEST_ABIS 47 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/GameControllerDeviceInfo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "GameControllerInternalConstants.h" 20 | 21 | namespace paddleboat { 22 | 23 | class GameControllerDeviceInfo { 24 | public: 25 | // These are copied directly from the int[] mGameControllerDeviceInfoArray, 26 | // the layout should match the field definitions in 27 | // Paddleboat_Controller_Info.java 28 | struct InfoFields { 29 | int32_t mDeviceId; 30 | int32_t mVendorId; 31 | int32_t mProductId; 32 | int32_t mAxisBitsLow; 33 | int32_t mAxisBitsHigh; 34 | int32_t mControllerNumber; 35 | int32_t mControllerFlags; 36 | }; 37 | 38 | GameControllerDeviceInfo(); 39 | 40 | void setName(const char *name); 41 | 42 | const char *getName() const { return mName; } 43 | 44 | const InfoFields &getInfo() const { return mInfo; } 45 | 46 | const float *getMinArray() const { return mAxisMinArray; } 47 | 48 | const float *getMaxArray() const { return mAxisMaxArray; } 49 | 50 | const float *getFlatArray() const { return mAxisFlatArray; } 51 | 52 | const float *getFuzzArray() const { return mAxisFuzzArray; } 53 | 54 | InfoFields *getInfo() { return &mInfo; } 55 | 56 | float *getMinArray() { return mAxisMinArray; } 57 | 58 | float *getMaxArray() { return mAxisMaxArray; } 59 | 60 | float *getFlatArray() { return mAxisFlatArray; } 61 | 62 | float *getFuzzArray() { return mAxisFuzzArray; } 63 | 64 | private: 65 | char mName[paddleboat::DEVICEINFO_MAX_NAME_LENGTH]; 66 | InfoFields mInfo; 67 | float mAxisMinArray[paddleboat::MAX_AXIS_COUNT]; 68 | float mAxisMaxArray[paddleboat::MAX_AXIS_COUNT]; 69 | float mAxisFlatArray[paddleboat::MAX_AXIS_COUNT]; 70 | float mAxisFuzzArray[paddleboat::MAX_AXIS_COUNT]; 71 | }; 72 | } // namespace paddleboat 73 | -------------------------------------------------------------------------------- /GameActivity/prefab-src/modules/game-activity/include/game-activity/system_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "system_utils.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | namespace gamesdk { 24 | 25 | #if __ANDROID_API__ >= 26 26 | std::string getSystemPropViaCallback(const char* key, 27 | const char* default_value = "") { 28 | const prop_info* prop = __system_property_find(key); 29 | if (prop == nullptr) { 30 | return default_value; 31 | } 32 | std::string return_value; 33 | auto thunk = [](void* cookie, const char* /*name*/, const char* value, 34 | uint32_t /*serial*/) { 35 | if (value != nullptr) { 36 | std::string* r = static_cast(cookie); 37 | *r = value; 38 | } 39 | }; 40 | __system_property_read_callback(prop, thunk, &return_value); 41 | return return_value; 42 | } 43 | #else 44 | std::string getSystemPropViaGet(const char* key, 45 | const char* default_value = "") { 46 | char buffer[PROP_VALUE_MAX + 1] = ""; // +1 for terminator 47 | int bufferLen = __system_property_get(key, buffer); 48 | if (bufferLen > 0) 49 | return buffer; 50 | else 51 | return ""; 52 | } 53 | #endif 54 | 55 | std::string GetSystemProp(const char* key, const char* default_value) { 56 | #if __ANDROID_API__ >= 26 57 | return getSystemPropViaCallback(key, default_value); 58 | #else 59 | return getSystemPropViaGet(key, default_value); 60 | #endif 61 | } 62 | 63 | int GetSystemPropAsInt(const char* key, int default_value) { 64 | std::string prop = GetSystemProp(key); 65 | return prop == "" ? default_value : strtoll(prop.c_str(), nullptr, 10); 66 | } 67 | 68 | bool GetSystemPropAsBool(const char* key, bool default_value) { 69 | return GetSystemPropAsInt(key, default_value) != 0; 70 | } 71 | 72 | } // namespace gamesdk -------------------------------------------------------------------------------- /GameController/src/main/cpp/GameControllerGameActivityMirror.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | namespace paddleboat { 22 | // GameActivity needs to use its own event data structures for key and motion 23 | // events. We do not want to have a dependency on GameActivity. Unfortunately, 24 | // this means we need to internally mirror the relevant structures. 25 | // Paddleboat_processGameActivityInputEvent includes a struct size parameter, 26 | // and we know that the GameActivity structures will only ever add fields, so we 27 | // can determine if we are being passed a later version of the struct than the 28 | // mirrored internal version. 29 | 30 | // The following should mirror GameActivity.h 31 | #define PADDLEBOAT_GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48 32 | #define PADDLEBOAT_MAX_NUM_POINTERS_IN_MOTION_EVENT 8 33 | 34 | typedef struct Paddleboat_GameActivityPointerInfo { 35 | int32_t id; 36 | int32_t toolType; // added in newer version 37 | float axisValues[PADDLEBOAT_GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT]; 38 | float rawX; 39 | float rawY; 40 | } Paddleboat_GameActivityPointerInfo; 41 | 42 | typedef struct Paddleboat_GameActivityMotionEvent { 43 | int32_t deviceId; 44 | int32_t source; 45 | int32_t action; 46 | int64_t eventTime; 47 | int64_t downTime; 48 | int32_t flags; 49 | int32_t metaState; 50 | int32_t actionButton; 51 | int32_t buttonState; 52 | int32_t classification; 53 | int32_t edgeFlags; 54 | uint32_t pointerCount; 55 | Paddleboat_GameActivityPointerInfo 56 | pointers[PADDLEBOAT_MAX_NUM_POINTERS_IN_MOTION_EVENT]; 57 | float precisionX; 58 | float precisionY; 59 | } Paddleboat_GameActivityMotionEvent; 60 | 61 | typedef struct Paddleboat_GameActivityKeyEvent { 62 | int32_t deviceId; 63 | int32_t source; 64 | int32_t action; 65 | int64_t eventTime; 66 | int64_t downTime; 67 | int32_t flags; 68 | int32_t metaState; 69 | int32_t modifiers; 70 | int32_t repeatCount; 71 | int32_t keyCode; 72 | } Paddleboat_GameActivityKeyEvent; 73 | } // namespace paddleboat 74 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /GameController/src/main/java/com/google/android/games/paddleboat/GameControllerThread.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Android Open Source Project 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.google.android.games.paddleboat; 15 | 16 | import android.hardware.input.InputManager; 17 | import android.os.Handler; 18 | import android.os.Looper; 19 | import android.util.Log; 20 | 21 | import java.lang.Thread; 22 | 23 | public class GameControllerThread extends Thread implements InputManager.InputDeviceListener { 24 | private static final String TAG = "GameControllerThread"; 25 | private boolean activeInputDeviceListener = false; 26 | private GameControllerManager mGameControllerManager; 27 | private Handler mHandler; 28 | 29 | public void setGameControllerManager(GameControllerManager gcManager) { 30 | mGameControllerManager = gcManager; 31 | } 32 | 33 | @Override 34 | public void run () { 35 | Looper.prepare(); 36 | mHandler = new Handler(Looper.myLooper()); 37 | onStart(); 38 | Looper.loop(); 39 | } 40 | 41 | public void onStop() { 42 | if (activeInputDeviceListener) { 43 | Log.d(TAG, "unregisterInputDeviceListener"); 44 | mGameControllerManager.getAppInputManager().unregisterInputDeviceListener(this); 45 | activeInputDeviceListener = false; 46 | } 47 | } 48 | 49 | public void onStart() { 50 | if (!activeInputDeviceListener) { 51 | Log.d(TAG, "registerInputDeviceListener"); 52 | mGameControllerManager.getAppInputManager().registerInputDeviceListener(this, mHandler); 53 | activeInputDeviceListener = true; 54 | } 55 | } 56 | 57 | public void terminate() { 58 | if (mHandler != null) { 59 | Log.d(TAG, "terminate"); 60 | mHandler.getLooper().quit(); 61 | mHandler = null; 62 | } 63 | } 64 | 65 | @Override 66 | public void onInputDeviceAdded(int deviceId) { 67 | Log.d(TAG, "onInputDeviceAdded id: " + deviceId); 68 | mGameControllerManager.onInputDeviceAdded(deviceId); 69 | } 70 | 71 | @Override 72 | public void onInputDeviceRemoved(int deviceId) { 73 | Log.d(TAG, "onInputDeviceRemoved id: " + deviceId); 74 | mGameControllerManager.onInputDeviceRemoved(deviceId); 75 | } 76 | 77 | @Override 78 | public void onInputDeviceChanged(int deviceId) { 79 | Log.d(TAG, "onInputDeviceChanged id: " + deviceId); 80 | mGameControllerManager.onInputDeviceChanged(deviceId); 81 | } 82 | } -------------------------------------------------------------------------------- /GameTextInput/src/main/java/com/google/androidgamesdk/gametextinput/GameTextInput.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.androidgamesdk.gametextinput; 17 | 18 | import android.text.Editable; 19 | import android.text.Spanned; 20 | import android.view.inputmethod.EditorInfo; 21 | 22 | /* 23 | * Singleton GameTextInput class with helper methods. 24 | */ 25 | public final class GameTextInput { 26 | private static final GameTextInput composingRegionKey; 27 | private static final Class selectionKey; 28 | 29 | public final static void copyEditorInfo(EditorInfo from, EditorInfo to) { 30 | if (from == null || to == null) 31 | return; 32 | if (from.hintText != null) { 33 | to.hintText = from.hintText; 34 | } 35 | 36 | to.inputType = from.inputType; 37 | to.imeOptions = from.imeOptions; 38 | to.label = from.label; 39 | to.initialCapsMode = from.initialCapsMode; 40 | to.privateImeOptions = from.privateImeOptions; 41 | if (from.packageName != null) { 42 | to.packageName = from.packageName; 43 | } 44 | 45 | to.fieldId = from.fieldId; 46 | if (from.fieldName != null) { 47 | to.fieldName = from.fieldName; 48 | } 49 | } 50 | 51 | public static final class Pair { 52 | int first, second; 53 | 54 | Pair(int f, int s) { 55 | first = f; 56 | second = s; 57 | } 58 | } 59 | 60 | public final static Pair getSelection(Editable editable) { 61 | return new Pair(editable.getSpanStart(selectionKey), editable.getSpanEnd(selectionKey)); 62 | } 63 | 64 | public final static Pair getComposingRegion(Editable editable) { 65 | return new Pair( 66 | editable.getSpanStart(composingRegionKey), editable.getSpanEnd(composingRegionKey)); 67 | } 68 | 69 | public final static void setSelection(Editable editable, int start, int end) { 70 | if (start > editable.length()) 71 | start = editable.length(); 72 | if (end > editable.length()) 73 | end = editable.length(); 74 | 75 | // Note that selections can be in the opposite order 76 | if (start > end) 77 | editable.setSpan(selectionKey, end, start, 0); 78 | else 79 | editable.setSpan(selectionKey, start, end, 0); 80 | } 81 | 82 | public final static void setComposingRegion(Editable editable, int start, int end) { 83 | if (start > editable.length()) 84 | start = editable.length(); 85 | if (end > editable.length()) 86 | end = editable.length(); 87 | 88 | // Note that selections can be in the opposite order 89 | if (start > end) 90 | editable.setSpan(composingRegionKey, end, start, Spanned.SPAN_COMPOSING); 91 | else 92 | editable.setSpan(composingRegionKey, start, end, Spanned.SPAN_COMPOSING); 93 | } 94 | 95 | public final static void removeComposingRegion(Editable editable) { 96 | editable.removeSpan(composingRegionKey); 97 | } 98 | 99 | public final static GameTextInput getComposingRegionKey() { 100 | return composingRegionKey; 101 | } 102 | 103 | public final static Class getSelectionKey() { 104 | return selectionKey; 105 | } 106 | 107 | private GameTextInput() {} 108 | 109 | static { 110 | composingRegionKey = new GameTextInput(); 111 | selectionKey = composingRegionKey.getClass(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/GameControllerLog.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "GameControllerLog.h" 18 | 19 | #include 20 | 21 | #include "GameControllerLogStrings.h" 22 | #include "Log.h" 23 | 24 | #define LOG_TAG "GameControllerManager" 25 | // Filter input event logging to qualifying 'gamecontroller' event sources 26 | #define LOG_FILTER_PADDLEBOAT_SOURCES 27 | 28 | #define ELEMENTS_OF(x) (sizeof(x) / sizeof(x[0])) 29 | 30 | namespace paddleboat { 31 | const char *LogGetInputSourceString(const int32_t eventSource) { 32 | const char *inputSourceString = "AINPUT_SOURCE_UNKNOWN"; 33 | 34 | switch (eventSource) { 35 | case AINPUT_SOURCE_KEYBOARD: 36 | inputSourceString = "AINPUT_SOURCE_KEYBOARD"; 37 | break; 38 | case AINPUT_SOURCE_DPAD: 39 | inputSourceString = "AINPUT_SOURCE_DPAD"; 40 | break; 41 | case AINPUT_SOURCE_GAMEPAD: 42 | inputSourceString = "AINPUT_SOURCE_GAMEPAD"; 43 | break; 44 | case AINPUT_SOURCE_TOUCHSCREEN: 45 | inputSourceString = "AINPUT_SOURCE_TOUCHSCREEN"; 46 | break; 47 | case AINPUT_SOURCE_MOUSE: 48 | inputSourceString = "AINPUT_SOURCE_MOUSE"; 49 | break; 50 | case AINPUT_SOURCE_STYLUS: 51 | inputSourceString = "AINPUT_SOURCE_STYLUS"; 52 | break; 53 | case AINPUT_SOURCE_BLUETOOTH_STYLUS: 54 | inputSourceString = "AINPUT_SOURCE_BLUETOOTH_STYLUS"; 55 | break; 56 | case AINPUT_SOURCE_MOUSE_RELATIVE: 57 | inputSourceString = "AINPUT_SOURCE_MOUSE_RELATIVE"; 58 | break; 59 | case AINPUT_SOURCE_TOUCHPAD: 60 | inputSourceString = "AINPUT_SOURCE_TOUCHPAD"; 61 | break; 62 | case AINPUT_SOURCE_TOUCH_NAVIGATION: 63 | inputSourceString = "AINPUT_SOURCE_TOUCH_NAVIGATION"; 64 | break; 65 | case AINPUT_SOURCE_JOYSTICK: 66 | inputSourceString = "AINPUT_SOURCE_JOYSTICK"; 67 | break; 68 | case AINPUT_SOURCE_ROTARY_ENCODER: 69 | inputSourceString = "AINPUT_SOURCE_ROTARY_ENCODER"; 70 | break; 71 | default: 72 | break; 73 | } 74 | return inputSourceString; 75 | } 76 | 77 | void LogInputEvent(const AInputEvent *event) { 78 | const int32_t eventSource = AInputEvent_getSource(event); 79 | #if defined LOG_FILTER_PADDLEBOAT_SOURCES 80 | if (!(eventSource == AINPUT_SOURCE_DPAD || 81 | eventSource == AINPUT_SOURCE_GAMEPAD || 82 | eventSource == AINPUT_SOURCE_JOYSTICK)) { 83 | return; 84 | } 85 | #endif 86 | const int32_t eventDeviceId = AInputEvent_getDeviceId(event); 87 | const int32_t eventType = AInputEvent_getType(event); 88 | const char *inputSourceString = LogGetInputSourceString(eventSource); 89 | 90 | if (eventType == AINPUT_EVENT_TYPE_KEY) { 91 | const int32_t eventAction = AKeyEvent_getAction(event); 92 | const int32_t eventFlags = AKeyEvent_getFlags(event); 93 | const int32_t eventKeycode = AKeyEvent_getKeyCode(event); 94 | const char *actionString = 95 | (eventAction < ELEMENTS_OF(AKEY_ACTION_STRINGS) && eventAction >= 0) 96 | ? AKEY_ACTION_STRINGS[eventAction] 97 | : "AKEY_ACTION out of range"; 98 | const char *keycodeString = 99 | (eventKeycode < ELEMENTS_OF(AKEYCODE_STRINGS) && eventKeycode >= 0) 100 | ? AKEYCODE_STRINGS[eventKeycode] 101 | : "AKEYCODE out of range"; 102 | ALOGI( 103 | "LogInputEvent\nAINPUT_EVENT_TYPE_KEY deviceId %d source %s\n%s %s " 104 | "%08x", 105 | eventDeviceId, inputSourceString, actionString, keycodeString, 106 | eventFlags); 107 | } else if (eventType == AINPUT_EVENT_TYPE_MOTION) { 108 | const int32_t eventAction = 109 | AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK; 110 | const int32_t eventFlags = AMotionEvent_getFlags(event); 111 | const char *actionString = 112 | (eventAction < ELEMENTS_OF(AMOTION_ACTION_STRINGS) && 113 | eventAction >= 0) 114 | ? AMOTION_ACTION_STRINGS[eventAction] 115 | : "AMOTION_ACTION out of range"; 116 | ALOGI( 117 | "LogInputEvent\nAINPUT_EVENT_TYPE_MOTION deviceId %d source %s\n%s " 118 | "%08x", 119 | eventDeviceId, inputSourceString, actionString, eventFlags); 120 | } 121 | } 122 | } // namespace paddleboat -------------------------------------------------------------------------------- /GameController/src/main/cpp/GameController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #include "GameControllerDeviceInfo.h" 22 | #include "GameControllerGameActivityMirror.h" 23 | #include "paddleboat.h" 24 | 25 | namespace paddleboat { 26 | 27 | // set if axisMultiplier/axisAdjustment should be applied (raw device axis isn't 28 | // -1.0 to 1.0) 29 | inline constexpr uint32_t GAMECONTROLLER_AXIS_FLAG_APPLY_ADJUSTMENTS = 30 | (1U << 0); 31 | // set if trigger is being faked as an analog axis (device just has a on/off 32 | // button flag) 33 | inline constexpr uint32_t GAMECONTROLLER_AXIS_FLAG_DIGITAL_TRIGGER = (1U << 1); 34 | 35 | class GameController { 36 | public: 37 | enum GameControllerAxis { 38 | GAMECONTROLLER_AXIS_LSTICK_X = 0, 39 | GAMECONTROLLER_AXIS_LSTICK_Y, 40 | GAMECONTROLLER_AXIS_RSTICK_X, 41 | GAMECONTROLLER_AXIS_RSTICK_Y, 42 | GAMECONTROLLER_AXIS_L1, 43 | GAMECONTROLLER_AXIS_L2, 44 | GAMECONTROLLER_AXIS_R1, 45 | GAMECONTROLLER_AXIS_R2, 46 | GAMECONTROLLER_AXIS_HAT_X, 47 | GAMECONTROLLER_AXIS_HAT_Y, 48 | GAMECONTROLLER_AXIS_COUNT 49 | }; 50 | 51 | struct GameControllerAxisInfo { 52 | // Index into the device axis array, -1 is unmapped 53 | int32_t axisIndex = -1; 54 | // See GAMECONTROLLER_AXIS_FLAG constants 55 | uint32_t axisFlags = 0; 56 | // Button mask flag, if backed/shadowed by digital button when axis > 57 | // 0.0 58 | uint32_t axisButtonMask = 0; 59 | // Button mask flag, if backed/shadowed by digital button when axis < 60 | // 0.0 61 | uint32_t axisButtonNegativeMask = 0; 62 | // Multiplier to normalize to a 0.0 center -> 1.0 edge 63 | float axisMultiplier = 1.0f; 64 | // Adjustment to bring center to 0.0 if necessary 65 | float axisAdjust = 0.0f; 66 | 67 | void resetInfo() { 68 | axisIndex = -1; 69 | axisFlags = 0; 70 | axisButtonMask = 0; 71 | axisButtonNegativeMask = 0; 72 | axisMultiplier = 1.0f; 73 | axisAdjust = 0.0f; 74 | } 75 | }; 76 | 77 | GameController(); 78 | 79 | void setupController(const Paddleboat_Controller_Mapping_Data *mappingData); 80 | 81 | void initializeDefaultAxisMapping(); 82 | 83 | int32_t processGameActivityKeyEvent( 84 | const Paddleboat_GameActivityKeyEvent *event, const size_t eventSize); 85 | 86 | int32_t processGameActivityMotionEvent( 87 | const Paddleboat_GameActivityMotionEvent *event, 88 | const size_t eventSize); 89 | 90 | int32_t processKeyEvent(const AInputEvent *event); 91 | 92 | int32_t processMotionEvent(const AInputEvent *event); 93 | 94 | Paddleboat_ControllerStatus getControllerStatus() const { 95 | return mControllerStatus; 96 | } 97 | 98 | void setControllerStatus( 99 | const Paddleboat_ControllerStatus controllerStatus) { 100 | mControllerStatus = controllerStatus; 101 | } 102 | 103 | int32_t getConnectionIndex() const { return mConnectionIndex; } 104 | 105 | void setConnectionIndex(const int32_t connectionIndex) { 106 | mConnectionIndex = connectionIndex; 107 | } 108 | 109 | uint64_t getControllerAxisMask() const { return mControllerAxisMask; } 110 | 111 | Paddleboat_Controller_Data &getControllerData() { return mControllerData; } 112 | 113 | const Paddleboat_Controller_Data &getControllerData() const { 114 | return mControllerData; 115 | } 116 | 117 | Paddleboat_Controller_Info &getControllerInfo() { return mControllerInfo; } 118 | 119 | const Paddleboat_Controller_Info &getControllerInfo() const { 120 | return mControllerInfo; 121 | } 122 | 123 | GameControllerDeviceInfo &getDeviceInfo() { return mDeviceInfo; } 124 | 125 | const GameControllerDeviceInfo &getDeviceInfo() const { 126 | return mDeviceInfo; 127 | } 128 | 129 | GameControllerAxisInfo *getAxisInfo() { return mAxisInfo; } 130 | 131 | const GameControllerAxisInfo *getAxisInfo() const { return mAxisInfo; } 132 | 133 | bool getControllerDataDirty() const { return mControllerDataDirty; } 134 | 135 | void setControllerDataDirty(const bool dirty); 136 | 137 | void resetControllerData(); 138 | 139 | private: 140 | int32_t processKeyEventInternal(const int32_t eventKeyCode, 141 | const int32_t eventKeyAction); 142 | 143 | int32_t processMotionEventInternal(const float *axisArray, 144 | const AInputEvent *event); 145 | 146 | void setupAxis(const GameControllerAxis gcAxis, 147 | const int32_t preferredNativeAxisId, 148 | const int32_t secondaryNativeAxisId, 149 | const int32_t buttonMask, const int32_t buttonNegativeMask); 150 | 151 | void adjustAxisConstants(); 152 | 153 | uint64_t mControllerAxisMask = 0; 154 | Paddleboat_ControllerStatus mControllerStatus = 155 | PADDLEBOAT_CONTROLLER_INACTIVE; 156 | int32_t mConnectionIndex = -1; 157 | Paddleboat_Controller_Data mControllerData; 158 | Paddleboat_Controller_Info mControllerInfo; 159 | int32_t mButtonKeycodes[PADDLEBOAT_BUTTON_COUNT]; 160 | GameControllerAxisInfo mAxisInfo[GAMECONTROLLER_AXIS_COUNT]; 161 | GameControllerDeviceInfo mDeviceInfo; 162 | // Controller data has been updated since the last time it was read 163 | bool mControllerDataDirty; 164 | }; 165 | } // namespace paddleboat 166 | -------------------------------------------------------------------------------- /GameController/src/main/java/com/google/android/games/paddleboat/GameControllerInfo.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Android Open Source Project 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.google.android.games.paddleboat; 15 | 16 | import android.os.Build; 17 | import android.view.InputDevice; 18 | 19 | import java.util.List; 20 | 21 | public class GameControllerInfo { 22 | private static final int MAX_AXIS_COUNT = 48; 23 | // Axis bits won't fit in 32-bits, split them across low/high 24 | private static final int AXIS_COUNT_LOW = 31; 25 | 26 | // Indices into the mGameControllerDeviceInfoArray that 27 | // pass device specific information to the native client 28 | private static final int DEVICEINFO_INDEX_DEVICEID = 0; 29 | private static final int DEVICEINFO_INDEX_VENDORID = 1; 30 | private static final int DEVICEINFO_INDEX_PRODUCTID = 2; 31 | private static final int DEVICEINFO_INDEX_AXISBITS_LOW = 3; 32 | private static final int DEVICEINFO_INDEX_AXISBITS_HIGH = 4; 33 | private static final int DEVICEINFO_INDEX_CONTROLLERNUMBER = 5; 34 | private static final int DEVICEINFO_INDEX_DEVICEFLAGS = 6; 35 | private static final int DEVICEINFO_ARRAY_SIZE = 7; 36 | 37 | private static final int DEVICEFLAG_VIBRATION = 0x8000000; 38 | private static final int DEVICEFLAG_VIBRATION_DUAL_MOTOR = 0x10000000; 39 | private static final int DEVICEFLAG_VIRTUAL_MOUSE = 0x40000000; 40 | 41 | private final int[] mGameControllerDeviceInfoArray; 42 | private final float[] mGameControllerAxisMinArray; 43 | private final float[] mGameControllerAxisMaxArray; 44 | private final float[] mGameControllerAxisFlatArray; 45 | private final float[] mGameControllerAxisFuzzArray; 46 | private final String mGameControllerNameString; 47 | 48 | private GameControllerListener mListener = null; 49 | 50 | GameControllerInfo(InputDevice inputDevice) { 51 | mGameControllerDeviceInfoArray = new int[DEVICEINFO_ARRAY_SIZE]; 52 | mGameControllerAxisMinArray = new float[MAX_AXIS_COUNT]; 53 | mGameControllerAxisMaxArray = new float[MAX_AXIS_COUNT]; 54 | mGameControllerAxisFlatArray = new float[MAX_AXIS_COUNT]; 55 | mGameControllerAxisFuzzArray = new float[MAX_AXIS_COUNT]; 56 | 57 | for (int index = 0; index < DEVICEINFO_ARRAY_SIZE; ++index) { 58 | mGameControllerDeviceInfoArray[index] = 0; 59 | } 60 | 61 | for (int index = 0; index < MAX_AXIS_COUNT; ++index) { 62 | mGameControllerAxisMinArray[index] = 0.0f; 63 | mGameControllerAxisMaxArray[index] = 0.0f; 64 | mGameControllerAxisFlatArray[index] = 0.0f; 65 | mGameControllerAxisFuzzArray[index] = 0.0f; 66 | } 67 | 68 | mGameControllerNameString = inputDevice.getName(); 69 | EnumerateAxis(inputDevice); 70 | EnumerateInfoArray(inputDevice); 71 | } 72 | 73 | public GameControllerListener GetListener() { 74 | return mListener; 75 | } 76 | 77 | public void SetListener(GameControllerListener listener) { 78 | mListener = listener; 79 | } 80 | 81 | public int GetGameControllerDeviceId() { 82 | return mGameControllerDeviceInfoArray[DEVICEINFO_INDEX_DEVICEID]; 83 | } 84 | 85 | public int GetGameControllerFlags() { 86 | return mGameControllerDeviceInfoArray[DEVICEINFO_INDEX_DEVICEFLAGS]; 87 | } 88 | 89 | public int[] GetGameControllerDeviceInfoArray() { 90 | return mGameControllerDeviceInfoArray; 91 | } 92 | 93 | public float[] GetGameControllerAxisMinArray() { 94 | return mGameControllerAxisMinArray; 95 | } 96 | 97 | public float[] GetGameControllerAxisMaxArray() { 98 | return mGameControllerAxisMaxArray; 99 | } 100 | 101 | public float[] GetGameControllerAxisFlatArray() { 102 | return mGameControllerAxisFlatArray; 103 | } 104 | 105 | public float[] GetGameControllerAxisFuzzArray() { 106 | return mGameControllerAxisFuzzArray; 107 | } 108 | 109 | public String GetGameControllerNameString() { 110 | return mGameControllerNameString; 111 | } 112 | 113 | private void EnumerateAxis(InputDevice inputDevice) { 114 | List motionRanges = inputDevice.getMotionRanges(); 115 | for (InputDevice.MotionRange motionRange : motionRanges) { 116 | int axisIndex = motionRange.getAxis(); 117 | if (axisIndex >= 0 && axisIndex < MAX_AXIS_COUNT) { 118 | int axisSource = motionRange.getSource(); 119 | if (axisSource == InputDevice.SOURCE_JOYSTICK || 120 | axisSource == InputDevice.SOURCE_GAMEPAD) { 121 | if (axisIndex <= AXIS_COUNT_LOW) { 122 | mGameControllerDeviceInfoArray[DEVICEINFO_INDEX_AXISBITS_LOW] |= 123 | (1 << axisIndex); 124 | } else { 125 | mGameControllerDeviceInfoArray[DEVICEINFO_INDEX_AXISBITS_HIGH] |= 126 | (1 << (axisIndex - (AXIS_COUNT_LOW + 1))); 127 | } 128 | mGameControllerAxisMinArray[axisIndex] = motionRange.getMin(); 129 | mGameControllerAxisMaxArray[axisIndex] = motionRange.getMax(); 130 | mGameControllerAxisFlatArray[axisIndex] = motionRange.getFlat(); 131 | mGameControllerAxisFuzzArray[axisIndex] = motionRange.getFuzz(); 132 | } 133 | } 134 | } 135 | } 136 | 137 | private void EnumerateInfoArray(InputDevice inputDevice) { 138 | mGameControllerDeviceInfoArray[DEVICEINFO_INDEX_DEVICEID] = inputDevice.getId(); 139 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 140 | mGameControllerDeviceInfoArray[DEVICEINFO_INDEX_VENDORID] = inputDevice.getVendorId(); 141 | mGameControllerDeviceInfoArray[DEVICEINFO_INDEX_PRODUCTID] = inputDevice.getProductId(); 142 | mGameControllerDeviceInfoArray[DEVICEINFO_INDEX_CONTROLLERNUMBER] = 143 | inputDevice.getControllerNumber(); 144 | } 145 | mGameControllerDeviceInfoArray[DEVICEINFO_INDEX_DEVICEFLAGS] = 146 | GameControllerManager.getControllerFlagsForDevice(inputDevice); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/paddleboat_c.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "GameControllerManager.h" 18 | #include "paddleboat.h" 19 | 20 | using namespace paddleboat; 21 | 22 | extern "C" { 23 | 24 | // Internal macros to track Paddleboat version, do not use directly. 25 | #define PADDLEBOAT_MAJOR_VERSION 1 26 | #define PADDLEBOAT_MINOR_VERSION 1 27 | #define PADDLEBOAT_BUGFIX_VERSION 0 28 | 29 | #define PADDLEBOAT_PACKED_VERSION \ 30 | ((PADDLEBOAT_MAJOR_VERSION << 24) | (PADDLEBOAT_MINOR_VERSION << 16) | \ 31 | (PADDLEBOAT_BUGFIX_VERSION)) 32 | 33 | #define PADDLEBOAT_VERSION_CONCAT_NX(PREFIX, MAJOR, MINOR, BUGFIX) \ 34 | PREFIX##_##MAJOR##_##MINOR##_##BUGFIX 35 | #define PADDLEBOAT_VERSION_CONCAT(PREFIX, MAJOR, MINOR, BUGFIX) \ 36 | PADDLEBOAT_VERSION_CONCAT_NX(PREFIX, MAJOR, MINOR, BUGFIX) 37 | #define PADDLEBOAT_VERSION_SYMBOL \ 38 | PADDLEBOAT_VERSION_CONCAT(PADDLEBOAT_version, PADDLEBOAT_MAJOR_VERSION, \ 39 | PADDLEBOAT_MINOR_VERSION, \ 40 | PADDLEBOAT_BUGFIX_VERSION) 41 | 42 | void PADDLEBOAT_VERSION_SYMBOL(); 43 | 44 | Paddleboat_ErrorCode Paddleboat_init(JNIEnv *env, jobject jcontext) { 45 | PADDLEBOAT_VERSION_SYMBOL(); 46 | Paddleboat_ErrorCode errorCode = GameControllerManager::init(env, jcontext); 47 | if (errorCode == PADDLEBOAT_NO_ERROR) { 48 | GameControllerManager::update(env); 49 | } 50 | return errorCode; 51 | } 52 | 53 | bool Paddleboat_isInitialized() { 54 | return GameControllerManager::isInitialized(); 55 | } 56 | 57 | void Paddleboat_destroy(JNIEnv *env) { 58 | GameControllerManager::destroyInstance(env); 59 | } 60 | 61 | void Paddleboat_onStop(JNIEnv *env) { GameControllerManager::onStop(env); } 62 | 63 | void Paddleboat_onStart(JNIEnv *env) { GameControllerManager::onStart(env); } 64 | 65 | int32_t Paddleboat_processInputEvent(const AInputEvent *event) { 66 | return GameControllerManager::processInputEvent(event); 67 | } 68 | 69 | int32_t Paddleboat_processGameActivityKeyInputEvent(const void *event, 70 | const size_t eventSize) { 71 | return GameControllerManager::processGameActivityKeyInputEvent(event, 72 | eventSize); 73 | } 74 | 75 | int32_t Paddleboat_processGameActivityMotionInputEvent(const void *event, 76 | const size_t eventSize) { 77 | return GameControllerManager::processGameActivityMotionInputEvent( 78 | event, eventSize); 79 | } 80 | 81 | uint64_t Paddleboat_getActiveAxisMask() { 82 | return GameControllerManager::getActiveAxisMask(); 83 | } 84 | 85 | bool Paddleboat_getBackButtonConsumed() { 86 | return GameControllerManager::getBackButtonConsumed(); 87 | } 88 | 89 | void Paddleboat_setBackButtonConsumed(bool consumeBackButton) { 90 | GameControllerManager::setBackButtonConsumed(consumeBackButton); 91 | } 92 | 93 | void Paddleboat_setControllerStatusCallback( 94 | Paddleboat_ControllerStatusCallback statusCallback, void *userData) { 95 | GameControllerManager::setControllerStatusCallback(statusCallback, 96 | userData); 97 | } 98 | 99 | void Paddleboat_setMotionDataCallback( 100 | Paddleboat_MotionDataCallback motionDataCallback, void *userData) { 101 | GameControllerManager::setMotionDataCallback(motionDataCallback, userData); 102 | } 103 | 104 | void Paddleboat_setMouseStatusCallback( 105 | Paddleboat_MouseStatusCallback statusCallback, void *userData) { 106 | GameControllerManager::setMouseStatusCallback(statusCallback, userData); 107 | } 108 | 109 | Paddleboat_ErrorCode Paddleboat_getControllerData( 110 | const int32_t controllerIndex, Paddleboat_Controller_Data *controllerData) { 111 | return GameControllerManager::getControllerData(controllerIndex, 112 | controllerData); 113 | } 114 | 115 | Paddleboat_ErrorCode Paddleboat_getControllerInfo( 116 | const int32_t controllerIndex, Paddleboat_Controller_Info *controllerInfo) { 117 | return GameControllerManager::getControllerInfo(controllerIndex, 118 | controllerInfo); 119 | } 120 | 121 | Paddleboat_ErrorCode Paddleboat_getControllerName(const int32_t controllerIndex, 122 | const size_t bufferSize, 123 | char *controllerName) { 124 | return GameControllerManager::getControllerName(controllerIndex, bufferSize, 125 | controllerName); 126 | } 127 | 128 | Paddleboat_ControllerStatus Paddleboat_getControllerStatus( 129 | const int32_t controllerIndex) { 130 | return GameControllerManager::getControllerStatus(controllerIndex); 131 | } 132 | 133 | Paddleboat_ErrorCode Paddleboat_setControllerLight( 134 | const int32_t controllerIndex, const Paddleboat_LightType lightType, 135 | const uint32_t lightData, JNIEnv *env) { 136 | return GameControllerManager::setControllerLight(controllerIndex, lightType, 137 | lightData, env); 138 | } 139 | 140 | Paddleboat_ErrorCode Paddleboat_setControllerVibrationData( 141 | const int32_t controllerIndex, 142 | const Paddleboat_Vibration_Data *vibrationData, JNIEnv *env) { 143 | return GameControllerManager::setControllerVibrationData( 144 | controllerIndex, vibrationData, env); 145 | } 146 | 147 | Paddleboat_ErrorCode Paddleboat_getMouseData(Paddleboat_Mouse_Data *mouseData) { 148 | return GameControllerManager::getMouseData(mouseData); 149 | } 150 | 151 | Paddleboat_MouseStatus Paddleboat_getMouseStatus() { 152 | return GameControllerManager::getMouseStatus(); 153 | } 154 | 155 | void Paddleboat_addControllerRemapData( 156 | const Paddleboat_Remap_Addition_Mode addMode, 157 | const int32_t remapTableEntryCount, 158 | const Paddleboat_Controller_Mapping_Data *mappingData) { 159 | GameControllerManager::addControllerRemapData(addMode, remapTableEntryCount, 160 | mappingData); 161 | } 162 | 163 | int32_t Paddleboat_getControllerRemapTableData( 164 | const int32_t destRemapTableEntryCount, 165 | Paddleboat_Controller_Mapping_Data *mappingData) { 166 | return GameControllerManager::getControllerRemapTableData( 167 | destRemapTableEntryCount, mappingData); 168 | } 169 | 170 | void Paddleboat_update(JNIEnv *env) { GameControllerManager::update(env); } 171 | 172 | int32_t Paddleboat_getLastKeycode() { 173 | return GameControllerManager::getLastKeycode(); 174 | } 175 | 176 | void PADDLEBOAT_VERSION_SYMBOL() { 177 | // Intentionally empty: this function is used to ensure that the proper 178 | // version of the library is linked against the proper headers. 179 | // In case of mismatch, a linker error will be triggered because of an 180 | // undefined symbol, as the name of the function depends on the version. 181 | } 182 | } // extern "C" { -------------------------------------------------------------------------------- /src/extras/src/main/java/com/google/androidgamesdk/SwappyDisplayManager.java: -------------------------------------------------------------------------------- 1 | package com.google.androidgamesdk; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.ComponentName; 6 | import android.content.pm.ActivityInfo; 7 | import android.content.pm.PackageManager; 8 | import android.hardware.display.DisplayManager; 9 | import android.os.Build; 10 | import android.os.Handler; 11 | import android.os.Looper; 12 | import android.util.Log; 13 | import android.view.Display; 14 | import android.view.Window; 15 | import android.view.WindowManager; 16 | 17 | import java.util.concurrent.locks.Condition; 18 | import java.util.concurrent.locks.Lock; 19 | import java.util.concurrent.locks.ReentrantLock; 20 | 21 | import static android.app.NativeActivity.META_DATA_LIB_NAME; 22 | 23 | public class SwappyDisplayManager implements DisplayManager.DisplayListener { 24 | final private String LOG_TAG = "SwappyDisplayManager"; 25 | final private boolean DEBUG = false; 26 | final private long ONE_MS_IN_NS = 1000000; 27 | final private long ONE_S_IN_NS = ONE_MS_IN_NS * 1000; 28 | 29 | private long mCookie; 30 | private Activity mActivity; 31 | private WindowManager mWindowManager; 32 | private Display.Mode mCurrentMode; 33 | 34 | private LooperThread mLooper; 35 | 36 | private class LooperThread extends Thread { 37 | public Handler mHandler; 38 | private Lock mLock = new ReentrantLock(); 39 | private Condition mCondition = mLock.newCondition(); 40 | 41 | @Override 42 | public void start() { 43 | mLock.lock(); 44 | super.start(); 45 | try { 46 | mCondition.await(); 47 | } catch (InterruptedException e) { 48 | e.printStackTrace(); 49 | } 50 | mLock.unlock(); 51 | 52 | } 53 | 54 | public void run() { 55 | Log.i(LOG_TAG, "Starting looper thread"); 56 | 57 | mLock.lock(); 58 | Looper.prepare(); 59 | mHandler = new Handler(); 60 | mCondition.signal(); 61 | mLock.unlock(); 62 | 63 | Looper.loop(); 64 | 65 | Log.i(LOG_TAG, "Terminating looper thread"); 66 | } 67 | } 68 | 69 | @TargetApi(Build.VERSION_CODES.M) 70 | private boolean modeMatchesCurrentResolution(Display.Mode mode) { 71 | return mode.getPhysicalHeight() == mCurrentMode.getPhysicalHeight() && 72 | mode.getPhysicalWidth() == mCurrentMode.getPhysicalWidth(); 73 | 74 | } 75 | 76 | public SwappyDisplayManager(long cookie, Activity activity) { 77 | // Load the native library for cases where an NDK application is running 78 | // without a java componenet 79 | try { 80 | ActivityInfo ai = activity.getPackageManager().getActivityInfo( 81 | activity.getIntent().getComponent(), PackageManager.GET_META_DATA); 82 | if (ai.metaData != null) { 83 | String nativeLibName = ai.metaData.getString(META_DATA_LIB_NAME); 84 | if (nativeLibName != null) { 85 | System.loadLibrary(nativeLibName); 86 | } 87 | } 88 | } catch (java.lang.Throwable e) { 89 | Log.e(LOG_TAG, e.getMessage()); 90 | } 91 | 92 | mCookie = cookie; 93 | mActivity = activity; 94 | 95 | mWindowManager = mActivity.getSystemService(WindowManager.class); 96 | Display display = mWindowManager.getDefaultDisplay(); 97 | mCurrentMode = display.getMode(); 98 | updateSupportedRefreshRates(display); 99 | 100 | // Register display listener callbacks 101 | DisplayManager dm = mActivity.getSystemService(DisplayManager.class); 102 | 103 | synchronized(this) { 104 | mLooper = new LooperThread(); 105 | mLooper.start(); 106 | dm.registerDisplayListener(this, mLooper.mHandler); 107 | } 108 | } 109 | 110 | private void updateSupportedRefreshRates(Display display) { 111 | Display.Mode[] supportedModes = display.getSupportedModes(); 112 | int totalModes = 0; 113 | for (int i = 0; i < supportedModes.length; i++) { 114 | if (!modeMatchesCurrentResolution(supportedModes[i])) { 115 | continue; 116 | } 117 | totalModes++; 118 | } 119 | 120 | long[] supportedRefreshPeriods = new long[totalModes]; 121 | int[] supportedDisplayModeIds = new int[totalModes]; 122 | totalModes = 0; 123 | for (int i = 0; i < supportedModes.length; i++) { 124 | if (!modeMatchesCurrentResolution(supportedModes[i])) { 125 | continue; 126 | } 127 | supportedRefreshPeriods[totalModes] = 128 | (long) (ONE_S_IN_NS / supportedModes[i].getRefreshRate()); 129 | supportedDisplayModeIds[totalModes] = supportedModes[i].getModeId(); 130 | totalModes++; 131 | 132 | } 133 | // Call down to native to set the supported refresh rates 134 | nSetSupportedRefreshPeriods(mCookie, supportedRefreshPeriods, supportedDisplayModeIds); 135 | } 136 | 137 | public void setPreferredDisplayModeId(final int modeId) { 138 | mActivity.runOnUiThread(new Runnable() { 139 | @Override 140 | public void run() { 141 | Window w = mActivity.getWindow(); 142 | WindowManager.LayoutParams l = w.getAttributes(); 143 | if (DEBUG) { 144 | Log.v(LOG_TAG, "set preferredDisplayModeId to " + modeId); 145 | } 146 | l.preferredDisplayModeId = modeId; 147 | 148 | 149 | w.setAttributes(l); 150 | } 151 | }); 152 | } 153 | 154 | public void terminate() { 155 | mLooper.mHandler.getLooper().quit(); 156 | } 157 | 158 | @Override 159 | public void onDisplayAdded(int displayId) { 160 | 161 | } 162 | 163 | @Override 164 | public void onDisplayRemoved(int displayId) { 165 | 166 | } 167 | 168 | @Override 169 | public void onDisplayChanged(int displayId) { 170 | synchronized(this) { 171 | Display display = mWindowManager.getDefaultDisplay(); 172 | float newRefreshRate = display.getRefreshRate(); 173 | Display.Mode newMode = display.getMode(); 174 | boolean resolutionChanged = 175 | (newMode.getPhysicalWidth() != mCurrentMode.getPhysicalWidth()) | 176 | (newMode.getPhysicalHeight() != mCurrentMode.getPhysicalHeight()); 177 | boolean refreshRateChanged = (newRefreshRate != mCurrentMode.getRefreshRate()); 178 | mCurrentMode = newMode; 179 | 180 | if (resolutionChanged) { 181 | updateSupportedRefreshRates(display); 182 | } 183 | 184 | if (refreshRateChanged) { 185 | final long appVsyncOffsetNanos = display.getAppVsyncOffsetNanos(); 186 | final long vsyncPresentationDeadlineNanos = 187 | mWindowManager.getDefaultDisplay().getPresentationDeadlineNanos(); 188 | 189 | final long vsyncPeriodNanos = (long)(ONE_S_IN_NS / newRefreshRate); 190 | final long sfVsyncOffsetNanos = 191 | vsyncPeriodNanos - (vsyncPresentationDeadlineNanos - ONE_MS_IN_NS); 192 | 193 | nOnRefreshPeriodChanged(mCookie, 194 | vsyncPeriodNanos, 195 | appVsyncOffsetNanos, 196 | sfVsyncOffsetNanos); 197 | } 198 | } 199 | } 200 | 201 | private native void nSetSupportedRefreshPeriods(long cookie, 202 | long[] refreshPeriods, 203 | int[] modeIds); 204 | private native void nOnRefreshPeriodChanged(long cookie, 205 | long refreshPeriod, 206 | long appOffset, 207 | long sfOffset); 208 | } 209 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/GameControllerMappingUtils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "GameControllerMappingUtils.h" 18 | 19 | #include "GameControllerManager.h" 20 | 21 | namespace paddleboat { 22 | MappingTableSearch::MappingTableSearch() 23 | : mappingRoot(nullptr), 24 | vendorId(0), 25 | productId(0), 26 | minApi(0), 27 | maxApi(0), 28 | tableIndex(0), 29 | mapEntryCount(0), 30 | tableEntryCount(0), 31 | tableMaxEntryCount(GameControllerManager::getRemapTableSize()) {} 32 | 33 | MappingTableSearch::MappingTableSearch( 34 | Paddleboat_Controller_Mapping_Data *mapRoot, int32_t entryCount) 35 | : mappingRoot(mapRoot), 36 | vendorId(0), 37 | productId(0), 38 | minApi(0), 39 | maxApi(0), 40 | tableIndex(0), 41 | mapEntryCount(0), 42 | tableEntryCount(entryCount), 43 | tableMaxEntryCount(GameControllerManager::getRemapTableSize()) {} 44 | 45 | void MappingTableSearch::initSearchParameters(const int32_t newVendorId, 46 | const int32_t newProductId, 47 | const int32_t newMinApi, 48 | const int32_t newMaxApi) { 49 | vendorId = newVendorId; 50 | productId = newProductId; 51 | minApi = newMinApi; 52 | maxApi = newMaxApi; 53 | tableIndex = 0; 54 | } 55 | 56 | bool GameControllerMappingUtils::findMatchingMapEntry( 57 | MappingTableSearch *searchEntry) { 58 | int32_t currentIndex = 0; 59 | 60 | // Starting out with a linear search. Updating the map table is something 61 | // that should only ever be done once at startup, if it actually takes an 62 | // appreciable time to execute when working with a big remap dictionary, 63 | // this is low-hanging fruit to optimize. 64 | const Paddleboat_Controller_Mapping_Data *mapRoot = 65 | searchEntry->mappingRoot; 66 | while (currentIndex < searchEntry->tableEntryCount) { 67 | const Paddleboat_Controller_Mapping_Data &mapEntry = 68 | mapRoot[currentIndex]; 69 | if (mapEntry.vendorId > searchEntry->vendorId) { 70 | // Passed by the search vendorId value, so we don't already exist in 71 | // the table, set the current index as the insert point and bail 72 | searchEntry->tableIndex = currentIndex; 73 | return false; 74 | } else if (searchEntry->vendorId == mapEntry.vendorId) { 75 | if (mapEntry.productId > searchEntry->productId) { 76 | // Passed by the search productId value, so we don't already 77 | // exist in the table, set the current index as the insert point 78 | // and bail 79 | searchEntry->tableIndex = currentIndex; 80 | return false; 81 | } else if (searchEntry->productId == mapEntry.productId) { 82 | // Any overlap of the min/max API range is treated as matching 83 | // an existing entry 84 | if ((searchEntry->minApi >= mapEntry.minimumEffectiveApiLevel && 85 | searchEntry->minApi <= 86 | mapEntry.maximumEffectiveApiLevel) || 87 | (searchEntry->minApi >= mapEntry.minimumEffectiveApiLevel && 88 | mapEntry.maximumEffectiveApiLevel == 0)) { 89 | searchEntry->tableIndex = currentIndex; 90 | return true; 91 | } 92 | } 93 | } 94 | ++currentIndex; 95 | } 96 | searchEntry->tableIndex = currentIndex; 97 | return false; 98 | } 99 | 100 | bool GameControllerMappingUtils::insertMapEntry( 101 | const Paddleboat_Controller_Mapping_Data *mappingData, 102 | MappingTableSearch *searchEntry) { 103 | bool insertSuccess = false; 104 | // Verify there is room in the table for another entry 105 | if (searchEntry->tableEntryCount < searchEntry->tableMaxEntryCount && 106 | searchEntry->tableIndex < searchEntry->tableMaxEntryCount) { 107 | // Empty table, or inserting at the end, no relocation of existing data 108 | // required, otherwise shift existing data down starting at the insert 109 | // index. 110 | if (!(searchEntry->tableEntryCount == 0 || 111 | searchEntry->tableIndex == searchEntry->tableEntryCount)) { 112 | const size_t copySize = 113 | (searchEntry->tableEntryCount - searchEntry->tableIndex) * 114 | sizeof(Paddleboat_Controller_Mapping_Data); 115 | memmove(&searchEntry->mappingRoot[searchEntry->tableIndex + 1], 116 | &searchEntry->mappingRoot[searchEntry->tableIndex], 117 | copySize); 118 | } 119 | // Insert the new data 120 | memcpy(&searchEntry->mappingRoot[searchEntry->tableIndex], mappingData, 121 | sizeof(Paddleboat_Controller_Mapping_Data)); 122 | insertSuccess = true; 123 | } 124 | return insertSuccess; 125 | } 126 | 127 | const Paddleboat_Controller_Mapping_Data * 128 | GameControllerMappingUtils::validateMapTable( 129 | const Paddleboat_Controller_Mapping_Data *mappingRoot, 130 | const int32_t tableEntryCount) { 131 | // The map table is always assumed to be sorted by increasing vendorId. Each 132 | // sequence of entries with the same vendorId are sorted by increasing 133 | // productId. Multiple entries with the same vendorId/productId range are 134 | // sorted by increasing min/max API ranges. vendorId 135 | // productId 136 | // minApi 137 | int32_t currentIndex = 0; 138 | int32_t previousVendorId = -1; 139 | while (currentIndex < tableEntryCount) { 140 | if (mappingRoot[currentIndex].vendorId < previousVendorId) { 141 | // failure in vendorId order, return the offending entry 142 | return &mappingRoot[currentIndex]; 143 | } 144 | 145 | int32_t previousProductId = mappingRoot[currentIndex].productId; 146 | int32_t previousMinApi = 147 | mappingRoot[currentIndex].minimumEffectiveApiLevel; 148 | int32_t previousMaxApi = 149 | mappingRoot[currentIndex].maximumEffectiveApiLevel; 150 | previousVendorId = mappingRoot[currentIndex++].vendorId; 151 | 152 | while (currentIndex < tableEntryCount && 153 | mappingRoot[currentIndex].vendorId == previousVendorId) { 154 | while (currentIndex < tableEntryCount && 155 | mappingRoot[currentIndex].productId == previousProductId) { 156 | if (mappingRoot[currentIndex].minimumEffectiveApiLevel < 157 | previousMinApi || 158 | mappingRoot[currentIndex].minimumEffectiveApiLevel < 159 | previousMaxApi) { 160 | // failure in API order, return the offending entry 161 | return &mappingRoot[currentIndex]; 162 | } 163 | previousMinApi = 164 | mappingRoot[currentIndex].minimumEffectiveApiLevel; 165 | previousMaxApi = 166 | mappingRoot[currentIndex++].maximumEffectiveApiLevel; 167 | } 168 | if (mappingRoot[currentIndex].productId < previousProductId) { 169 | // failure in productId order, return the offending entry 170 | return &mappingRoot[currentIndex]; 171 | } 172 | previousProductId = mappingRoot[currentIndex++].productId; 173 | } 174 | } 175 | 176 | // Validation success, return nullptr (no offending entries to return) 177 | return nullptr; 178 | } 179 | } // namespace paddleboat 180 | -------------------------------------------------------------------------------- /GameController/src/main/cpp/GameControllerManager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "GameController.h" 25 | #include "ThreadUtil.h" 26 | 27 | namespace paddleboat { 28 | 29 | class GameControllerManager { 30 | private: 31 | // Allows construction with std::unique_ptr from a static method, but 32 | // disallows construction outside of the class since no one else can 33 | // construct a ConstructorTag 34 | struct ConstructorTag {}; 35 | 36 | static constexpr int32_t MAX_MOUSE_DEVICES = 2; 37 | static constexpr int32_t INVALID_MOUSE_ID = -1; 38 | static constexpr int32_t MAX_REMAP_TABLE_SIZE = 256; 39 | 40 | // Assuming update is getting called at 60Hz, wait one minute in between 41 | // checking battery status 42 | static constexpr int32_t BATTERY_REFRESH_WAIT = 60 * 60; 43 | 44 | public: 45 | GameControllerManager(JNIEnv *env, jobject jcontext, ConstructorTag); 46 | 47 | ~GameControllerManager(); 48 | 49 | static inline int32_t getRemapTableSize() { return MAX_REMAP_TABLE_SIZE; } 50 | 51 | static Paddleboat_ErrorCode init(JNIEnv *env, jobject jcontext); 52 | 53 | static void destroyInstance(JNIEnv *env); 54 | 55 | static bool isInitialized(); 56 | 57 | // Get/Set whether AKEYCODE_BACK is 'eaten' or allowed to pass through to 58 | // the system This can be used to block the OS backing out of the game, or 59 | // allowing it if the game is in an appropriate state (i.e. the title 60 | // screen) 61 | static bool getBackButtonConsumed(); 62 | 63 | static void setBackButtonConsumed(bool consumed); 64 | 65 | static Paddleboat_ErrorCode setControllerLight( 66 | const int32_t controllerIndex, const Paddleboat_LightType lightType, 67 | const uint32_t lightData, JNIEnv *env); 68 | 69 | static void setControllerStatusCallback( 70 | Paddleboat_ControllerStatusCallback statusCallback, void *userData); 71 | 72 | static void setMotionDataCallback( 73 | Paddleboat_MotionDataCallback motionDataCallback, void *userData); 74 | 75 | static void setMouseStatusCallback( 76 | Paddleboat_MouseStatusCallback statusCallback, void *userData); 77 | 78 | static void onStop(JNIEnv *env); 79 | 80 | static void onStart(JNIEnv *env); 81 | 82 | static void terminate(JNIEnv *env); 83 | 84 | static void update(JNIEnv *env); 85 | 86 | static Paddleboat_ErrorCode getControllerData( 87 | const int32_t controllerIndex, 88 | Paddleboat_Controller_Data *controllerData); 89 | 90 | static Paddleboat_ErrorCode getControllerInfo( 91 | const int32_t controllerIndex, Paddleboat_Controller_Info *deviceInfo); 92 | 93 | static Paddleboat_ErrorCode getControllerName(const int32_t controllerIndex, 94 | const size_t bufferSize, 95 | char *controllerName); 96 | 97 | static Paddleboat_ControllerStatus getControllerStatus( 98 | const int32_t controllerIndex); 99 | 100 | static Paddleboat_ErrorCode setControllerVibrationData( 101 | const int32_t controllerIndex, 102 | const Paddleboat_Vibration_Data *vibrationData, JNIEnv *env); 103 | 104 | static Paddleboat_ErrorCode getMouseData(Paddleboat_Mouse_Data *mouseData); 105 | 106 | static Paddleboat_MouseStatus getMouseStatus(); 107 | 108 | static int32_t processInputEvent(const AInputEvent *event); 109 | 110 | static int32_t processGameActivityKeyInputEvent(const void *event, 111 | const size_t eventSize); 112 | 113 | static int32_t processGameActivityMotionInputEvent(const void *event, 114 | const size_t eventSize); 115 | 116 | static uint64_t getActiveAxisMask(); 117 | 118 | static void addControllerRemapData( 119 | const Paddleboat_Remap_Addition_Mode addMode, 120 | const int32_t remapTableEntryCount, 121 | const Paddleboat_Controller_Mapping_Data *mappingData); 122 | 123 | static int32_t getControllerRemapTableData( 124 | const int32_t destRemapTableEntryCount, 125 | Paddleboat_Controller_Mapping_Data *mappingData); 126 | 127 | // Called from the JNI bridge functions 128 | static GameControllerDeviceInfo *onConnection(); 129 | 130 | static void onDisconnection(const int32_t deviceId); 131 | 132 | static void onMotionData(const int32_t deviceId, const int32_t motionType, 133 | const uint64_t timestamp, const float dataX, 134 | const float dataY, const float dataZ); 135 | 136 | static void onMouseConnection(const int32_t deviceId); 137 | 138 | static void onMouseDisconnection(const int32_t deviceId); 139 | 140 | static jclass getGameControllerClass(); 141 | 142 | static jobject getGameControllerObject(); 143 | 144 | // device debug helper function 145 | static int32_t getLastKeycode(); 146 | 147 | private: 148 | static GameControllerManager *getInstance(); 149 | 150 | Paddleboat_ErrorCode initMethods(JNIEnv *env); 151 | 152 | bool isLightTypeSupported(const Paddleboat_Controller_Info &controllerInfo, 153 | const Paddleboat_LightType lightType); 154 | 155 | int32_t processControllerKeyEvent(const AInputEvent *event, 156 | GameController &gameController); 157 | 158 | int32_t processControllerGameActivityKeyEvent( 159 | const Paddleboat_GameActivityKeyEvent *event, const size_t eventSize, 160 | GameController &gameController); 161 | 162 | int32_t processMouseEvent(const AInputEvent *event); 163 | 164 | int32_t processGameActivityMouseEvent(const void *event, 165 | const size_t eventSize, 166 | const int32_t eventDeviceId); 167 | 168 | void rescanVirtualMouseControllers(); 169 | 170 | void updateBattery(JNIEnv *env); 171 | 172 | void updateMouseDataTimestamp(); 173 | 174 | void releaseGlobals(JNIEnv *env); 175 | 176 | const Paddleboat_Controller_Mapping_Data *getMapForController( 177 | const GameController &gameController); 178 | 179 | bool mInitialized = false; 180 | bool mGCMClassInitialized = false; 181 | bool mBackButtonConsumed = true; 182 | bool mMotionEventReporting = false; 183 | 184 | int32_t mApiLevel = 16; 185 | int32_t mBatteryWait = BATTERY_REFRESH_WAIT; 186 | jobject mContext = NULL; 187 | jclass mGameControllerClass = NULL; 188 | jobject mGameControllerObject = NULL; 189 | jmethodID mInitMethodId = NULL; 190 | jmethodID mGetApiLevelMethodId = NULL; 191 | jmethodID mGetBatteryLevelMethodId = NULL; 192 | jmethodID mGetBatteryStatusMethodId = NULL; 193 | jmethodID mSetLightMethodId = NULL; 194 | jmethodID mSetNativeReadyMethodId = NULL; 195 | jmethodID mSetReportMotionEventsMethodId = NULL; 196 | jmethodID mSetVibrationMethodId = NULL; 197 | 198 | uint64_t mActiveAxisMask = 0; 199 | 200 | int32_t mRemapEntryCount = 0; 201 | Paddleboat_Controller_Mapping_Data mMappingTable[MAX_REMAP_TABLE_SIZE]; 202 | 203 | Paddleboat_MotionDataCallback mMotionDataCallback = nullptr; 204 | void *mMotionDataCallbackUserData = nullptr; 205 | 206 | GameController mGameControllers[PADDLEBOAT_MAX_CONTROLLERS]; 207 | Paddleboat_ControllerStatusCallback mStatusCallback = nullptr; 208 | void *mStatusCallbackUserData = nullptr; 209 | // device debug helper 210 | int32_t mLastKeyEventKeyCode = 0; 211 | 212 | Paddleboat_MouseStatus mMouseStatus = PADDLEBOAT_MOUSE_NONE; 213 | int32_t mMouseDeviceIds[MAX_MOUSE_DEVICES] = {INVALID_MOUSE_ID, 214 | INVALID_MOUSE_ID}; 215 | int32_t mMouseControllerIndex = INVALID_MOUSE_ID; 216 | Paddleboat_Mouse_Data mMouseData; 217 | Paddleboat_MouseStatusCallback mMouseCallback = nullptr; 218 | void *mMouseCallbackUserData = nullptr; 219 | 220 | std::mutex mUpdateMutex; 221 | static std::mutex sInstanceMutex; 222 | static std::unique_ptr sInstance 223 | GUARDED_BY(sInstanceMutex); 224 | }; 225 | } // namespace paddleboat 226 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /GameTextInput/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * @defgroup game_text_input Game Text Input 19 | * The interface to use GameTextInput. 20 | * @{ 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "gamecommon.h" 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | /** 36 | * This struct holds a span within a region of text from start (inclusive) to 37 | * end (exclusive). An empty span or cursor position is specified with 38 | * start==end. An undefined span is specified with start = end = SPAN_UNDEFINED. 39 | */ 40 | typedef struct GameTextInputSpan { 41 | /** The start of the region (inclusive). */ 42 | int32_t start; 43 | /** The end of the region (exclusive). */ 44 | int32_t end; 45 | } GameTextInputSpan; 46 | 47 | /** 48 | * Values with special meaning in a GameTextInputSpan. 49 | */ 50 | enum GameTextInputSpanFlag { SPAN_UNDEFINED = -1 }; 51 | 52 | /** 53 | * This struct holds the state of an editable section of text. 54 | * The text can have a selection and a composing region defined on it. 55 | * A composing region is used by IMEs that allow input using multiple steps to 56 | * compose a glyph or word. Use functions GameTextInput_getState and 57 | * GameTextInput_setState to read and modify the state that an IME is editing. 58 | */ 59 | typedef struct GameTextInputState { 60 | /** 61 | * Text owned by the state, as a modified UTF-8 string. Null-terminated. 62 | * https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8 63 | */ 64 | const char *text_UTF8; 65 | /** 66 | * Length in bytes of text_UTF8, *not* including the null at end. 67 | */ 68 | int32_t text_length; 69 | /** 70 | * A selection defined on the text. 71 | */ 72 | GameTextInputSpan selection; 73 | /** 74 | * A composing region defined on the text. 75 | */ 76 | GameTextInputSpan composingRegion; 77 | } GameTextInputState; 78 | 79 | /** 80 | * A callback called by GameTextInput_getState. 81 | * @param context User-defined context. 82 | * @param state State, owned by the library, that will be valid for the duration 83 | * of the callback. 84 | */ 85 | typedef void (*GameTextInputGetStateCallback)( 86 | void *context, const struct GameTextInputState *state); 87 | 88 | /** 89 | * Opaque handle to the GameTextInput API. 90 | */ 91 | typedef struct GameTextInput GameTextInput; 92 | 93 | /** 94 | * Initialize the GameTextInput library. 95 | * If called twice without GameTextInput_destroy being called, the same pointer 96 | * will be returned and a warning will be issued. 97 | * @param env A JNI env valid on the calling thread. 98 | * @param max_string_size The maximum length of a string that can be edited. If 99 | * zero, the maximum defaults to 65536 bytes. A buffer of this size is allocated 100 | * at initialization. 101 | * @return A handle to the library. 102 | */ 103 | GameTextInput *GameTextInput_init(JNIEnv *env, uint32_t max_string_size); 104 | 105 | /** 106 | * When using GameTextInput, you need to create a gametextinput.InputConnection 107 | * on the Java side and pass it using this function to the library, unless using 108 | * GameActivity in which case this will be done for you. See the GameActivity 109 | * source code or GameTextInput samples for examples of usage. 110 | * @param input A valid GameTextInput library handle. 111 | * @param inputConnection A gametextinput.InputConnection object. 112 | */ 113 | void GameTextInput_setInputConnection(GameTextInput *input, 114 | jobject inputConnection); 115 | 116 | /** 117 | * Unless using GameActivity, it is required to call this function from your 118 | * Java gametextinput.Listener.stateChanged method to convert eventState and 119 | * trigger any event callbacks. When using GameActivity, this does not need to 120 | * be called as event processing is handled by the Activity. 121 | * @param input A valid GameTextInput library handle. 122 | * @param eventState A Java gametextinput.State object. 123 | */ 124 | void GameTextInput_processEvent(GameTextInput *input, jobject eventState); 125 | 126 | /** 127 | * Free any resources owned by the GameTextInput library. 128 | * Any subsequent calls to the library will fail until GameTextInput_init is 129 | * called again. 130 | * @param input A valid GameTextInput library handle. 131 | */ 132 | void GameTextInput_destroy(GameTextInput *input); 133 | 134 | /** 135 | * Flags to be passed to GameTextInput_showIme. 136 | */ 137 | enum ShowImeFlags { 138 | SHOW_IME_UNDEFINED = 0, // Default value. 139 | SHOW_IMPLICIT = 140 | 1, // Indicates that the user has forced the input method open so it 141 | // should not be closed until they explicitly do so. 142 | SHOW_FORCED = 2 // Indicates that this is an implicit request to show the 143 | // input window, not as the result of a direct request by 144 | // the user. The window may not be shown in this case. 145 | }; 146 | 147 | /** 148 | * Show the IME. Calls InputMethodManager.showSoftInput(). 149 | * @param input A valid GameTextInput library handle. 150 | * @param flags Defined in ShowImeFlags above. For more information see: 151 | * https://developer.android.com/reference/android/view/inputmethod/InputMethodManager 152 | */ 153 | void GameTextInput_showIme(GameTextInput *input, uint32_t flags); 154 | 155 | /** 156 | * Flags to be passed to GameTextInput_hideIme. 157 | */ 158 | enum HideImeFlags { 159 | HIDE_IME_UNDEFINED = 0, // Default value. 160 | HIDE_IMPLICIT_ONLY = 161 | 1, // Indicates that the soft input window should only be hidden if it 162 | // was not explicitly shown by the user. 163 | HIDE_NOT_ALWAYS = 164 | 2, // Indicates that the soft input window should normally be hidden, 165 | // unless it was originally shown with SHOW_FORCED. 166 | }; 167 | 168 | /** 169 | * Show the IME. Calls InputMethodManager.hideSoftInputFromWindow(). 170 | * @param input A valid GameTextInput library handle. 171 | * @param flags Defined in HideImeFlags above. For more information see: 172 | * https://developer.android.com/reference/android/view/inputmethod/InputMethodManager 173 | */ 174 | void GameTextInput_hideIme(GameTextInput *input, uint32_t flags); 175 | 176 | /** 177 | * Call a callback with the current GameTextInput state, which may have been 178 | * modified by changes in the IME and calls to GameTextInput_setState. We use a 179 | * callback rather than returning the state in order to simplify ownership of 180 | * text_UTF8 strings. These strings are only valid during the calling of the 181 | * callback. 182 | * @param input A valid GameTextInput library handle. 183 | * @param callback A function that will be called with valid state. 184 | * @param context Context used by the callback. 185 | */ 186 | void GameTextInput_getState(GameTextInput *input, 187 | GameTextInputGetStateCallback callback, 188 | void *context); 189 | 190 | /** 191 | * Set the current GameTextInput state. This state is reflected to any active 192 | * IME. 193 | * @param input A valid GameTextInput library handle. 194 | * @param state The state to set. Ownership is maintained by the caller and must 195 | * remain valid for the duration of the call. 196 | */ 197 | void GameTextInput_setState(GameTextInput *input, 198 | const GameTextInputState *state); 199 | 200 | /** 201 | * Type of the callback needed by GameTextInput_setEventCallback that will be 202 | * called every time the IME state changes. 203 | * @param context User-defined context set in GameTextInput_setEventCallback. 204 | * @param current_state Current IME state, owned by the library and valid during 205 | * the callback. 206 | */ 207 | typedef void (*GameTextInputEventCallback)( 208 | void *context, const GameTextInputState *current_state); 209 | 210 | /** 211 | * Optionally set a callback to be called whenever the IME state changes. 212 | * Not necessary if you are using GameActivity, which handles these callbacks 213 | * for you. 214 | * @param input A valid GameTextInput library handle. 215 | * @param callback Called by the library when the IME state changes. 216 | * @param context Context passed as first argument to the callback. 217 | */ 218 | void GameTextInput_setEventCallback(GameTextInput *input, 219 | GameTextInputEventCallback callback, 220 | void *context); 221 | 222 | /** 223 | * Type of the callback needed by GameTextInput_setImeInsetsCallback that will 224 | * be called every time the IME window insets change. 225 | * @param context User-defined context set in 226 | * GameTextInput_setImeWIndowInsetsCallback. 227 | * @param current_insets Current IME insets, owned by the library and valid 228 | * during the callback. 229 | */ 230 | typedef void (*GameTextInputImeInsetsCallback)(void *context, 231 | const ARect *current_insets); 232 | 233 | /** 234 | * Optionally set a callback to be called whenever the IME insets change. 235 | * Not necessary if you are using GameActivity, which handles these callbacks 236 | * for you. 237 | * @param input A valid GameTextInput library handle. 238 | * @param callback Called by the library when the IME insets change. 239 | * @param context Context passed as first argument to the callback. 240 | */ 241 | void GameTextInput_setImeInsetsCallback(GameTextInput *input, 242 | GameTextInputImeInsetsCallback callback, 243 | void *context); 244 | 245 | /** 246 | * Get the current window insets for the IME. 247 | * @param input A valid GameTextInput library handle. 248 | * @param insets Filled with the current insets by this function. 249 | */ 250 | void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets); 251 | 252 | /** 253 | * Unless using GameActivity, it is required to call this function from your 254 | * Java gametextinput.Listener.onImeInsetsChanged method to 255 | * trigger any event callbacks. When using GameActivity, this does not need to 256 | * be called as insets processing is handled by the Activity. 257 | * @param input A valid GameTextInput library handle. 258 | * @param eventState A Java gametextinput.State object. 259 | */ 260 | void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets); 261 | 262 | /** 263 | * Convert a GameTextInputState struct to a Java gametextinput.State object. 264 | * Don't forget to delete the returned Java local ref when you're done. 265 | * @param input A valid GameTextInput library handle. 266 | * @param state Input state to convert. 267 | * @return A Java object of class gametextinput.State. The caller is required to 268 | * delete this local reference. 269 | */ 270 | jobject GameTextInputState_toJava(const GameTextInput *input, 271 | const GameTextInputState *state); 272 | 273 | /** 274 | * Convert from a Java gametextinput.State object into a C GameTextInputState 275 | * struct. 276 | * @param input A valid GameTextInput library handle. 277 | * @param state A Java gametextinput.State object. 278 | * @param callback A function called with the C struct, valid for the duration 279 | * of the call. 280 | * @param context Context passed to the callback. 281 | */ 282 | void GameTextInputState_fromJava(const GameTextInput *input, jobject state, 283 | GameTextInputGetStateCallback callback, 284 | void *context); 285 | 286 | #ifdef __cplusplus 287 | } 288 | #endif 289 | 290 | /** @} */ 291 | -------------------------------------------------------------------------------- /GameController/src/main/java/com/google/android/games/paddleboat/GameControllerListener.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Android Open Source Project 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.android.games.paddleboat; 16 | 17 | import android.hardware.Sensor; 18 | import android.hardware.SensorDirectChannel; 19 | import android.hardware.SensorEvent; 20 | import android.hardware.SensorManager; 21 | import android.hardware.lights.Light; 22 | import android.hardware.lights.LightState; 23 | import android.hardware.lights.LightsManager; 24 | import android.hardware.lights.LightsRequest; 25 | import android.os.Build; 26 | import android.util.Log; 27 | import android.view.InputDevice; 28 | 29 | 30 | public class GameControllerListener { 31 | 32 | private static final String TAG = "GameControllerListener"; 33 | private boolean reportMotionEvents; 34 | private int inputDeviceFlags; 35 | private int inputDeviceId; 36 | private final GameControllerManager gameControllerManager; 37 | private GameControllerAccelerometerListener accelerometerListener; 38 | private GameControllerGyroscopeListener gyroscopeListener; 39 | private InputDevice inputDevice; 40 | private final Object mLightLock = new Object(); 41 | @GuardedBy("mLightLock") 42 | private LightsManager lightsManager; 43 | private LightsManager.LightsSession lightsSession; 44 | private Sensor accelerometer; 45 | private Sensor gyroscope; 46 | private final Object mSensorLock = new Object(); 47 | @GuardedBy("mSensorLock") 48 | private SensorManager sensorManager; 49 | public GameControllerListener(GameControllerManager gcManager, InputDevice newDevice, 50 | int newFlags, boolean motionEvents) { 51 | gameControllerManager = gcManager; 52 | inputDevice = newDevice; 53 | inputDeviceFlags = newFlags; 54 | inputDeviceId = inputDevice.getId(); 55 | reportMotionEvents = motionEvents; 56 | lightsManager = null; 57 | lightsSession = null; 58 | accelerometer = null; 59 | accelerometerListener = null; 60 | gyroscope = null; 61 | gyroscopeListener = null; 62 | sensorManager = null; 63 | 64 | configureMotion(); 65 | } 66 | 67 | public void resetListener(InputDevice newDevice, int newFlags) { 68 | shutdownListener(); 69 | inputDevice = newDevice; 70 | inputDeviceFlags = newFlags; 71 | inputDeviceId = newDevice.getId(); 72 | configureMotion(); 73 | } 74 | 75 | public void shutdownListener() { 76 | // Called when the device sends a disconnected or changed event 77 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 78 | Log.d(TAG, "shutdownListener"); 79 | synchronized (mLightLock) { 80 | if (lightsSession != null) { 81 | lightsSession.close(); 82 | } 83 | lightsSession = null; 84 | lightsManager = null; 85 | } 86 | 87 | synchronized (mSensorLock) { 88 | if (sensorManager != null) { 89 | if (accelerometerListener != null) { 90 | sensorManager.unregisterListener(accelerometerListener); 91 | accelerometerListener = null; 92 | } 93 | if (gyroscopeListener != null) { 94 | sensorManager.unregisterListener((gyroscopeListener)); 95 | gyroscopeListener = null; 96 | } 97 | } 98 | accelerometer = null; 99 | gyroscope = null; 100 | sensorManager = null; 101 | } 102 | } 103 | inputDeviceFlags = 0; 104 | inputDevice = null; 105 | } 106 | 107 | public void setReportMotionEvents() { 108 | reportMotionEvents = true; 109 | configureMotion(); 110 | } 111 | 112 | public void setLight(int lightType, int lightValue) { 113 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 114 | synchronized (mLightLock) { 115 | // Avoid starting a light session until we actually make changes 116 | // to a light 117 | if (lightsManager == null) { 118 | configureLights(); 119 | } 120 | if (lightsManager != null) { 121 | for (Light currentLight : lightsManager.getLights()) { 122 | if (lightType == GameControllerManager.LIGHT_TYPE_PLAYER && 123 | currentLight.getType() == Light.LIGHT_TYPE_PLAYER_ID) { 124 | LightState.Builder stateBuilder = new LightState.Builder(); 125 | stateBuilder.setPlayerId(lightValue); 126 | LightsRequest.Builder requestBuilder = new LightsRequest.Builder(); 127 | requestBuilder.addLight(currentLight, stateBuilder.build()); 128 | lightsSession.requestLights(requestBuilder.build()); 129 | break; 130 | } else if (lightType == GameControllerManager.LIGHT_TYPE_RGB && 131 | currentLight.hasRgbControl()) { 132 | LightState.Builder stateBuilder = new LightState.Builder(); 133 | stateBuilder.setColor(lightValue); 134 | LightsRequest.Builder requestBuilder = new LightsRequest.Builder(); 135 | requestBuilder.addLight(currentLight, stateBuilder.build()); 136 | lightsSession.requestLights(requestBuilder.build()); 137 | break; 138 | } 139 | } 140 | } 141 | } 142 | } 143 | 144 | } 145 | 146 | private void configureLights() { 147 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 148 | if ((inputDeviceFlags & (GameControllerManager.DEVICEFLAG_LIGHT_PLAYER | 149 | GameControllerManager.DEVICEFLAG_LIGHT_RGB)) != 0) { 150 | synchronized (mLightLock) { 151 | Log.d(TAG, "configureLights"); 152 | lightsManager = inputDevice.getLightsManager(); 153 | lightsSession = lightsManager.openSession(); 154 | } 155 | } 156 | } 157 | } 158 | 159 | private void configureMotion() { 160 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && reportMotionEvents) { 161 | synchronized (mSensorLock) { 162 | sensorManager = inputDevice.getSensorManager(); 163 | if ((inputDeviceFlags & (GameControllerManager.DEVICEFLAG_ACCELEROMETER | 164 | GameControllerManager.DEVICEFLAG_GYROSCOPE)) != 0) { 165 | accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 166 | if (accelerometer != null) { 167 | if (gameControllerManager.getPrintControllerInfo()) { 168 | printSensorInformation(accelerometer, "accelerometer"); 169 | } 170 | accelerometerListener = 171 | new GameControllerAccelerometerListener(accelerometer); 172 | Log.d(TAG, "registering listener for accelerometer"); 173 | sensorManager.registerListener(accelerometerListener, accelerometer, 174 | SensorManager.SENSOR_DELAY_GAME); 175 | } 176 | gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); 177 | if (gyroscope != null) { 178 | if (gameControllerManager.getPrintControllerInfo()) { 179 | printSensorInformation(gyroscope, "gyroscope"); 180 | } 181 | gyroscopeListener = new GameControllerGyroscopeListener(gyroscope); 182 | Log.d(TAG, "registering listener for gyroscope"); 183 | sensorManager.registerListener(gyroscopeListener, gyroscope, 184 | SensorManager.SENSOR_DELAY_GAME); 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | private void printSensorInformation(Sensor sensor, String sensorName) { 192 | Log.d(TAG, "Registering listener for " + sensorName); 193 | Log.d(TAG, "Begin sensor information -----------------------------"); 194 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 195 | Log.d(TAG, "getFifoMaxEventCount: " + sensor.getFifoMaxEventCount()); 196 | Log.d(TAG, "getFifoReservedEventCount: " + sensor.getFifoReservedEventCount()); 197 | } 198 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 199 | Log.d(TAG, "getHighestDirectReportRateLevel: " + 200 | sensor.getHighestDirectReportRateLevel()); 201 | } 202 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 203 | Log.d(TAG, "getId: " + sensor.getId()); 204 | } 205 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 206 | Log.d(TAG, "getMaxDelay: " + sensor.getMaxDelay()); 207 | } 208 | Log.d(TAG, "getMaximumRange: " + sensor.getMaximumRange()); 209 | Log.d(TAG, "getMinDelay: " + sensor.getMinDelay()); 210 | Log.d(TAG, "getName: " + sensor.getName()); 211 | Log.d(TAG, "getPower: " + sensor.getPower()); 212 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 213 | Log.d(TAG, "getReportingMode: " + sensor.getReportingMode()); 214 | } 215 | Log.d(TAG, "getVendor: " + sensor.getVendor()); 216 | Log.d(TAG, "getVersion: " + sensor.getVersion()); 217 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 218 | Log.d(TAG, "isAdditionalInfoSupported: " + sensor.isAdditionalInfoSupported()); 219 | } 220 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 221 | boolean canMemoryFile = 222 | sensor.isDirectChannelTypeSupported(SensorDirectChannel.TYPE_MEMORY_FILE); 223 | boolean canHardwareBuffer = 224 | sensor.isDirectChannelTypeSupported(SensorDirectChannel.TYPE_HARDWARE_BUFFER); 225 | Log.d(TAG, "DirectChannel Memory File: " + canMemoryFile); 226 | Log.d(TAG, "DirectChannel Hardware Buffer: " + canHardwareBuffer); 227 | } 228 | Log.d(TAG, "End sensor information -------------------------------"); 229 | } 230 | 231 | class GameControllerAccelerometerListener implements android.hardware.SensorEventListener { 232 | private final Sensor listenerAccelerometer; 233 | 234 | GameControllerAccelerometerListener(Sensor newAccelerometer) { 235 | listenerAccelerometer = newAccelerometer; 236 | } 237 | 238 | @Override 239 | public void onSensorChanged(SensorEvent event) { 240 | if (listenerAccelerometer != null) { 241 | synchronized (listenerAccelerometer) { 242 | if (event.sensor == listenerAccelerometer) { 243 | gameControllerManager.onMotionData(inputDeviceId, 244 | GameControllerManager.MOTION_ACCELEROMETER, event.timestamp, 245 | event.values[0], event.values[1], event.values[2]); 246 | } 247 | } 248 | } 249 | } 250 | 251 | @Override 252 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 253 | } 254 | } 255 | 256 | class GameControllerGyroscopeListener implements android.hardware.SensorEventListener { 257 | private final Sensor listenerGyroscope; 258 | 259 | GameControllerGyroscopeListener(Sensor newGyroscope) { 260 | listenerGyroscope = newGyroscope; 261 | } 262 | 263 | @Override 264 | public void onSensorChanged(SensorEvent event) { 265 | if (listenerGyroscope != null) { 266 | synchronized (listenerGyroscope) { 267 | if (event.sensor == listenerGyroscope) { 268 | gameControllerManager.onMotionData(inputDeviceId, 269 | GameControllerManager.MOTION_GYROSCOPE, event.timestamp, 270 | event.values[0], event.values[1], event.values[2]); 271 | } 272 | } 273 | } 274 | } 275 | 276 | @Override 277 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 278 | 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /GameActivity/prefab-src/modules/game-activity/include/game-activity/GameActivityEvents.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * @addtogroup GameActivity Game Activity Events 19 | * The interface to use Game Activity Events. 20 | * @{ 21 | */ 22 | 23 | /** 24 | * @file GameActivityEvents.h 25 | */ 26 | #ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H 27 | #define ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #ifdef __cplusplus 36 | extern "C" { 37 | #endif 38 | 39 | /** 40 | * The maximum number of axes supported in an Android MotionEvent. 41 | * See https://developer.android.com/ndk/reference/group/input. 42 | */ 43 | #define GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48 44 | 45 | /** 46 | * \brief Describe information about a pointer, found in a 47 | * GameActivityMotionEvent. 48 | * 49 | * You can read values directly from this structure, or use helper functions 50 | * (`GameActivityPointerAxes_getX`, `GameActivityPointerAxes_getY` and 51 | * `GameActivityPointerAxes_getAxisValue`). 52 | * 53 | * The X axis and Y axis are enabled by default but any other axis that you want 54 | * to read **must** be enabled first, using 55 | * `GameActivityPointerAxes_enableAxis`. 56 | * 57 | * \see GameActivityMotionEvent 58 | */ 59 | typedef struct GameActivityPointerAxes { 60 | int32_t id; 61 | int32_t toolType; 62 | float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT]; 63 | float rawX; 64 | float rawY; 65 | } GameActivityPointerAxes; 66 | 67 | /** \brief Get the toolType of the pointer. */ 68 | inline int32_t GameActivityPointerAxes_getToolType( 69 | const GameActivityPointerAxes* pointerInfo) { 70 | return pointerInfo->toolType; 71 | } 72 | 73 | /** \brief Get the current X coordinate of the pointer. */ 74 | inline float GameActivityPointerAxes_getX( 75 | const GameActivityPointerAxes* pointerInfo) { 76 | return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X]; 77 | } 78 | 79 | /** \brief Get the current Y coordinate of the pointer. */ 80 | inline float GameActivityPointerAxes_getY( 81 | const GameActivityPointerAxes* pointerInfo) { 82 | return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y]; 83 | } 84 | 85 | /** 86 | * \brief Enable the specified axis, so that its value is reported in the 87 | * GameActivityPointerAxes structures stored in a motion event. 88 | * 89 | * You must enable any axis that you want to read, apart from 90 | * `AMOTION_EVENT_AXIS_X` and `AMOTION_EVENT_AXIS_Y` that are enabled by 91 | * default. 92 | * 93 | * If the axis index is out of range, nothing is done. 94 | */ 95 | void GameActivityPointerAxes_enableAxis(int32_t axis); 96 | 97 | /** 98 | * \brief Disable the specified axis. Its value won't be reported in the 99 | * GameActivityPointerAxes structures stored in a motion event anymore. 100 | * 101 | * Apart from X and Y, any axis that you want to read **must** be enabled first, 102 | * using `GameActivityPointerAxes_enableAxis`. 103 | * 104 | * If the axis index is out of range, nothing is done. 105 | */ 106 | void GameActivityPointerAxes_disableAxis(int32_t axis); 107 | 108 | /** 109 | * \brief Get the value of the requested axis. 110 | * 111 | * Apart from X and Y, any axis that you want to read **must** be enabled first, 112 | * using `GameActivityPointerAxes_enableAxis`. 113 | * 114 | * Find the valid enums for the axis (`AMOTION_EVENT_AXIS_X`, 115 | * `AMOTION_EVENT_AXIS_Y`, `AMOTION_EVENT_AXIS_PRESSURE`...) 116 | * in https://developer.android.com/ndk/reference/group/input. 117 | * 118 | * @param pointerInfo The structure containing information about the pointer, 119 | * obtained from GameActivityMotionEvent. 120 | * @param axis The axis to get the value from 121 | * @return The value of the axis, or 0 if the axis is invalid or was not 122 | * enabled. 123 | */ 124 | float GameActivityPointerAxes_getAxisValue( 125 | const GameActivityPointerAxes* pointerInfo, int32_t axis); 126 | 127 | inline float GameActivityPointerAxes_getPressure( 128 | const GameActivityPointerAxes* pointerInfo) { 129 | return GameActivityPointerAxes_getAxisValue(pointerInfo, 130 | AMOTION_EVENT_AXIS_PRESSURE); 131 | } 132 | 133 | inline float GameActivityPointerAxes_getSize( 134 | const GameActivityPointerAxes* pointerInfo) { 135 | return GameActivityPointerAxes_getAxisValue(pointerInfo, 136 | AMOTION_EVENT_AXIS_SIZE); 137 | } 138 | 139 | inline float GameActivityPointerAxes_getTouchMajor( 140 | const GameActivityPointerAxes* pointerInfo) { 141 | return GameActivityPointerAxes_getAxisValue(pointerInfo, 142 | AMOTION_EVENT_AXIS_TOUCH_MAJOR); 143 | } 144 | 145 | inline float GameActivityPointerAxes_getTouchMinor( 146 | const GameActivityPointerAxes* pointerInfo) { 147 | return GameActivityPointerAxes_getAxisValue(pointerInfo, 148 | AMOTION_EVENT_AXIS_TOUCH_MINOR); 149 | } 150 | 151 | inline float GameActivityPointerAxes_getToolMajor( 152 | const GameActivityPointerAxes* pointerInfo) { 153 | return GameActivityPointerAxes_getAxisValue(pointerInfo, 154 | AMOTION_EVENT_AXIS_TOOL_MAJOR); 155 | } 156 | 157 | inline float GameActivityPointerAxes_getToolMinor( 158 | const GameActivityPointerAxes* pointerInfo) { 159 | return GameActivityPointerAxes_getAxisValue(pointerInfo, 160 | AMOTION_EVENT_AXIS_TOOL_MINOR); 161 | } 162 | 163 | inline float GameActivityPointerAxes_getOrientation( 164 | const GameActivityPointerAxes* pointerInfo) { 165 | return GameActivityPointerAxes_getAxisValue(pointerInfo, 166 | AMOTION_EVENT_AXIS_ORIENTATION); 167 | } 168 | 169 | /** 170 | * The maximum number of pointers returned inside a motion event. 171 | */ 172 | #if (defined GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE) 173 | #define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT \ 174 | GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE 175 | #else 176 | #define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8 177 | #endif 178 | 179 | /** 180 | * \brief Describe a motion event that happened on the GameActivity SurfaceView. 181 | * 182 | * This is 1:1 mapping to the information contained in a Java `MotionEvent` 183 | * (see https://developer.android.com/reference/android/view/MotionEvent). 184 | */ 185 | typedef struct GameActivityMotionEvent { 186 | int32_t deviceId; 187 | int32_t source; 188 | int32_t action; 189 | 190 | int64_t eventTime; 191 | int64_t downTime; 192 | 193 | int32_t flags; 194 | int32_t metaState; 195 | 196 | int32_t actionButton; 197 | int32_t buttonState; 198 | int32_t classification; 199 | int32_t edgeFlags; 200 | 201 | uint32_t pointerCount; 202 | GameActivityPointerAxes 203 | pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT]; 204 | 205 | int historySize; 206 | long* historicalEventTimesMillis; 207 | long* historicalEventTimesNanos; 208 | float* historicalAxisValues; 209 | 210 | float precisionX; 211 | float precisionY; 212 | } GameActivityMotionEvent; 213 | 214 | float GameActivityMotionEvent_getHistoricalAxisValue( 215 | const GameActivityMotionEvent* event, int axis, int pointerIndex, 216 | int historyPos); 217 | 218 | inline int GameActivityMotionEvent_getHistorySize( 219 | const GameActivityMotionEvent* event) { 220 | return event->historySize; 221 | } 222 | 223 | inline float GameActivityMotionEvent_getHistoricalX( 224 | const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { 225 | return GameActivityMotionEvent_getHistoricalAxisValue( 226 | event, AMOTION_EVENT_AXIS_X, pointerIndex, historyPos); 227 | } 228 | 229 | inline float GameActivityMotionEvent_getHistoricalY( 230 | const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { 231 | return GameActivityMotionEvent_getHistoricalAxisValue( 232 | event, AMOTION_EVENT_AXIS_Y, pointerIndex, historyPos); 233 | } 234 | 235 | inline float GameActivityMotionEvent_getHistoricalPressure( 236 | const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { 237 | return GameActivityMotionEvent_getHistoricalAxisValue( 238 | event, AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historyPos); 239 | } 240 | 241 | inline float GameActivityMotionEvent_getHistoricalSize( 242 | const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { 243 | return GameActivityMotionEvent_getHistoricalAxisValue( 244 | event, AMOTION_EVENT_AXIS_SIZE, pointerIndex, historyPos); 245 | } 246 | 247 | inline float GameActivityMotionEvent_getHistoricalTouchMajor( 248 | const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { 249 | return GameActivityMotionEvent_getHistoricalAxisValue( 250 | event, AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historyPos); 251 | } 252 | 253 | inline float GameActivityMotionEvent_getHistoricalTouchMinor( 254 | const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { 255 | return GameActivityMotionEvent_getHistoricalAxisValue( 256 | event, AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historyPos); 257 | } 258 | 259 | inline float GameActivityMotionEvent_getHistoricalToolMajor( 260 | const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { 261 | return GameActivityMotionEvent_getHistoricalAxisValue( 262 | event, AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historyPos); 263 | } 264 | 265 | inline float GameActivityMotionEvent_getHistoricalToolMinor( 266 | const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { 267 | return GameActivityMotionEvent_getHistoricalAxisValue( 268 | event, AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historyPos); 269 | } 270 | 271 | inline float GameActivityMotionEvent_getHistoricalOrientation( 272 | const GameActivityMotionEvent* event, int pointerIndex, int historyPos) { 273 | return GameActivityMotionEvent_getHistoricalAxisValue( 274 | event, AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historyPos); 275 | } 276 | 277 | /** \brief Performs necessary initialization steps for GameActivityEvents.a 278 | * 279 | * User must call this function before calling any other functions of this unit. 280 | * If you use GameActivity it will call this function for you. 281 | */ 282 | void GameActivityEventsInit(JNIEnv* env); 283 | 284 | /** \brief Handle the freeing of the GameActivityMotionEvent struct. */ 285 | void GameActivityMotionEvent_destroy(GameActivityMotionEvent* c_event); 286 | 287 | /** 288 | * \brief Convert a Java `MotionEvent` to a `GameActivityMotionEvent`. 289 | * 290 | * This is done automatically by the GameActivity: see `onTouchEvent` to set 291 | * a callback to consume the received events. 292 | * This function can be used if you re-implement events handling in your own 293 | * activity. 294 | * Ownership of out_event is maintained by the caller. 295 | * Note that we pass as much information from Java Activity as possible 296 | * to avoid extra JNI calls. 297 | */ 298 | void GameActivityMotionEvent_fromJava( 299 | JNIEnv* env, jobject motionEvent, GameActivityMotionEvent* out_event, 300 | int pointerCount, int historySize, int deviceId, int source, int action, 301 | int64_t eventTime, int64_t downTime, int flags, int metaState, 302 | int actionButton, int buttonState, int classification, int edgeFlags, 303 | float precisionX, float precisionY); 304 | 305 | /** 306 | * \brief Describe a key event that happened on the GameActivity SurfaceView. 307 | * 308 | * This is 1:1 mapping to the information contained in a Java `KeyEvent` 309 | * (see https://developer.android.com/reference/android/view/KeyEvent). 310 | * The only exception is the event times, which are reported as 311 | * nanoseconds in this struct. 312 | */ 313 | typedef struct GameActivityKeyEvent { 314 | int32_t deviceId; 315 | int32_t source; 316 | int32_t action; 317 | 318 | int64_t eventTime; 319 | int64_t downTime; 320 | 321 | int32_t flags; 322 | int32_t metaState; 323 | 324 | int32_t modifiers; 325 | int32_t repeatCount; 326 | int32_t keyCode; 327 | int32_t scanCode; 328 | int32_t unicodeChar; 329 | } GameActivityKeyEvent; 330 | 331 | /** 332 | * \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`. 333 | * 334 | * This is done automatically by the GameActivity: see `onKeyUp` and `onKeyDown` 335 | * to set a callback to consume the received events. 336 | * This function can be used if you re-implement events handling in your own 337 | * activity. 338 | * Ownership of out_event is maintained by the caller. 339 | */ 340 | void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent, 341 | GameActivityKeyEvent* out_event); 342 | 343 | #ifdef __cplusplus 344 | } 345 | #endif 346 | 347 | /** @} */ 348 | 349 | #endif // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H 350 | -------------------------------------------------------------------------------- /GameActivity/prefab-src/modules/game-activity/include/game-activity/GameActivityEvents.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "GameActivityEvents.h" 18 | 19 | #include 20 | 21 | #include 22 | 23 | #include "GameActivityLog.h" 24 | #include "system_utils.h" 25 | 26 | static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = { 27 | /* AMOTION_EVENT_AXIS_X */ true, 28 | /* AMOTION_EVENT_AXIS_Y */ true, 29 | // Disable all other axes by default (they can be enabled using 30 | // `GameActivityPointerAxes_enableAxis`). 31 | false}; 32 | 33 | extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) { 34 | if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { 35 | return; 36 | } 37 | 38 | enabledAxes[axis] = true; 39 | } 40 | 41 | float GameActivityPointerAxes_getAxisValue( 42 | const GameActivityPointerAxes *pointerInfo, int32_t axis) { 43 | if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { 44 | return 0; 45 | } 46 | 47 | if (!enabledAxes[axis]) { 48 | ALOGW("Axis %d must be enabled before it can be accessed.", axis); 49 | return 0; 50 | } 51 | 52 | return pointerInfo->axisValues[axis]; 53 | } 54 | 55 | extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) { 56 | if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { 57 | return; 58 | } 59 | 60 | enabledAxes[axis] = false; 61 | } 62 | 63 | float GameActivityMotionEvent_getHistoricalAxisValue( 64 | const GameActivityMotionEvent *event, int axis, int pointerIndex, 65 | int historyPos) { 66 | if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) { 67 | return 0; 68 | } 69 | 70 | if (!enabledAxes[axis]) { 71 | ALOGW("Axis %d must be enabled before it can be accessed.", axis); 72 | return 0; 73 | } 74 | 75 | return event->historicalAxisValues[event->pointerCount * historyPos + axis]; 76 | } 77 | 78 | static struct { 79 | jmethodID getDeviceId; 80 | jmethodID getSource; 81 | jmethodID getAction; 82 | 83 | jmethodID getEventTime; 84 | jmethodID getDownTime; 85 | 86 | jmethodID getFlags; 87 | jmethodID getMetaState; 88 | 89 | jmethodID getActionButton; 90 | jmethodID getButtonState; 91 | jmethodID getClassification; 92 | jmethodID getEdgeFlags; 93 | 94 | jmethodID getHistorySize; 95 | jmethodID getHistoricalEventTime; 96 | 97 | jmethodID getPointerCount; 98 | jmethodID getPointerId; 99 | 100 | jmethodID getToolType; 101 | 102 | jmethodID getRawX; 103 | jmethodID getRawY; 104 | jmethodID getXPrecision; 105 | jmethodID getYPrecision; 106 | jmethodID getAxisValue; 107 | 108 | jmethodID getHistoricalAxisValue; 109 | } gMotionEventClassInfo; 110 | 111 | extern "C" void GameActivityMotionEvent_destroy( 112 | GameActivityMotionEvent *c_event) { 113 | delete c_event->historicalAxisValues; 114 | delete c_event->historicalEventTimesMillis; 115 | delete c_event->historicalEventTimesNanos; 116 | } 117 | 118 | static void initMotionEvents(JNIEnv *env) { 119 | int sdkVersion = gamesdk::GetSystemPropAsInt("ro.build.version.sdk"); 120 | gMotionEventClassInfo = {0}; 121 | jclass motionEventClass = env->FindClass("android/view/MotionEvent"); 122 | gMotionEventClassInfo.getDeviceId = 123 | env->GetMethodID(motionEventClass, "getDeviceId", "()I"); 124 | gMotionEventClassInfo.getSource = 125 | env->GetMethodID(motionEventClass, "getSource", "()I"); 126 | gMotionEventClassInfo.getAction = 127 | env->GetMethodID(motionEventClass, "getAction", "()I"); 128 | gMotionEventClassInfo.getEventTime = 129 | env->GetMethodID(motionEventClass, "getEventTime", "()J"); 130 | gMotionEventClassInfo.getDownTime = 131 | env->GetMethodID(motionEventClass, "getDownTime", "()J"); 132 | gMotionEventClassInfo.getFlags = 133 | env->GetMethodID(motionEventClass, "getFlags", "()I"); 134 | gMotionEventClassInfo.getMetaState = 135 | env->GetMethodID(motionEventClass, "getMetaState", "()I"); 136 | if (sdkVersion >= 23) { 137 | gMotionEventClassInfo.getActionButton = 138 | env->GetMethodID(motionEventClass, "getActionButton", "()I"); 139 | } 140 | if (sdkVersion >= 14) { 141 | gMotionEventClassInfo.getButtonState = 142 | env->GetMethodID(motionEventClass, "getButtonState", "()I"); 143 | } 144 | if (sdkVersion >= 29) { 145 | gMotionEventClassInfo.getClassification = 146 | env->GetMethodID(motionEventClass, "getClassification", "()I"); 147 | } 148 | gMotionEventClassInfo.getEdgeFlags = 149 | env->GetMethodID(motionEventClass, "getEdgeFlags", "()I"); 150 | 151 | gMotionEventClassInfo.getHistorySize = 152 | env->GetMethodID(motionEventClass, "getHistorySize", "()I"); 153 | gMotionEventClassInfo.getHistoricalEventTime = 154 | env->GetMethodID(motionEventClass, "getHistoricalEventTime", "(I)J"); 155 | 156 | gMotionEventClassInfo.getPointerCount = 157 | env->GetMethodID(motionEventClass, "getPointerCount", "()I"); 158 | gMotionEventClassInfo.getPointerId = 159 | env->GetMethodID(motionEventClass, "getPointerId", "(I)I"); 160 | gMotionEventClassInfo.getToolType = 161 | env->GetMethodID(motionEventClass, "getToolType", "(I)I"); 162 | if (sdkVersion >= 29) { 163 | gMotionEventClassInfo.getRawX = 164 | env->GetMethodID(motionEventClass, "getRawX", "(I)F"); 165 | gMotionEventClassInfo.getRawY = 166 | env->GetMethodID(motionEventClass, "getRawY", "(I)F"); 167 | } 168 | gMotionEventClassInfo.getXPrecision = 169 | env->GetMethodID(motionEventClass, "getXPrecision", "()F"); 170 | gMotionEventClassInfo.getYPrecision = 171 | env->GetMethodID(motionEventClass, "getYPrecision", "()F"); 172 | gMotionEventClassInfo.getAxisValue = 173 | env->GetMethodID(motionEventClass, "getAxisValue", "(II)F"); 174 | 175 | gMotionEventClassInfo.getHistoricalAxisValue = 176 | env->GetMethodID(motionEventClass, "getHistoricalAxisValue", "(III)F"); 177 | } 178 | 179 | extern "C" void GameActivityMotionEvent_fromJava( 180 | JNIEnv *env, jobject motionEvent, GameActivityMotionEvent *out_event, 181 | int pointerCount, int historySize, int deviceId, int source, int action, 182 | int64_t eventTime, int64_t downTime, int flags, int metaState, 183 | int actionButton, int buttonState, int classification, int edgeFlags, 184 | float precisionX, float precisionY) { 185 | pointerCount = 186 | std::min(pointerCount, GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT); 187 | out_event->pointerCount = pointerCount; 188 | for (int i = 0; i < pointerCount; ++i) { 189 | out_event->pointers[i] = { 190 | /*id=*/env->CallIntMethod(motionEvent, 191 | gMotionEventClassInfo.getPointerId, i), 192 | /*toolType=*/ 193 | env->CallIntMethod(motionEvent, gMotionEventClassInfo.getToolType, 194 | i), 195 | /*axisValues=*/{0}, 196 | /*rawX=*/gMotionEventClassInfo.getRawX 197 | ? env->CallFloatMethod(motionEvent, 198 | gMotionEventClassInfo.getRawX, i) 199 | : 0, 200 | /*rawY=*/gMotionEventClassInfo.getRawY 201 | ? env->CallFloatMethod(motionEvent, 202 | gMotionEventClassInfo.getRawY, i) 203 | : 0, 204 | }; 205 | 206 | for (int axisIndex = 0; 207 | axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; ++axisIndex) { 208 | if (enabledAxes[axisIndex]) { 209 | out_event->pointers[i].axisValues[axisIndex] = 210 | env->CallFloatMethod(motionEvent, 211 | gMotionEventClassInfo.getAxisValue, 212 | axisIndex, i); 213 | } 214 | } 215 | } 216 | 217 | out_event->historySize = historySize; 218 | out_event->historicalAxisValues = 219 | new float[historySize * pointerCount * 220 | GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT]; 221 | out_event->historicalEventTimesMillis = new long[historySize]; 222 | out_event->historicalEventTimesNanos = new long[historySize]; 223 | 224 | for (int historyIndex = 0; historyIndex < historySize; historyIndex++) { 225 | out_event->historicalEventTimesMillis[historyIndex] = 226 | env->CallLongMethod(motionEvent, 227 | gMotionEventClassInfo.getHistoricalEventTime, 228 | historyIndex); 229 | out_event->historicalEventTimesNanos[historyIndex] = 230 | out_event->historicalEventTimesMillis[historyIndex] * 1000000; 231 | for (int i = 0; i < pointerCount; ++i) { 232 | int pointerOffset = i * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; 233 | int historyAxisOffset = historyIndex * pointerCount * 234 | GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; 235 | float *axisValues = 236 | &out_event 237 | ->historicalAxisValues[historyAxisOffset + pointerOffset]; 238 | for (int axisIndex = 0; 239 | axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; 240 | ++axisIndex) { 241 | if (enabledAxes[axisIndex]) { 242 | axisValues[axisIndex] = env->CallFloatMethod( 243 | motionEvent, 244 | gMotionEventClassInfo.getHistoricalAxisValue, axisIndex, 245 | i, historyIndex); 246 | } 247 | } 248 | } 249 | } 250 | 251 | out_event->deviceId = deviceId; 252 | out_event->source = source; 253 | out_event->action = action; 254 | 255 | out_event->eventTime = eventTime; 256 | out_event->downTime = downTime; 257 | 258 | out_event->flags = flags; 259 | out_event->metaState = metaState; 260 | 261 | out_event->actionButton = actionButton; 262 | out_event->buttonState = buttonState; 263 | out_event->classification = classification; 264 | out_event->edgeFlags = edgeFlags; 265 | 266 | out_event->precisionX = precisionX; 267 | out_event->precisionY = precisionY; 268 | } 269 | 270 | static struct { 271 | jmethodID getDeviceId; 272 | jmethodID getSource; 273 | jmethodID getAction; 274 | 275 | jmethodID getEventTime; 276 | jmethodID getDownTime; 277 | 278 | jmethodID getFlags; 279 | jmethodID getMetaState; 280 | 281 | jmethodID getModifiers; 282 | jmethodID getRepeatCount; 283 | jmethodID getKeyCode; 284 | jmethodID getScanCode; 285 | jmethodID getUnicodeChar; 286 | } gKeyEventClassInfo; 287 | 288 | static void initKeyEvents(JNIEnv *env) { 289 | int sdkVersion = gamesdk::GetSystemPropAsInt("ro.build.version.sdk"); 290 | gKeyEventClassInfo = {0}; 291 | jclass keyEventClass = env->FindClass("android/view/KeyEvent"); 292 | gKeyEventClassInfo.getDeviceId = 293 | env->GetMethodID(keyEventClass, "getDeviceId", "()I"); 294 | gKeyEventClassInfo.getSource = 295 | env->GetMethodID(keyEventClass, "getSource", "()I"); 296 | gKeyEventClassInfo.getAction = 297 | env->GetMethodID(keyEventClass, "getAction", "()I"); 298 | gKeyEventClassInfo.getEventTime = 299 | env->GetMethodID(keyEventClass, "getEventTime", "()J"); 300 | gKeyEventClassInfo.getDownTime = 301 | env->GetMethodID(keyEventClass, "getDownTime", "()J"); 302 | gKeyEventClassInfo.getFlags = 303 | env->GetMethodID(keyEventClass, "getFlags", "()I"); 304 | gKeyEventClassInfo.getMetaState = 305 | env->GetMethodID(keyEventClass, "getMetaState", "()I"); 306 | if (sdkVersion >= 13) { 307 | gKeyEventClassInfo.getModifiers = 308 | env->GetMethodID(keyEventClass, "getModifiers", "()I"); 309 | } 310 | gKeyEventClassInfo.getRepeatCount = 311 | env->GetMethodID(keyEventClass, "getRepeatCount", "()I"); 312 | gKeyEventClassInfo.getKeyCode = 313 | env->GetMethodID(keyEventClass, "getKeyCode", "()I"); 314 | gKeyEventClassInfo.getScanCode = 315 | env->GetMethodID(keyEventClass, "getScanCode", "()I"); 316 | gKeyEventClassInfo.getUnicodeChar = 317 | env->GetMethodID(keyEventClass, "getUnicodeChar", "()I"); 318 | } 319 | 320 | extern "C" void GameActivityKeyEvent_fromJava(JNIEnv *env, jobject keyEvent, 321 | GameActivityKeyEvent *out_event) { 322 | *out_event = { 323 | /*deviceId=*/env->CallIntMethod(keyEvent, 324 | gKeyEventClassInfo.getDeviceId), 325 | /*source=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getSource), 326 | /*action=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getAction), 327 | // TODO: introduce a millisecondsToNanoseconds helper: 328 | /*eventTime=*/ 329 | env->CallLongMethod(keyEvent, gKeyEventClassInfo.getEventTime) * 330 | 1000000, 331 | /*downTime=*/ 332 | env->CallLongMethod(keyEvent, gKeyEventClassInfo.getDownTime) * 1000000, 333 | /*flags=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getFlags), 334 | /*metaState=*/ 335 | env->CallIntMethod(keyEvent, gKeyEventClassInfo.getMetaState), 336 | /*modifiers=*/gKeyEventClassInfo.getModifiers 337 | ? env->CallIntMethod(keyEvent, gKeyEventClassInfo.getModifiers) 338 | : 0, 339 | /*repeatCount=*/ 340 | env->CallIntMethod(keyEvent, gKeyEventClassInfo.getRepeatCount), 341 | /*keyCode=*/ 342 | env->CallIntMethod(keyEvent, gKeyEventClassInfo.getKeyCode), 343 | /*scanCode=*/ 344 | env->CallIntMethod(keyEvent, gKeyEventClassInfo.getScanCode), 345 | /*unicodeChar=*/ 346 | env->CallIntMethod(keyEvent, gKeyEventClassInfo.getUnicodeChar)}; 347 | } 348 | 349 | extern "C" void GameActivityEventsInit(JNIEnv *env) { 350 | initMotionEvents(env); 351 | initKeyEvents(env); 352 | } 353 | -------------------------------------------------------------------------------- /GameTextInput/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "game-text-input/gametextinput.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #define LOG_TAG "GameTextInput" 28 | 29 | static constexpr int32_t DEFAULT_MAX_STRING_SIZE = 1 << 16; 30 | 31 | // Cache of field ids in the Java GameTextInputState class 32 | struct StateClassInfo { 33 | jfieldID text; 34 | jfieldID selectionStart; 35 | jfieldID selectionEnd; 36 | jfieldID composingRegionStart; 37 | jfieldID composingRegionEnd; 38 | }; 39 | 40 | // Main GameTextInput object. 41 | struct GameTextInput { 42 | public: 43 | GameTextInput(JNIEnv *env, uint32_t max_string_size); 44 | ~GameTextInput(); 45 | void setState(const GameTextInputState &state); 46 | const GameTextInputState &getState() const { return currentState_; } 47 | void setInputConnection(jobject inputConnection); 48 | void processEvent(jobject textInputEvent); 49 | void showIme(uint32_t flags); 50 | void hideIme(uint32_t flags); 51 | void setEventCallback(GameTextInputEventCallback callback, void *context); 52 | jobject stateToJava(const GameTextInputState &state) const; 53 | void stateFromJava(jobject textInputEvent, 54 | GameTextInputGetStateCallback callback, 55 | void *context) const; 56 | void setImeInsetsCallback(GameTextInputImeInsetsCallback callback, 57 | void *context); 58 | void processImeInsets(const ARect *insets); 59 | const ARect &getImeInsets() const { return currentInsets_; } 60 | 61 | private: 62 | // Copy string and set other fields 63 | void setStateInner(const GameTextInputState &state); 64 | static void processCallback(void *context, const GameTextInputState *state); 65 | JNIEnv *env_ = nullptr; 66 | // Cached at initialization from 67 | // com/google/androidgamesdk/gametextinput/State. 68 | jclass stateJavaClass_ = nullptr; 69 | // The latest text input update. 70 | GameTextInputState currentState_ = {}; 71 | // An instance of gametextinput.InputConnection. 72 | jclass inputConnectionClass_ = nullptr; 73 | jobject inputConnection_ = nullptr; 74 | jmethodID inputConnectionSetStateMethod_; 75 | jmethodID setSoftKeyboardActiveMethod_; 76 | void (*eventCallback_)(void *context, 77 | const struct GameTextInputState *state) = nullptr; 78 | void *eventCallbackContext_ = nullptr; 79 | void (*insetsCallback_)(void *context, 80 | const struct ARect *insets) = nullptr; 81 | ARect currentInsets_ = {}; 82 | void *insetsCallbackContext_ = nullptr; 83 | StateClassInfo stateClassInfo_ = {}; 84 | // Constant-sized buffer used to store state text. 85 | std::vector stateStringBuffer_; 86 | }; 87 | 88 | std::unique_ptr s_gameTextInput; 89 | 90 | extern "C" { 91 | 92 | /////////////////////////////////////////////////////////// 93 | /// GameTextInputState C Functions 94 | /////////////////////////////////////////////////////////// 95 | 96 | // Convert to a Java structure. 97 | jobject currentState_toJava(const GameTextInput *gameTextInput, 98 | const GameTextInputState *state) { 99 | if (state == nullptr) return NULL; 100 | return gameTextInput->stateToJava(*state); 101 | } 102 | 103 | // Convert from Java structure. 104 | void currentState_fromJava(const GameTextInput *gameTextInput, 105 | jobject textInputEvent, 106 | GameTextInputGetStateCallback callback, 107 | void *context) { 108 | gameTextInput->stateFromJava(textInputEvent, callback, context); 109 | } 110 | 111 | /////////////////////////////////////////////////////////// 112 | /// GameTextInput C Functions 113 | /////////////////////////////////////////////////////////// 114 | 115 | struct GameTextInput *GameTextInput_init(JNIEnv *env, 116 | uint32_t max_string_size) { 117 | if (s_gameTextInput.get() != nullptr) { 118 | __android_log_print(ANDROID_LOG_WARN, LOG_TAG, 119 | "Warning: called GameTextInput_init twice without " 120 | "calling GameTextInput_destroy"); 121 | return s_gameTextInput.get(); 122 | } 123 | // Don't use make_unique, for C++11 compatibility 124 | s_gameTextInput = 125 | std::unique_ptr(new GameTextInput(env, max_string_size)); 126 | return s_gameTextInput.get(); 127 | } 128 | 129 | void GameTextInput_destroy(GameTextInput *input) { 130 | if (input == nullptr || s_gameTextInput.get() == nullptr) return; 131 | s_gameTextInput.reset(); 132 | } 133 | 134 | void GameTextInput_setState(GameTextInput *input, 135 | const GameTextInputState *state) { 136 | if (state == nullptr) return; 137 | input->setState(*state); 138 | } 139 | 140 | void GameTextInput_getState(GameTextInput *input, 141 | GameTextInputGetStateCallback callback, 142 | void *context) { 143 | callback(context, &input->getState()); 144 | } 145 | 146 | void GameTextInput_setInputConnection(GameTextInput *input, 147 | jobject inputConnection) { 148 | input->setInputConnection(inputConnection); 149 | } 150 | 151 | void GameTextInput_processEvent(GameTextInput *input, jobject textInputEvent) { 152 | input->processEvent(textInputEvent); 153 | } 154 | 155 | void GameTextInput_processImeInsets(GameTextInput *input, const ARect *insets) { 156 | input->processImeInsets(insets); 157 | } 158 | 159 | void GameTextInput_showIme(struct GameTextInput *input, uint32_t flags) { 160 | input->showIme(flags); 161 | } 162 | 163 | void GameTextInput_hideIme(struct GameTextInput *input, uint32_t flags) { 164 | input->hideIme(flags); 165 | } 166 | 167 | void GameTextInput_setEventCallback(struct GameTextInput *input, 168 | GameTextInputEventCallback callback, 169 | void *context) { 170 | input->setEventCallback(callback, context); 171 | } 172 | 173 | void GameTextInput_setImeInsetsCallback(struct GameTextInput *input, 174 | GameTextInputImeInsetsCallback callback, 175 | void *context) { 176 | input->setImeInsetsCallback(callback, context); 177 | } 178 | 179 | void GameTextInput_getImeInsets(const GameTextInput *input, ARect *insets) { 180 | *insets = input->getImeInsets(); 181 | } 182 | 183 | } // extern "C" 184 | 185 | /////////////////////////////////////////////////////////// 186 | /// GameTextInput C++ class Implementation 187 | /////////////////////////////////////////////////////////// 188 | 189 | GameTextInput::GameTextInput(JNIEnv *env, uint32_t max_string_size) 190 | : env_(env), 191 | stateStringBuffer_(max_string_size == 0 ? DEFAULT_MAX_STRING_SIZE 192 | : max_string_size) { 193 | stateJavaClass_ = (jclass)env_->NewGlobalRef( 194 | env_->FindClass("com/google/androidgamesdk/gametextinput/State")); 195 | inputConnectionClass_ = (jclass)env_->NewGlobalRef(env_->FindClass( 196 | "com/google/androidgamesdk/gametextinput/InputConnection")); 197 | inputConnectionSetStateMethod_ = 198 | env_->GetMethodID(inputConnectionClass_, "setState", 199 | "(Lcom/google/androidgamesdk/gametextinput/State;)V"); 200 | setSoftKeyboardActiveMethod_ = env_->GetMethodID( 201 | inputConnectionClass_, "setSoftKeyboardActive", "(ZI)V"); 202 | 203 | stateClassInfo_.text = 204 | env_->GetFieldID(stateJavaClass_, "text", "Ljava/lang/String;"); 205 | stateClassInfo_.selectionStart = 206 | env_->GetFieldID(stateJavaClass_, "selectionStart", "I"); 207 | stateClassInfo_.selectionEnd = 208 | env_->GetFieldID(stateJavaClass_, "selectionEnd", "I"); 209 | stateClassInfo_.composingRegionStart = 210 | env_->GetFieldID(stateJavaClass_, "composingRegionStart", "I"); 211 | stateClassInfo_.composingRegionEnd = 212 | env_->GetFieldID(stateJavaClass_, "composingRegionEnd", "I"); 213 | 214 | s_gameTextInput.get(); 215 | } 216 | 217 | GameTextInput::~GameTextInput() { 218 | if (stateJavaClass_ != NULL) { 219 | env_->DeleteGlobalRef(stateJavaClass_); 220 | stateJavaClass_ = NULL; 221 | } 222 | if (inputConnectionClass_ != NULL) { 223 | env_->DeleteGlobalRef(inputConnectionClass_); 224 | inputConnectionClass_ = NULL; 225 | } 226 | if (inputConnection_ != NULL) { 227 | env_->DeleteGlobalRef(inputConnection_); 228 | inputConnection_ = NULL; 229 | } 230 | } 231 | 232 | void GameTextInput::setState(const GameTextInputState &state) { 233 | if (inputConnection_ == nullptr) return; 234 | jobject jstate = stateToJava(state); 235 | env_->CallVoidMethod(inputConnection_, inputConnectionSetStateMethod_, 236 | jstate); 237 | env_->DeleteLocalRef(jstate); 238 | setStateInner(state); 239 | } 240 | 241 | void GameTextInput::setStateInner(const GameTextInputState &state) { 242 | // Check if we're setting using our own string (other parts may be 243 | // different) 244 | if (state.text_UTF8 == currentState_.text_UTF8) { 245 | currentState_ = state; 246 | return; 247 | } 248 | // Otherwise, copy across the string. 249 | auto bytes_needed = 250 | std::min(static_cast(state.text_length + 1), 251 | static_cast(stateStringBuffer_.size())); 252 | currentState_.text_UTF8 = stateStringBuffer_.data(); 253 | std::copy(state.text_UTF8, state.text_UTF8 + bytes_needed - 1, 254 | stateStringBuffer_.data()); 255 | currentState_.text_length = state.text_length; 256 | currentState_.selection = state.selection; 257 | currentState_.composingRegion = state.composingRegion; 258 | stateStringBuffer_[bytes_needed - 1] = 0; 259 | } 260 | 261 | void GameTextInput::setInputConnection(jobject inputConnection) { 262 | if (inputConnection_ != NULL) { 263 | env_->DeleteGlobalRef(inputConnection_); 264 | } 265 | inputConnection_ = env_->NewGlobalRef(inputConnection); 266 | } 267 | 268 | /*static*/ void GameTextInput::processCallback( 269 | void *context, const GameTextInputState *state) { 270 | auto thiz = static_cast(context); 271 | if (state != nullptr) thiz->setStateInner(*state); 272 | } 273 | 274 | void GameTextInput::processEvent(jobject textInputEvent) { 275 | stateFromJava(textInputEvent, processCallback, this); 276 | if (eventCallback_) { 277 | eventCallback_(eventCallbackContext_, ¤tState_); 278 | } 279 | } 280 | 281 | void GameTextInput::showIme(uint32_t flags) { 282 | if (inputConnection_ == nullptr) return; 283 | env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, true, 284 | flags); 285 | } 286 | 287 | void GameTextInput::setEventCallback(GameTextInputEventCallback callback, 288 | void *context) { 289 | eventCallback_ = callback; 290 | eventCallbackContext_ = context; 291 | } 292 | 293 | void GameTextInput::setImeInsetsCallback( 294 | GameTextInputImeInsetsCallback callback, void *context) { 295 | insetsCallback_ = callback; 296 | insetsCallbackContext_ = context; 297 | } 298 | 299 | void GameTextInput::processImeInsets(const ARect *insets) { 300 | currentInsets_ = *insets; 301 | if (insetsCallback_) { 302 | insetsCallback_(insetsCallbackContext_, ¤tInsets_); 303 | } 304 | } 305 | 306 | void GameTextInput::hideIme(uint32_t flags) { 307 | if (inputConnection_ == nullptr) return; 308 | env_->CallVoidMethod(inputConnection_, setSoftKeyboardActiveMethod_, false, 309 | flags); 310 | } 311 | 312 | jobject GameTextInput::stateToJava(const GameTextInputState &state) const { 313 | static jmethodID constructor = nullptr; 314 | if (constructor == nullptr) { 315 | constructor = env_->GetMethodID(stateJavaClass_, "", 316 | "(Ljava/lang/String;IIII)V"); 317 | if (constructor == nullptr) { 318 | __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, 319 | "Can't find gametextinput.State constructor"); 320 | return nullptr; 321 | } 322 | } 323 | const char *text = state.text_UTF8; 324 | if (text == nullptr) { 325 | static char empty_string[] = ""; 326 | text = empty_string; 327 | } 328 | // Note that this expects 'modified' UTF-8 which is not the same as UTF-8 329 | // https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8 330 | jstring jtext = env_->NewStringUTF(text); 331 | jobject jobj = 332 | env_->NewObject(stateJavaClass_, constructor, jtext, 333 | state.selection.start, state.selection.end, 334 | state.composingRegion.start, state.composingRegion.end); 335 | env_->DeleteLocalRef(jtext); 336 | return jobj; 337 | } 338 | 339 | void GameTextInput::stateFromJava(jobject textInputEvent, 340 | GameTextInputGetStateCallback callback, 341 | void *context) const { 342 | jstring text = 343 | (jstring)env_->GetObjectField(textInputEvent, stateClassInfo_.text); 344 | // Note this is 'modified' UTF-8, not true UTF-8. It has no NULLs in it, 345 | // except at the end. It's actually not specified whether the value returned 346 | // by GetStringUTFChars includes a null at the end, but it *seems to* on 347 | // Android. 348 | const char *text_chars = env_->GetStringUTFChars(text, NULL); 349 | int text_len = env_->GetStringUTFLength( 350 | text); // Length in bytes, *not* including the null. 351 | int selectionStart = 352 | env_->GetIntField(textInputEvent, stateClassInfo_.selectionStart); 353 | int selectionEnd = 354 | env_->GetIntField(textInputEvent, stateClassInfo_.selectionEnd); 355 | int composingRegionStart = 356 | env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionStart); 357 | int composingRegionEnd = 358 | env_->GetIntField(textInputEvent, stateClassInfo_.composingRegionEnd); 359 | GameTextInputState state{text_chars, 360 | text_len, 361 | {selectionStart, selectionEnd}, 362 | {composingRegionStart, composingRegionEnd}}; 363 | callback(context, &state); 364 | env_->ReleaseStringUTFChars(text, text_chars); 365 | env_->DeleteLocalRef(text); 366 | } 367 | -------------------------------------------------------------------------------- /GameActivity/src/main/java/com/google/androidgamesdk/GameActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.androidgamesdk; 17 | 18 | import static android.view.inputmethod.EditorInfo.IME_ACTION_GO; 19 | import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE; 20 | import static android.view.inputmethod.EditorInfo.IME_MASK_ACTION; 21 | import static android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION; 22 | 23 | import android.app.Activity; 24 | import android.content.pm.ActivityInfo; 25 | import android.content.pm.PackageManager; 26 | import android.content.res.AssetManager; 27 | import android.content.res.Configuration; 28 | import android.graphics.PixelFormat; 29 | import android.os.Build; 30 | import android.os.Bundle; 31 | import android.text.InputType; 32 | import android.util.Log; 33 | import android.view.KeyEvent; 34 | import android.view.MotionEvent; 35 | import android.view.Surface; 36 | import android.view.SurfaceHolder; 37 | import android.view.SurfaceView; 38 | import android.view.View; 39 | import android.view.WindowManager; 40 | import android.view.inputmethod.EditorInfo; 41 | import android.widget.FrameLayout; 42 | 43 | import dalvik.system.BaseDexClassLoader; 44 | import java.io.File; 45 | 46 | public class GameActivity 47 | extends Activity 48 | implements SurfaceHolder.Callback2 { 49 | private static final String LOG_TAG = "GameActivity"; 50 | 51 | /** 52 | * Optional meta-that can be in the manifest for this component, specifying 53 | * the name of the native shared library to load. If not specified, 54 | * "main" is used. 55 | */ 56 | public static final String META_DATA_LIB_NAME = "android.app.lib_name"; 57 | 58 | /** 59 | * Optional meta-that can be in the manifest for this component, specifying 60 | * the name of the main entry point for this native activity in the 61 | * {@link #META_DATA_LIB_NAME} native code. If not specified, 62 | * "GameActivity_onCreate" is used. 63 | */ 64 | public static final String META_DATA_FUNC_NAME = "android.app.func_name"; 65 | 66 | private static final String KEY_NATIVE_SAVED_STATE = "android:native_state"; 67 | 68 | protected int contentViewId; 69 | 70 | private EditorInfo imeEditorInfo; 71 | 72 | /** 73 | * The SurfaceView used by default for displaying the game and getting a text 74 | * input. 75 | * You can override its creation in `onCreateSurfaceView`. 76 | * This can be null, usually if you override `onCreateSurfaceView` to render on 77 | * the whole activity 78 | * window. 79 | */ 80 | protected SurfaceView mSurfaceView; 81 | 82 | protected boolean processMotionEvent(MotionEvent event) { 83 | int action = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ? event.getActionButton() : 0; 84 | int cls = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ? event.getClassification() : 0; 85 | 86 | return onTouchEventNative(mNativeHandle, event, event.getPointerCount(), 87 | event.getHistorySize(), event.getDeviceId(), event.getSource(), event.getAction(), 88 | event.getEventTime(), event.getDownTime(), event.getFlags(), event.getMetaState(), action, 89 | event.getButtonState(), cls, event.getEdgeFlags(), event.getXPrecision(), 90 | event.getYPrecision()); 91 | } 92 | 93 | @Override 94 | public boolean onTouchEvent(MotionEvent event) { 95 | if (processMotionEvent(event)) { 96 | return true; 97 | } else { 98 | return super.onTouchEvent(event); 99 | } 100 | } 101 | 102 | @Override 103 | public boolean onGenericMotionEvent(MotionEvent event) { 104 | if (processMotionEvent(event)) { 105 | return true; 106 | } else { 107 | return super.onGenericMotionEvent(event); 108 | } 109 | } 110 | 111 | @Override 112 | public boolean onKeyUp(final int keyCode, KeyEvent event) { 113 | if (onKeyUpNative(mNativeHandle, event)) { 114 | return true; 115 | } else { 116 | return super.onKeyUp(keyCode, event); 117 | } 118 | } 119 | 120 | @Override 121 | public boolean onKeyDown(final int keyCode, KeyEvent event) { 122 | if (onKeyDownNative(mNativeHandle, event)) { 123 | return true; 124 | } else { 125 | return super.onKeyDown(keyCode, event); 126 | } 127 | } 128 | 129 | private long mNativeHandle; 130 | 131 | private SurfaceHolder mCurSurfaceHolder; 132 | 133 | protected final int[] mLocation = new int[2]; 134 | 135 | protected boolean mDestroyed; 136 | 137 | protected native long loadNativeCode(String path, String funcname, String internalDataPath, 138 | String obbPath, String externalDataPath, AssetManager assetMgr, byte[] savedState); 139 | 140 | protected native String getDlError(); 141 | 142 | protected native void unloadNativeCode(long handle); 143 | 144 | protected native void onStartNative(long handle); 145 | 146 | protected native void onResumeNative(long handle); 147 | 148 | protected native byte[] onSaveInstanceStateNative(long handle); 149 | 150 | protected native void onPauseNative(long handle); 151 | 152 | protected native void onStopNative(long handle); 153 | 154 | protected native void onConfigurationChangedNative(long handle); 155 | 156 | protected native void onTrimMemoryNative(long handle, int level); 157 | 158 | protected native void onWindowFocusChangedNative(long handle, boolean focused); 159 | 160 | protected native void onSurfaceCreatedNative(long handle, Surface surface); 161 | 162 | protected native void onSurfaceChangedNative( 163 | long handle, Surface surface, int format, int width, int height); 164 | 165 | protected native void onSurfaceRedrawNeededNative(long handle, Surface surface); 166 | 167 | protected native void onSurfaceDestroyedNative(long handle); 168 | 169 | protected native boolean onTouchEventNative(long handle, MotionEvent motionEvent, 170 | int pointerCount, int historySize, int deviceId, int source, int action, long eventTime, 171 | long downTime, int flags, int metaState, int actionButton, int buttonState, 172 | int classification, int edgeFlags, float precisionX, float precisionY); 173 | 174 | protected native boolean onKeyDownNative(long handle, KeyEvent keyEvent); 175 | 176 | protected native boolean onKeyUpNative(long handle, KeyEvent keyEvent); 177 | 178 | protected native void onWindowInsetsChangedNative(long handle); 179 | 180 | /** 181 | * Get the pointer to the C `GameActivity` struct associated to this activity. 182 | * 183 | * @return the pointer to the C `GameActivity` struct associated to this 184 | * activity. 185 | */ 186 | public long getGameActivityNativeHandle() { 187 | return this.mNativeHandle; 188 | } 189 | 190 | /** 191 | * Called to create the SurfaceView when the game will be rendered. It should be 192 | * stored in 193 | * the mSurfaceView field, and its ID in contentViewId (if applicable). 194 | * 195 | * You can also redefine this to not create a SurfaceView at all, 196 | * and call `getWindow().takeSurface(this);` instead if you want to render on 197 | * the whole activity 198 | * window. 199 | */ 200 | protected void onCreateSurfaceView() { 201 | mSurfaceView = new SurfaceView(this); 202 | FrameLayout frameLayout = new FrameLayout(this); 203 | contentViewId = View.generateViewId(); 204 | frameLayout.setId(contentViewId); 205 | frameLayout.addView(mSurfaceView); 206 | 207 | setContentView(frameLayout); 208 | frameLayout.requestFocus(); 209 | 210 | mSurfaceView.getHolder().addCallback( 211 | this); // Register as a callback for the rendering of the surface, so that we can pass 212 | // this 213 | // surface to the native code 214 | 215 | } 216 | 217 | /** 218 | * Called to set up the window after the SurfaceView is created. Override this 219 | * if you want to 220 | * change the Format (default is `PixelFormat.RGB_565`) or the Soft Input Mode 221 | * (default is 222 | * `WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED | 223 | * WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE`). 224 | */ 225 | protected void onSetUpWindow() { 226 | getWindow().setFormat(PixelFormat.RGB_565); 227 | getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED 228 | | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); 229 | } 230 | 231 | @Override 232 | protected void onCreate(Bundle savedInstanceState) { 233 | onCreateSurfaceView(); 234 | onSetUpWindow(); 235 | 236 | String libname = "main"; 237 | if (null != getIntent().getStringExtra(META_DATA_LIB_NAME)) { 238 | libname = getIntent().getStringExtra(META_DATA_LIB_NAME); 239 | } 240 | String funcname = "GameActivity_onCreate"; 241 | ActivityInfo ai; 242 | try { 243 | ai = getPackageManager().getActivityInfo( 244 | getIntent().getComponent(), PackageManager.GET_META_DATA); 245 | if (ai.metaData != null) { 246 | String ln = ai.metaData.getString(META_DATA_LIB_NAME); 247 | if (ln != null) 248 | libname = ln; 249 | ln = ai.metaData.getString(META_DATA_FUNC_NAME); 250 | if (ln != null) 251 | funcname = ln; 252 | } 253 | } catch (PackageManager.NameNotFoundException e) { 254 | throw new RuntimeException("Error getting activity info", e); 255 | } 256 | 257 | BaseDexClassLoader classLoader = (BaseDexClassLoader) getClassLoader(); 258 | String path = classLoader.findLibrary(libname); 259 | 260 | if (path == null) { 261 | throw new IllegalArgumentException("Unable to find native library " + libname 262 | + " using classloader: " + classLoader.toString()); 263 | } 264 | 265 | byte[] nativeSavedState = savedInstanceState != null ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) 266 | : null; 267 | 268 | mNativeHandle = loadNativeCode(path, funcname, getAbsolutePath(getFilesDir()), 269 | getAbsolutePath(getObbDir()), getAbsolutePath(getExternalFilesDir(null)), 270 | getAssets(), nativeSavedState); 271 | 272 | if (mNativeHandle == 0) { 273 | throw new UnsatisfiedLinkError( 274 | "Unable to load native library \"" + path + "\": " + getDlError()); 275 | } 276 | 277 | super.onCreate(savedInstanceState); 278 | } 279 | 280 | private static String getAbsolutePath(File file) { 281 | return (file != null) ? file.getAbsolutePath() : null; 282 | } 283 | 284 | @Override 285 | protected void onDestroy() { 286 | mDestroyed = true; 287 | if (mCurSurfaceHolder != null) { 288 | onSurfaceDestroyedNative(mNativeHandle); 289 | mCurSurfaceHolder = null; 290 | } 291 | 292 | unloadNativeCode(mNativeHandle); 293 | super.onDestroy(); 294 | } 295 | 296 | @Override 297 | protected void onPause() { 298 | super.onPause(); 299 | onPauseNative(mNativeHandle); 300 | } 301 | 302 | @Override 303 | protected void onResume() { 304 | super.onResume(); 305 | onResumeNative(mNativeHandle); 306 | } 307 | 308 | @Override 309 | protected void onSaveInstanceState(Bundle outState) { 310 | super.onSaveInstanceState(outState); 311 | byte[] state = onSaveInstanceStateNative(mNativeHandle); 312 | if (state != null) { 313 | outState.putByteArray(KEY_NATIVE_SAVED_STATE, state); 314 | } 315 | } 316 | 317 | @Override 318 | protected void onStart() { 319 | super.onStart(); 320 | onStartNative(mNativeHandle); 321 | } 322 | 323 | @Override 324 | protected void onStop() { 325 | super.onStop(); 326 | onStopNative(mNativeHandle); 327 | } 328 | 329 | @Override 330 | public void onConfigurationChanged(Configuration newConfig) { 331 | super.onConfigurationChanged(newConfig); 332 | if (!mDestroyed) { 333 | onConfigurationChangedNative(mNativeHandle); 334 | } 335 | } 336 | 337 | @Override 338 | public void onTrimMemory(int level) { 339 | super.onTrimMemory(level); 340 | if (!mDestroyed) { 341 | onTrimMemoryNative(mNativeHandle, level); 342 | } 343 | } 344 | 345 | @Override 346 | public void onWindowFocusChanged(boolean hasFocus) { 347 | super.onWindowFocusChanged(hasFocus); 348 | if (!mDestroyed) { 349 | onWindowFocusChangedNative(mNativeHandle, hasFocus); 350 | } 351 | } 352 | 353 | public void surfaceCreated(SurfaceHolder holder) { 354 | if (!mDestroyed) { 355 | mCurSurfaceHolder = holder; 356 | onSurfaceCreatedNative(mNativeHandle, holder.getSurface()); 357 | } 358 | } 359 | 360 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 361 | if (!mDestroyed) { 362 | mCurSurfaceHolder = holder; 363 | onSurfaceChangedNative(mNativeHandle, holder.getSurface(), format, width, height); 364 | } 365 | } 366 | 367 | public void surfaceRedrawNeeded(SurfaceHolder holder) { 368 | if (!mDestroyed) { 369 | mCurSurfaceHolder = holder; 370 | onSurfaceRedrawNeededNative(mNativeHandle, holder.getSurface()); 371 | } 372 | } 373 | 374 | public void surfaceDestroyed(SurfaceHolder holder) { 375 | mCurSurfaceHolder = null; 376 | if (!mDestroyed) { 377 | onSurfaceDestroyedNative(mNativeHandle); 378 | } 379 | } 380 | 381 | void setWindowFlags(int flags, int mask) { 382 | getWindow().setFlags(flags, mask); 383 | } 384 | 385 | void setWindowFormat(int format) { 386 | getWindow().setFormat(format); 387 | } 388 | 389 | /** 390 | * Get the EditorInfo structure used to initialize the IME when it is requested. 391 | * The default is to forward key requests to the app (InputType.TYPE_NULL) and 392 | * to 393 | * have no action button (IME_ACTION_NONE). 394 | * See 395 | * https://developer.android.com/reference/android/view/inputmethod/EditorInfo. 396 | */ 397 | public EditorInfo getImeEditorInfo() { 398 | if (imeEditorInfo == null) { 399 | imeEditorInfo = new EditorInfo(); 400 | imeEditorInfo.inputType = InputType.TYPE_NULL; 401 | imeEditorInfo.actionId = IME_ACTION_NONE; 402 | imeEditorInfo.imeOptions = IME_FLAG_NO_ENTER_ACTION; 403 | } 404 | return imeEditorInfo; 405 | } 406 | 407 | /** 408 | * Set the EditorInfo structure used to initialize the IME when it is requested. 409 | * Set the inputType and actionId in order to customize how the IME behaves. 410 | * See 411 | * https://developer.android.com/reference/android/view/inputmethod/EditorInfo. 412 | */ 413 | public void setImeEditorInfo(EditorInfo info) { 414 | imeEditorInfo = info; 415 | } 416 | 417 | /** 418 | * Set the inpuType and actionId fields of the EditorInfo structure used to 419 | * initialize the IME when it is requested. 420 | * This is called from the native side by GameActivity_setImeEditorInfo. 421 | * See 422 | * https://developer.android.com/reference/android/view/inputmethod/EditorInfo. 423 | */ 424 | public void setImeEditorInfoFields(int inputType, int actionId, int imeOptions) { 425 | EditorInfo info = getImeEditorInfo(); 426 | info.inputType = inputType; 427 | info.actionId = actionId; 428 | info.imeOptions = imeOptions; 429 | } 430 | } 431 | --------------------------------------------------------------------------------