summaryrefslogtreecommitdiffstats
path: root/platform/android/java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/java')
-rw-r--r--platform/android/java/app/build.gradle37
-rw-r--r--platform/android/java/app/config.gradle14
-rw-r--r--platform/android/java/app/res/drawable-nodpi/splash.pngbin14766 -> 0 bytes
-rw-r--r--platform/android/java/app/res/drawable-nodpi/splash_bg_color.pngbin1360 -> 0 bytes
-rw-r--r--platform/android/java/app/res/drawable/splash_drawable.xml12
-rw-r--r--platform/android/java/app/res/values/themes.xml15
-rw-r--r--platform/android/java/app/src/com/godot/game/GodotApp.java4
-rw-r--r--platform/android/java/build.gradle5
-rw-r--r--platform/android/java/editor/build.gradle10
-rw-r--r--platform/android/java/editor/src/debug/res/values/strings.xml4
-rw-r--r--platform/android/java/editor/src/dev/res/values/strings.xml4
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml61
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt68
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt153
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt3
-rw-r--r--platform/android/java/editor/src/main/res/layout/godot_editor_layout.xml25
-rw-r--r--platform/android/java/editor/src/main/res/values/strings.xml2
-rw-r--r--platform/android/java/editor/src/main/res/values/themes.xml6
-rw-r--r--platform/android/java/lib/build.gradle5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt257
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt37
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java1
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt92
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java183
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt17
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt39
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt (renamed from platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt)30
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java22
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt83
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java10
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt6
-rw-r--r--platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt104
-rw-r--r--platform/android/java/nativeSrcsConfigs/CMakeLists.txt4
-rw-r--r--platform/android/java/scripts/publish-root.gradle1
35 files changed, 839 insertions, 482 deletions
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 7797f4bc9d..01d5d9ef92 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -32,6 +32,7 @@ configurations {
dependencies {
implementation "androidx.fragment:fragment:$versions.fragmentVersion"
+ implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
if (rootProject.findProject(":lib")) {
implementation project(":lib")
@@ -124,6 +125,12 @@ android {
// - https://stackoverflow.com/a/44704840
useLegacyPackaging shouldUseLegacyPackaging()
}
+
+ // Always select Godot's version of libc++_shared.so in case deps have their own
+ pickFirst 'lib/x86/libc++_shared.so'
+ pickFirst 'lib/x86_64/libc++_shared.so'
+ pickFirst 'lib/armeabi-v7a/libc++_shared.so'
+ pickFirst 'lib/arm64-v8a/libc++_shared.so'
}
signingConfigs {
@@ -205,36 +212,66 @@ android {
}
task copyAndRenameDebugApk(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/apk/debug/android_debug.apk"
into getExportPath()
rename "android_debug.apk", getExportFilename()
}
task copyAndRenameDevApk(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/apk/dev/android_dev.apk"
into getExportPath()
rename "android_dev.apk", getExportFilename()
}
task copyAndRenameReleaseApk(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/apk/release/android_release.apk"
into getExportPath()
rename "android_release.apk", getExportFilename()
}
task copyAndRenameDebugAab(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/bundle/debug/build-debug.aab"
into getExportPath()
rename "build-debug.aab", getExportFilename()
}
task copyAndRenameDevAab(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/bundle/dev/build-dev.aab"
into getExportPath()
rename "build-dev.aab", getExportFilename()
}
task copyAndRenameReleaseAab(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/bundle/release/build-release.aab"
into getExportPath()
rename "build-release.aab", getExportFilename()
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index f2c4a5d1b6..01759a1b2f 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -11,7 +11,8 @@ ext.versions = [
nexusPublishVersion: '1.3.0',
javaVersion : JavaVersion.VERSION_17,
// Also update 'platform/android/detect.py#get_ndk_version()' when this is updated.
- ndkVersion : '23.2.8568313'
+ ndkVersion : '23.2.8568313',
+ splashscreenVersion: '1.0.1'
]
@@ -194,17 +195,17 @@ final String VALUE_SEPARATOR_REGEX = "\\|"
// get the list of ABIs the project should be exported to
ext.getExportEnabledABIs = { ->
- String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : "";
+ String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : ""
if (enabledABIs == null || enabledABIs.isEmpty()) {
enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|"
}
- Set<String> exportAbiFilter = [];
+ Set<String> exportAbiFilter = []
for (String abi_name : enabledABIs.split(VALUE_SEPARATOR_REGEX)) {
if (!abi_name.trim().isEmpty()){
- exportAbiFilter.add(abi_name);
+ exportAbiFilter.add(abi_name)
}
}
- return exportAbiFilter;
+ return exportAbiFilter
}
ext.getExportPath = {
@@ -330,8 +331,7 @@ ext.getReleaseKeyAlias = { ->
}
ext.isAndroidStudio = { ->
- def sysProps = System.getProperties()
- return sysProps != null && sysProps['idea.platform.prefix'] != null
+ return project.hasProperty('android.injected.invoked.from.ide')
}
ext.shouldZipAlign = { ->
diff --git a/platform/android/java/app/res/drawable-nodpi/splash.png b/platform/android/java/app/res/drawable-nodpi/splash.png
deleted file mode 100644
index 7bddd4325a..0000000000
--- a/platform/android/java/app/res/drawable-nodpi/splash.png
+++ /dev/null
Binary files differ
diff --git a/platform/android/java/app/res/drawable-nodpi/splash_bg_color.png b/platform/android/java/app/res/drawable-nodpi/splash_bg_color.png
deleted file mode 100644
index 004b6fd508..0000000000
--- a/platform/android/java/app/res/drawable-nodpi/splash_bg_color.png
+++ /dev/null
Binary files differ
diff --git a/platform/android/java/app/res/drawable/splash_drawable.xml b/platform/android/java/app/res/drawable/splash_drawable.xml
deleted file mode 100644
index 30627b998c..0000000000
--- a/platform/android/java/app/res/drawable/splash_drawable.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:drawable="@drawable/splash_bg_color" />
-
- <item>
- <bitmap
- android:gravity="center"
- android:filter="false"
- android:src="@drawable/splash" />
- </item>
-</layer-list>
diff --git a/platform/android/java/app/res/values/themes.xml b/platform/android/java/app/res/values/themes.xml
index d64b50ca45..3ab8401928 100644
--- a/platform/android/java/app/res/values/themes.xml
+++ b/platform/android/java/app/res/values/themes.xml
@@ -3,8 +3,17 @@
<style name="GodotAppMainTheme" parent="@android:style/Theme.Black.NoTitleBar"/>
- <style name="GodotAppSplashTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen">
- <item name="android:windowBackground">@drawable/splash_drawable</item>
- <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ <style name="GodotAppSplashTheme" parent="Theme.SplashScreen">
+ <!-- Set the splash screen background, animated icon, and animation
+ duration. -->
+ <item name="android:windowSplashScreenBackground">@mipmap/icon_background</item>
+
+ <!-- Use windowSplashScreenAnimatedIcon to add a drawable or an animated
+ drawable. One of these is required. -->
+ <item name="windowSplashScreenAnimatedIcon">@mipmap/icon_foreground</item>
+
+ <!-- Set the theme of the Activity that directly follows your splash
+ screen. This is required. -->
+ <item name="postSplashScreenTheme">@style/GodotAppMainTheme</item>
</style>
</resources>
diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java
index 9142d767b4..22e617f6e7 100644
--- a/platform/android/java/app/src/com/godot/game/GodotApp.java
+++ b/platform/android/java/app/src/com/godot/game/GodotApp.java
@@ -34,6 +34,8 @@ import org.godotengine.godot.GodotActivity;
import android.os.Bundle;
+import androidx.core.splashscreen.SplashScreen;
+
/**
* Template activity for Godot Android builds.
* Feel free to extend and modify this class for your custom logic.
@@ -41,7 +43,7 @@ import android.os.Bundle;
public class GodotApp extends GodotActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
- setTheme(R.style.GodotAppMainTheme);
+ SplashScreen.installSplashScreen(this);
super.onCreate(savedInstanceState);
}
}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index c609b33ef4..b91b023ce6 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -232,11 +232,6 @@ def generateBuildTasks(String flavor = "template") {
return tasks
}
-def isAndroidStudio() {
- def sysProps = System.getProperties()
- return sysProps != null && sysProps['idea.platform.prefix'] != null
-}
-
task copyEditorReleaseApkToBin(type: Copy) {
dependsOn ':editor:assembleRelease'
from('editor/build/outputs/apk/release')
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index 0f7ffeecae..55fe2a22fe 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -10,6 +10,8 @@ dependencies {
implementation project(":lib")
implementation "androidx.window:window:1.2.0"
+ implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
+ implementation "androidx.constraintlayout:constraintlayout:2.1.4"
}
ext {
@@ -20,7 +22,7 @@ ext {
String versionStatus = System.getenv("GODOT_VERSION_STATUS")
if (versionStatus != null && !versionStatus.isEmpty()) {
try {
- buildNumber = Integer.parseInt(versionStatus.replaceAll("[^0-9]", ""));
+ buildNumber = Integer.parseInt(versionStatus.replaceAll("[^0-9]", ""))
} catch (NumberFormatException ignored) {
buildNumber = 0
}
@@ -92,6 +94,10 @@ android {
targetSdkVersion versions.targetSdk
missingDimensionStrategy 'products', 'editor'
+ manifestPlaceholders += [
+ editorAppName: "Godot Editor 4",
+ editorBuildSuffix: ""
+ ]
}
base {
@@ -124,11 +130,13 @@ android {
dev {
initWith debug
applicationIdSuffix ".dev"
+ manifestPlaceholders += [editorBuildSuffix: " (dev)"]
}
debug {
initWith release
applicationIdSuffix ".debug"
+ manifestPlaceholders += [editorBuildSuffix: " (debug)"]
signingConfig signingConfigs.debug
}
diff --git a/platform/android/java/editor/src/debug/res/values/strings.xml b/platform/android/java/editor/src/debug/res/values/strings.xml
deleted file mode 100644
index 09ee2d77e1..0000000000
--- a/platform/android/java/editor/src/debug/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="godot_editor_name_string">Godot Editor 4 (debug)</string>
-</resources>
diff --git a/platform/android/java/editor/src/dev/res/values/strings.xml b/platform/android/java/editor/src/dev/res/values/strings.xml
deleted file mode 100644
index 215f2c7d0a..0000000000
--- a/platform/android/java/editor/src/dev/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="godot_editor_name_string">Godot Editor 4 (dev)</string>
-</resources>
diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml
index 78dcddac0e..c7d14a3f49 100644
--- a/platform/android/java/editor/src/main/AndroidManifest.xml
+++ b/platform/android/java/editor/src/main/AndroidManifest.xml
@@ -13,12 +13,15 @@
android:glEsVersion="0x00030000"
android:required="true" />
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
+ <uses-permission
+ android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
- android:maxSdkVersion="29"/>
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
- android:maxSdkVersion="29"/>
+ <uses-permission
+ android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ android:maxSdkVersion="29" />
+ <uses-permission
+ android:name="android.permission.READ_EXTERNAL_STORAGE"
+ android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.VIBRATE" />
@@ -26,52 +29,44 @@
<application
android:allowBackup="false"
android:icon="@mipmap/icon"
- android:label="@string/godot_editor_name_string"
- tools:ignore="GoogleAppIndexingWarning"
- android:theme="@style/GodotEditorTheme"
- android:requestLegacyExternalStorage="true">
+ android:label="${editorAppName}${editorBuildSuffix}"
+ android:requestLegacyExternalStorage="true"
+ android:theme="@style/GodotEditorSplashScreenTheme"
+ tools:ignore="GoogleAppIndexingWarning">
+ <profileable
+ android:shell="true"
+ android:enabled="true"
+ tools:targetApi="29" />
<activity
- android:name=".GodotProjectManager"
+ android:name=".GodotEditor"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
- android:launchMode="singleTask"
- android:screenOrientation="userLandscape"
android:exported="true"
- android:process=":GodotProjectManager">
-
- <layout android:defaultHeight="@dimen/editor_default_window_height"
- android:defaultWidth="@dimen/editor_default_window_width" />
+ android:launchMode="singleTask"
+ android:screenOrientation="userLandscape">
+ <layout
+ android:defaultWidth="@dimen/editor_default_window_width"
+ android:defaultHeight="@dimen/editor_default_window_height" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
+
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
-
- <activity
- android:name=".GodotEditor"
- android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
- android:process=":GodotEditor"
- android:launchMode="singleTask"
- android:screenOrientation="userLandscape"
- android:exported="false">
- <layout android:defaultHeight="@dimen/editor_default_window_height"
- android:defaultWidth="@dimen/editor_default_window_width" />
- </activity>
-
<activity
android:name=".GodotGame"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
+ android:exported="false"
android:label="@string/godot_project_name_string"
- android:process=":GodotGame"
android:launchMode="singleTask"
- android:exported="false"
+ android:process=":GodotGame"
android:screenOrientation="userLandscape">
- <layout android:defaultHeight="@dimen/editor_default_window_height"
- android:defaultWidth="@dimen/editor_default_window_width" />
+ <layout
+ android:defaultWidth="@dimen/editor_default_window_width"
+ android:defaultHeight="@dimen/editor_default_window_height" />
</activity>
-
</application>
</manifest>
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt
new file mode 100644
index 0000000000..0da1d01aed
--- /dev/null
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt
@@ -0,0 +1,68 @@
+/**************************************************************************/
+/* EditorWindowInfo.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.editor
+
+/**
+ * Specifies the policy for adjacent launches.
+ */
+enum class LaunchAdjacentPolicy {
+ /**
+ * Adjacent launches are disabled.
+ */
+ DISABLED,
+
+ /**
+ * Adjacent launches are enabled / disabled based on the device and screen metrics.
+ */
+ AUTO,
+
+ /**
+ * Adjacent launches are enabled.
+ */
+ ENABLED
+}
+
+/**
+ * Describe the editor window to launch
+ */
+data class EditorWindowInfo(
+ val windowClassName: String,
+ val windowId: Int,
+ val processNameSuffix: String,
+ val launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED
+) {
+ constructor(
+ windowClass: Class<*>,
+ windowId: Int,
+ processNameSuffix: String,
+ launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED
+ ) : this(windowClass.name, windowId, processNameSuffix, launchAdjacentPolicy)
+}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index caf64bc933..5515347bd6 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -32,12 +32,16 @@ package org.godotengine.editor
import android.Manifest
import android.app.ActivityManager
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.*
import android.util.Log
+import android.view.View
import android.widget.Toast
+import androidx.annotation.CallSuper
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.window.layout.WindowMetricsCalculator
import org.godotengine.godot.GodotActivity
import org.godotengine.godot.GodotLib
@@ -64,18 +68,15 @@ open class GodotEditor : GodotActivity() {
private const val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"
- private const val EDITOR_ID = 777
+ // Command line arguments
private const val EDITOR_ARG = "--editor"
private const val EDITOR_ARG_SHORT = "-e"
- private const val EDITOR_PROCESS_NAME_SUFFIX = ":GodotEditor"
+ private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
+ private const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p"
- private const val GAME_ID = 667
- private const val GAME_PROCESS_NAME_SUFFIX = ":GodotGame"
-
- private const val PROJECT_MANAGER_ID = 555
- private const val PROJECT_MANAGER_ARG = "--project-manager"
- private const val PROJECT_MANAGER_ARG_SHORT = "-p"
- private const val PROJECT_MANAGER_PROCESS_NAME_SUFFIX = ":GodotProjectManager"
+ // Info for the various classes used by the editor
+ internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "")
+ internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchAdjacentPolicy.AUTO)
/**
* Sets of constants to specify the window to use to run the project.
@@ -89,15 +90,20 @@ open class GodotEditor : GodotActivity() {
}
private val commandLineParams = ArrayList<String>()
+ private val editorLoadingIndicator: View? by lazy { findViewById(R.id.editor_loading_indicator) }
+
+ override fun getGodotAppLayout() = R.layout.godot_editor_layout
override fun onCreate(savedInstanceState: Bundle?) {
+ installSplashScreen()
+
// We exclude certain permissions from the set we request at startup, as they'll be
// requested on demand based on use-cases.
PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
- Log.d(TAG, "Received parameters ${params.contentToString()}")
- updateCommandLineParams(params)
+ Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}")
+ updateCommandLineParams(params?.asList() ?: emptyList())
if (BuildConfig.BUILD_TYPE == "dev" && WAIT_FOR_DEBUGGER) {
Debug.waitForDebugger()
@@ -122,109 +128,104 @@ open class GodotEditor : GodotActivity() {
}
}
+ override fun onGodotMainLoopStarted() {
+ super.onGodotMainLoopStarted()
+ runOnUiThread {
+ // Hide the loading indicator
+ editorLoadingIndicator?.visibility = View.GONE
+ }
+ }
+
/**
* Check for project permissions to enable
*/
protected open fun checkForProjectPermissionsToEnable() {
// Check for RECORD_AUDIO permission
- val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"));
+ val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"))
if (audioInputEnabled) {
PermissionsUtil.requestPermission(Manifest.permission.RECORD_AUDIO, this)
}
}
- private fun updateCommandLineParams(args: Array<String>?) {
+ @CallSuper
+ protected open fun updateCommandLineParams(args: List<String>) {
// Update the list of command line params with the new args
commandLineParams.clear()
- if (!args.isNullOrEmpty()) {
- commandLineParams.addAll(listOf(*args))
+ if (args.isNotEmpty()) {
+ commandLineParams.addAll(args)
}
if (BuildConfig.BUILD_TYPE == "dev") {
commandLineParams.add("--benchmark")
}
}
- override fun getCommandLine() = commandLineParams
+ final override fun getCommandLine() = commandLineParams
- override fun onNewGodotInstanceRequested(args: Array<String>): Int {
- // Parse the arguments to figure out which activity to start.
- var targetClass: Class<*> = GodotGame::class.java
- var instanceId = GAME_ID
-
- // Whether we should launch the new godot instance in an adjacent window
- // https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT
- var launchAdjacent = shouldGameLaunchAdjacent()
-
- for (arg in args) {
- if (EDITOR_ARG == arg || EDITOR_ARG_SHORT == arg) {
- targetClass = GodotEditor::class.java
- launchAdjacent = false
- instanceId = EDITOR_ID
- break
- }
+ protected open fun getEditorWindowInfo(args: Array<String>): EditorWindowInfo {
+ var hasEditor = false
- if (PROJECT_MANAGER_ARG == arg || PROJECT_MANAGER_ARG_SHORT == arg) {
- targetClass = GodotProjectManager::class.java
- launchAdjacent = false
- instanceId = PROJECT_MANAGER_ID
- break
+ var i = 0
+ while (i < args.size) {
+ when (args[i++]) {
+ EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true
}
}
+ return if (hasEditor) {
+ EDITOR_MAIN_INFO
+ } else {
+ RUN_GAME_INFO
+ }
+ }
+
+ protected open fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? {
+ return when (instanceId) {
+ RUN_GAME_INFO.windowId -> RUN_GAME_INFO
+ EDITOR_MAIN_INFO.windowId -> EDITOR_MAIN_INFO
+ else -> null
+ }
+ }
+
+ override fun onNewGodotInstanceRequested(args: Array<String>): Int {
+ val editorWindowInfo = getEditorWindowInfo(args)
+
// Launch a new activity
- val newInstance = Intent(this, targetClass)
+ val newInstance = Intent()
+ .setComponent(ComponentName(this, editorWindowInfo.windowClassName))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(EXTRA_COMMAND_LINE_PARAMS, args)
- if (launchAdjacent) {
- newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.ENABLED ||
+ (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.AUTO && shouldGameLaunchAdjacent())) {
+ Log.v(TAG, "Adding flag for adjacent launch")
+ newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)
+ }
}
- if (targetClass == javaClass) {
- Log.d(TAG, "Restarting $targetClass with parameters ${args.contentToString()}")
+ if (editorWindowInfo.windowClassName == javaClass.name) {
+ Log.d(TAG, "Restarting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
ProcessPhoenix.triggerRebirth(this, newInstance)
} else {
- Log.d(TAG, "Starting $targetClass with parameters ${args.contentToString()}")
+ Log.d(TAG, "Starting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
newInstance.putExtra(EXTRA_NEW_LAUNCH, true)
startActivity(newInstance)
}
- return instanceId
+ return editorWindowInfo.windowId
}
- override fun onGodotForceQuit(godotInstanceId: Int): Boolean {
- val targetClass: Class<*>?
- val processNameSuffix: String
- when (godotInstanceId) {
- GAME_ID -> {
- processNameSuffix = GAME_PROCESS_NAME_SUFFIX
- targetClass = GodotGame::class.java
- }
- EDITOR_ID -> {
- processNameSuffix = EDITOR_PROCESS_NAME_SUFFIX
- targetClass = GodotEditor::class.java
- }
- PROJECT_MANAGER_ID -> {
- processNameSuffix = PROJECT_MANAGER_PROCESS_NAME_SUFFIX
- targetClass = GodotProjectManager::class.java
- }
- else -> {
- processNameSuffix = ""
- targetClass = null
- }
- }
+ final override fun onGodotForceQuit(godotInstanceId: Int): Boolean {
+ val editorWindowInfo = getEditorWindowInfoForInstanceId(godotInstanceId) ?: return super.onGodotForceQuit(godotInstanceId)
- if (targetClass == javaClass) {
- Log.d(TAG, "Force quitting $targetClass")
+ if (editorWindowInfo.windowClassName == javaClass.name) {
+ Log.d(TAG, "Force quitting ${editorWindowInfo.windowClassName}")
ProcessPhoenix.forceQuit(this)
return true
}
- if (processNameSuffix.isBlank()) {
- return false
- }
-
+ val processName = packageName + editorWindowInfo.processNameSuffix
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningProcesses = activityManager.runningAppProcesses
for (runningProcess in runningProcesses) {
- if (runningProcess.processName.endsWith(processNameSuffix)) {
+ if (runningProcess.processName == processName) {
// Killing process directly
Log.v(TAG, "Killing Godot process ${runningProcess.processName}")
Process.killProcess(runningProcess.pid)
@@ -232,11 +233,11 @@ open class GodotEditor : GodotActivity() {
}
}
- return false
+ return super.onGodotForceQuit(godotInstanceId)
}
// Get the screen's density scale
- protected val isLargeScreen: Boolean
+ private val isLargeScreen: Boolean
// Get the minimum window size // Correspond to the EXPANDED window size class.
get() {
val metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this)
@@ -273,6 +274,10 @@ open class GodotEditor : GodotActivity() {
protected open fun enablePanAndScaleGestures() =
java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures"))
+ /**
+ * Whether we should launch the new godot instance in an adjacent window
+ * @see https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT
+ */
private fun shouldGameLaunchAdjacent(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
index aa4d02b5b2..8e4e089211 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
@@ -34,6 +34,9 @@ package org.godotengine.editor
* Drives the 'run project' window of the Godot Editor.
*/
class GodotGame : GodotEditor() {
+
+ override fun getGodotAppLayout() = org.godotengine.godot.R.layout.godot_app_layout
+
override fun overrideOrientationRequest() = false
override fun enableLongPressGestures() = false
diff --git a/platform/android/java/editor/src/main/res/layout/godot_editor_layout.xml b/platform/android/java/editor/src/main/res/layout/godot_editor_layout.xml
new file mode 100644
index 0000000000..431a468f29
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/layout/godot_editor_layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <FrameLayout
+ android:id="@+id/godot_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <ProgressBar
+ style="@android:style/Widget.Holo.ProgressBar.Large"
+ android:id="@+id/editor_loading_indicator"
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:indeterminate="true"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintVertical_bias="0.80"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/platform/android/java/editor/src/main/res/values/strings.xml b/platform/android/java/editor/src/main/res/values/strings.xml
index 216d02d9c7..909711ab18 100644
--- a/platform/android/java/editor/src/main/res/values/strings.xml
+++ b/platform/android/java/editor/src/main/res/values/strings.xml
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <string name="godot_editor_name_string">Godot Editor 4</string>
-
<string name="denied_storage_permission_error_msg">Missing storage access permission!</string>
</resources>
diff --git a/platform/android/java/editor/src/main/res/values/themes.xml b/platform/android/java/editor/src/main/res/values/themes.xml
index fda04d6dc7..2b352247db 100644
--- a/platform/android/java/editor/src/main/res/values/themes.xml
+++ b/platform/android/java/editor/src/main/res/values/themes.xml
@@ -2,4 +2,10 @@
<resources>
<style name="GodotEditorTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen">
</style>
+
+ <style name="GodotEditorSplashScreenTheme" parent="Theme.SplashScreen.IconBackground">
+ <!-- Set the theme of the Activity that directly follows your splash
+ screen. This is required. -->
+ <item name="postSplashScreenTheme">@style/GodotEditorTheme</item>
+ </style>
</resources>
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index ed967b9660..81ab598b90 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -11,6 +11,8 @@ apply from: "../scripts/publish-module.gradle"
dependencies {
implementation "androidx.fragment:fragment:$versions.fragmentVersion"
+
+ testImplementation "junit:junit:4.13.2"
}
def pathToRootDir = "../../../../"
@@ -74,6 +76,7 @@ android {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
+ test.java.srcDirs = ['srcTest/java']
res.srcDirs = ['res']
aidl.srcDirs = ['aidl']
assets.srcDirs = ['assets']
@@ -118,7 +121,7 @@ android {
case "dev":
default:
sconsTarget += "_debug"
- break;
+ break
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index e2e77e7796..290be727ab 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -38,7 +38,6 @@ import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Color
-import android.graphics.Rect
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
@@ -46,16 +45,19 @@ import android.hardware.SensorManager
import android.os.*
import android.util.Log
import android.view.*
-import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.FrameLayout
import androidx.annotation.Keep
import androidx.annotation.StringRes
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsAnimationCompat
+import androidx.core.view.WindowInsetsCompat
import com.google.android.vending.expansion.downloader.*
import org.godotengine.godot.input.GodotEditText
import org.godotengine.godot.io.directory.DirectoryAccessHandler
import org.godotengine.godot.io.file.FileAccessHandler
import org.godotengine.godot.plugin.GodotPluginRegistry
import org.godotengine.godot.tts.GodotTTS
+import org.godotengine.godot.utils.CommandLineFileParser
import org.godotengine.godot.utils.GodotNetUtils
import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.PermissionsUtil.requestPermission
@@ -68,7 +70,7 @@ import org.godotengine.godot.xr.XRMode
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
-import java.nio.charset.StandardCharsets
+import java.lang.Exception
import java.security.MessageDigest
import java.util.*
@@ -84,6 +86,9 @@ class Godot(private val context: Context) : SensorEventListener {
private val TAG = Godot::class.java.simpleName
}
+ private val windowManager: WindowManager by lazy {
+ requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ }
private val pluginRegistry: GodotPluginRegistry by lazy {
GodotPluginRegistry.getPluginRegistry()
}
@@ -120,6 +125,7 @@ class Godot(private val context: Context) : SensorEventListener {
val directoryAccessHandler = DirectoryAccessHandler(context)
val fileAccessHandler = FileAccessHandler(context)
val netUtils = GodotNetUtils(context)
+ private val commandLineFileParser = CommandLineFileParser()
/**
* Tracks whether [onCreate] was completed successfully.
@@ -150,7 +156,7 @@ class Godot(private val context: Context) : SensorEventListener {
private var useApkExpansion = false
private var useImmersive = false
private var useDebugOpengl = false
- private var darkMode = false;
+ private var darkMode = false
private var containerLayout: FrameLayout? = null
var renderView: GodotRenderView? = null
@@ -290,7 +296,7 @@ class Godot(private val context: Context) : SensorEventListener {
initializationStarted = false
throw e
} finally {
- endBenchmarkMeasure("Startup", "Godot::onCreate");
+ endBenchmarkMeasure("Startup", "Godot::onCreate")
}
}
@@ -396,72 +402,62 @@ class Godot(private val context: Context) : SensorEventListener {
}
if (host == primaryHost) {
- renderView!!.startRenderer()
+ renderView?.startRenderer()
}
- val view: View = renderView!!.view
- containerLayout?.addView(
- view,
+
+ renderView?.let {
+ containerLayout?.addView(
+ it.view,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
- )
+ )
+ }
+
editText.setView(renderView)
io?.setEdit(editText)
// Listeners for keyboard height.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- // Report the height of virtual keyboard as it changes during the animation.
- val decorView = activity.window.decorView
- decorView.setWindowInsetsAnimationCallback(object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
- var startBottom = 0
- var endBottom = 0
- override fun onPrepare(animation: WindowInsetsAnimation) {
- startBottom = decorView.rootWindowInsets.getInsets(WindowInsets.Type.ime()).bottom
- }
+ val decorView = activity.window.decorView
+ // Report the height of virtual keyboard as it changes during the animation.
+ ViewCompat.setWindowInsetsAnimationCallback(decorView, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
+ var startBottom = 0
+ var endBottom = 0
+ override fun onPrepare(animation: WindowInsetsAnimationCompat) {
+ startBottom = ViewCompat.getRootWindowInsets(decorView)?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0
+ }
- override fun onStart(animation: WindowInsetsAnimation, bounds: WindowInsetsAnimation.Bounds): WindowInsetsAnimation.Bounds {
- endBottom = decorView.rootWindowInsets.getInsets(WindowInsets.Type.ime()).bottom
- return bounds
- }
+ override fun onStart(animation: WindowInsetsAnimationCompat, bounds: WindowInsetsAnimationCompat.BoundsCompat): WindowInsetsAnimationCompat.BoundsCompat {
+ endBottom = ViewCompat.getRootWindowInsets(decorView)?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0
+ return bounds
+ }
- override fun onProgress(windowInsets: WindowInsets, list: List<WindowInsetsAnimation>): WindowInsets {
- // Find the IME animation.
- var imeAnimation: WindowInsetsAnimation? = null
- for (animation in list) {
- if (animation.typeMask and WindowInsets.Type.ime() != 0) {
- imeAnimation = animation
- break
- }
- }
- // Update keyboard height based on IME animation.
- if (imeAnimation != null) {
- val interpolatedFraction = imeAnimation.interpolatedFraction
- // Linear interpolation between start and end values.
- val keyboardHeight = startBottom * (1.0f - interpolatedFraction) + endBottom * interpolatedFraction
- GodotLib.setVirtualKeyboardHeight(keyboardHeight.toInt())
+ override fun onProgress(windowInsets: WindowInsetsCompat, animationsList: List<WindowInsetsAnimationCompat>): WindowInsetsCompat {
+ // Find the IME animation.
+ var imeAnimation: WindowInsetsAnimationCompat? = null
+ for (animation in animationsList) {
+ if (animation.typeMask and WindowInsetsCompat.Type.ime() != 0) {
+ imeAnimation = animation
+ break
}
- return windowInsets
}
- override fun onEnd(animation: WindowInsetsAnimation) {}
- })
- } else {
- // Infer the virtual keyboard height using visible area.
- view.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
- // Don't allocate a new Rect every time the callback is called.
- val visibleSize = Rect()
- override fun onGlobalLayout() {
- val surfaceView = renderView!!.view
- surfaceView.getWindowVisibleDisplayFrame(visibleSize)
- val keyboardHeight = surfaceView.height - visibleSize.bottom
- GodotLib.setVirtualKeyboardHeight(keyboardHeight)
+ // Update keyboard height based on IME animation.
+ if (imeAnimation != null) {
+ val interpolatedFraction = imeAnimation.interpolatedFraction
+ // Linear interpolation between start and end values.
+ val keyboardHeight = startBottom * (1.0f - interpolatedFraction) + endBottom * interpolatedFraction
+ GodotLib.setVirtualKeyboardHeight(keyboardHeight.toInt())
}
- })
- }
+ return windowInsets
+ }
+
+ override fun onEnd(animation: WindowInsetsAnimationCompat) {}
+ })
if (host == primaryHost) {
- renderView!!.queueOnRenderThread {
+ renderView?.queueOnRenderThread {
for (plugin in pluginRegistry.allPlugins) {
plugin.onRegisterPluginWithGodotNative()
}
@@ -495,7 +491,7 @@ class Godot(private val context: Context) : SensorEventListener {
return
}
- renderView!!.onActivityStarted()
+ renderView?.onActivityStarted()
}
fun onResume(host: GodotHost) {
@@ -503,7 +499,7 @@ class Godot(private val context: Context) : SensorEventListener {
return
}
- renderView!!.onActivityResumed()
+ renderView?.onActivityResumed()
if (mAccelerometer != null) {
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
}
@@ -535,7 +531,7 @@ class Godot(private val context: Context) : SensorEventListener {
return
}
- renderView!!.onActivityPaused()
+ renderView?.onActivityPaused()
mSensorManager.unregisterListener(this)
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainPause()
@@ -547,7 +543,7 @@ class Godot(private val context: Context) : SensorEventListener {
return
}
- renderView!!.onActivityStopped()
+ renderView?.onActivityStopped()
}
fun onDestroy(primaryHost: GodotHost) {
@@ -569,7 +565,7 @@ class Godot(private val context: Context) : SensorEventListener {
* Configuration change callback
*/
fun onConfigurationChanged(newConfig: Configuration) {
- var newDarkMode = newConfig.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
+ val newDarkMode = newConfig.uiMode.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
if (darkMode != newDarkMode) {
darkMode = newDarkMode
GodotLib.onNightModeChanged()
@@ -613,7 +609,7 @@ class Godot(private val context: Context) : SensorEventListener {
// These properties are defined after Godot setup completion, so we retrieve them here.
val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click"))
val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))
- val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis"));
+ val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis"))
runOnUiThread {
renderView?.inputHandler?.apply {
@@ -686,9 +682,7 @@ class Godot(private val context: Context) : SensorEventListener {
* This must be called after the render thread has started.
*/
fun runOnRenderThread(action: Runnable) {
- if (renderView != null) {
- renderView!!.queueOnRenderThread(action)
- }
+ renderView?.queueOnRenderThread(action)
}
/**
@@ -765,7 +759,7 @@ class Godot(private val context: Context) : SensorEventListener {
return mClipboard.hasPrimaryClip()
}
- fun getClipboard(): String? {
+ fun getClipboard(): String {
val clipData = mClipboard.primaryClip ?: return ""
val text = clipData.getItemAt(0).text ?: return ""
return text.toString()
@@ -782,15 +776,14 @@ class Godot(private val context: Context) : SensorEventListener {
@Keep
private fun forceQuit(instanceId: Int): Boolean {
- if (primaryHost == null) {
- return false
- }
- return if (instanceId == 0) {
- primaryHost!!.onGodotForceQuit(this)
- true
- } else {
- primaryHost!!.onGodotForceQuit(instanceId)
- }
+ primaryHost?.let {
+ if (instanceId == 0) {
+ it.onGodotForceQuit(this)
+ return true
+ } else {
+ return it.onGodotForceQuit(instanceId)
+ }
+ } ?: return false
}
fun onBackPressed(host: GodotHost) {
@@ -804,20 +797,17 @@ class Godot(private val context: Context) : SensorEventListener {
shouldQuit = false
}
}
- if (shouldQuit && renderView != null) {
- renderView!!.queueOnRenderThread { GodotLib.back() }
+ if (shouldQuit) {
+ renderView?.queueOnRenderThread { GodotLib.back() }
}
}
private fun getRotatedValues(values: FloatArray?): FloatArray? {
if (values == null || values.size != 3) {
- return values
+ return null
}
- val display =
- (requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
- val displayRotation = display.rotation
val rotatedValues = FloatArray(3)
- when (displayRotation) {
+ when (windowManager.defaultDisplay.rotation) {
Surface.ROTATION_0 -> {
rotatedValues[0] = values[0]
rotatedValues[1] = values[1]
@@ -846,37 +836,36 @@ class Godot(private val context: Context) : SensorEventListener {
if (renderView == null) {
return
}
+
+ val rotatedValues = getRotatedValues(event.values)
+
when (event.sensor.type) {
Sensor.TYPE_ACCELEROMETER -> {
- val rotatedValues = getRotatedValues(event.values)
- renderView!!.queueOnRenderThread {
- GodotLib.accelerometer(
- -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2]
- )
+ rotatedValues?.let {
+ renderView?.queueOnRenderThread {
+ GodotLib.accelerometer(-it[0], -it[1], -it[2])
+ }
}
}
Sensor.TYPE_GRAVITY -> {
- val rotatedValues = getRotatedValues(event.values)
- renderView!!.queueOnRenderThread {
- GodotLib.gravity(
- -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2]
- )
+ rotatedValues?.let {
+ renderView?.queueOnRenderThread {
+ GodotLib.gravity(-it[0], -it[1], -it[2])
+ }
}
}
Sensor.TYPE_MAGNETIC_FIELD -> {
- val rotatedValues = getRotatedValues(event.values)
- renderView!!.queueOnRenderThread {
- GodotLib.magnetometer(
- -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2]
- )
+ rotatedValues?.let {
+ renderView?.queueOnRenderThread {
+ GodotLib.magnetometer(-it[0], -it[1], -it[2])
+ }
}
}
Sensor.TYPE_GYROSCOPE -> {
- val rotatedValues = getRotatedValues(event.values)
- renderView!!.queueOnRenderThread {
- GodotLib.gyroscope(
- rotatedValues!![0], rotatedValues[1], rotatedValues[2]
- )
+ rotatedValues?.let {
+ renderView?.queueOnRenderThread {
+ GodotLib.gyroscope(it[0], it[1], it[2])
+ }
}
}
}
@@ -890,16 +879,25 @@ class Godot(private val context: Context) : SensorEventListener {
*/
@SuppressLint("MissingPermission")
@Keep
- private fun vibrate(durationMs: Int) {
+ private fun vibrate(durationMs: Int, amplitude: Int) {
if (durationMs > 0 && requestPermission("VIBRATE")) {
val vibratorService = getActivity()?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- vibratorService.vibrate(
- VibrationEffect.createOneShot(
- durationMs.toLong(),
- VibrationEffect.DEFAULT_AMPLITUDE
+ if (amplitude <= -1) {
+ vibratorService.vibrate(
+ VibrationEffect.createOneShot(
+ durationMs.toLong(),
+ VibrationEffect.DEFAULT_AMPLITUDE
+ )
)
- )
+ } else {
+ vibratorService.vibrate(
+ VibrationEffect.createOneShot(
+ durationMs.toLong(),
+ amplitude
+ )
+ )
+ }
} else {
// deprecated in API 26
vibratorService.vibrate(durationMs.toLong())
@@ -908,47 +906,18 @@ class Godot(private val context: Context) : SensorEventListener {
}
private fun getCommandLine(): MutableList<String> {
- val original: MutableList<String> = parseCommandLine()
+ val commandLine = try {
+ commandLineFileParser.parseCommandLine(requireActivity().assets.open("_cl_"))
+ } catch (ignored: Exception) {
+ mutableListOf()
+ }
+
val hostCommandLine = primaryHost?.commandLine
if (!hostCommandLine.isNullOrEmpty()) {
- original.addAll(hostCommandLine)
+ commandLine.addAll(hostCommandLine)
}
- return original
- }
- private fun parseCommandLine(): MutableList<String> {
- val inputStream: InputStream
- return try {
- inputStream = requireActivity().assets.open("_cl_")
- val len = ByteArray(4)
- var r = inputStream.read(len)
- if (r < 4) {
- return mutableListOf()
- }
- val argc =
- (len[3].toInt() and 0xFF) shl 24 or ((len[2].toInt() and 0xFF) shl 16) or ((len[1].toInt() and 0xFF) shl 8) or (len[0].toInt() and 0xFF)
- val cmdline = ArrayList<String>(argc)
- for (i in 0 until argc) {
- r = inputStream.read(len)
- if (r < 4) {
- return mutableListOf()
- }
- val strlen =
- (len[3].toInt() and 0xFF) shl 24 or ((len[2].toInt() and 0xFF) shl 16) or ((len[1].toInt() and 0xFF) shl 8) or (len[0].toInt() and 0xFF)
- if (strlen > 65535) {
- return mutableListOf()
- }
- val arg = ByteArray(strlen)
- r = inputStream.read(arg)
- if (r == strlen) {
- cmdline.add(String(arg, StandardCharsets.UTF_8))
- }
- }
- cmdline
- } catch (e: Exception) {
- // The _cl_ file can be missing with no adverse effect
- mutableListOf()
- }
+ return commandLine
}
/**
@@ -1039,7 +1008,7 @@ class Godot(private val context: Context) : SensorEventListener {
@Keep
private fun initInputDevices() {
- renderView!!.initInputDevices()
+ renderView?.initInputDevices()
}
@Keep
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
index e01c5481d5..4c5e857b7a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
@@ -36,6 +36,7 @@ import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import androidx.annotation.CallSuper
+import androidx.annotation.LayoutRes
import androidx.fragment.app.FragmentActivity
import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.ProcessPhoenix
@@ -65,7 +66,7 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.godot_app_layout)
+ setContentView(getGodotAppLayout())
handleStartIntent(intent, true)
@@ -80,11 +81,15 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
}
}
+ @LayoutRes
+ protected open fun getGodotAppLayout() = R.layout.godot_app_layout
+
override fun onDestroy() {
Log.v(TAG, "Destroying Godot app...")
super.onDestroy()
- if (godotFragment != null) {
- terminateGodotInstance(godotFragment!!.godot)
+
+ godotFragment?.let {
+ terminateGodotInstance(it.godot)
}
}
@@ -93,22 +98,26 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
}
private fun terminateGodotInstance(instance: Godot) {
- if (godotFragment != null && instance === godotFragment!!.godot) {
- Log.v(TAG, "Force quitting Godot instance")
- ProcessPhoenix.forceQuit(this)
+ godotFragment?.let {
+ if (instance === it.godot) {
+ Log.v(TAG, "Force quitting Godot instance")
+ ProcessPhoenix.forceQuit(this)
+ }
}
}
override fun onGodotRestartRequested(instance: Godot) {
runOnUiThread {
- if (godotFragment != null && instance === godotFragment!!.godot) {
- // It's very hard to properly de-initialize Godot on Android to restart the game
- // from scratch. Therefore, we need to kill the whole app process and relaunch it.
- //
- // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
- // releasing and reloading native libs or resetting their state somehow and clearing static data).
- Log.v(TAG, "Restarting Godot instance...")
- ProcessPhoenix.triggerRebirth(this)
+ godotFragment?.let {
+ if (instance === it.godot) {
+ // It's very hard to properly de-initialize Godot on Android to restart the game
+ // from scratch. Therefore, we need to kill the whole app process and relaunch it.
+ //
+ // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
+ // releasing and reloading native libs or resetting their state somehow and clearing static data).
+ Log.v(TAG, "Restarting Godot instance...")
+ ProcessPhoenix.triggerRebirth(this)
+ }
}
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
index bd8c58ad69..c316812404 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
@@ -1955,4 +1955,3 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
private int mEGLContextClientVersion;
private boolean mPreserveEGLContextOnPause;
}
-
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
index dc8a0e54bb..c085bb8886 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
@@ -266,8 +266,13 @@ public class GodotEditText extends EditText {
boolean hasHardwareKeyboard() {
Configuration config = getResources().getConfiguration();
- return config.keyboard != Configuration.KEYBOARD_NOKEYS &&
+ boolean hasHardwareKeyboardConfig = config.keyboard != Configuration.KEYBOARD_NOKEYS &&
config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
+ if (hasHardwareKeyboardConfig) {
+ return true;
+ }
+
+ return mRenderView.getInputHandler().hasHardwareKeyboard();
}
// ===========================================================
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
index 89fbb9f580..49b34a5229 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
@@ -61,8 +61,11 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
private var contextClickInProgress = false
private var pointerCaptureInProgress = false
+ private var lastDragX: Float = 0.0f
+ private var lastDragY: Float = 0.0f
+
override fun onDown(event: MotionEvent): Boolean {
- GodotInputHandler.handleMotionEvent(event.source, MotionEvent.ACTION_DOWN, event.buttonState, event.x, event.y, nextDownIsDoubleTap)
+ GodotInputHandler.handleMotionEvent(event, MotionEvent.ACTION_DOWN, nextDownIsDoubleTap)
nextDownIsDoubleTap = false
return true
}
@@ -82,20 +85,14 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
}
// Cancel the previous down event
- GodotInputHandler.handleMotionEvent(
- event.source,
- MotionEvent.ACTION_CANCEL,
- event.buttonState,
- event.x,
- event.y
- )
+ GodotInputHandler.handleMotionEvent(event, MotionEvent.ACTION_CANCEL)
// Turn a context click into a single tap right mouse button click.
GodotInputHandler.handleMouseEvent(
+ event,
MotionEvent.ACTION_DOWN,
MotionEvent.BUTTON_SECONDARY,
- event.x,
- event.y
+ false
)
contextClickInProgress = true
}
@@ -107,16 +104,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
if (!hasCapture) {
// Dispatch a mouse relative ACTION_UP event to signal the end of the capture
- GodotInputHandler.handleMouseEvent(
- MotionEvent.ACTION_UP,
- 0,
- 0f,
- 0f,
- 0f,
- 0f,
- false,
- true
- )
+ GodotInputHandler.handleMouseEvent(MotionEvent.ACTION_UP, true)
}
pointerCaptureInProgress = hasCapture
}
@@ -139,32 +127,19 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
return true
}
- val sourceMouseRelative = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)
- } else {
- false
- }
-
if (pointerCaptureInProgress || dragInProgress || contextClickInProgress) {
if (contextClickInProgress || GodotInputHandler.isMouseEvent(event)) {
// This may be an ACTION_BUTTON_RELEASE event which we don't handle,
// so we convert it to an ACTION_UP event.
- GodotInputHandler.handleMouseEvent(
- MotionEvent.ACTION_UP,
- event.buttonState,
- event.x,
- event.y,
- 0f,
- 0f,
- false,
- sourceMouseRelative
- )
+ GodotInputHandler.handleMouseEvent(event, MotionEvent.ACTION_UP)
} else {
GodotInputHandler.handleTouchEvent(event)
}
pointerCaptureInProgress = false
dragInProgress = false
contextClickInProgress = false
+ lastDragX = 0.0f
+ lastDragY = 0.0f
return true
}
@@ -173,22 +148,19 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
private fun onActionMove(event: MotionEvent): Boolean {
if (contextClickInProgress) {
- val sourceMouseRelative = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)
- } else {
- false
- }
- GodotInputHandler.handleMouseEvent(
- event.actionMasked,
- MotionEvent.BUTTON_SECONDARY,
- event.x,
- event.y,
- 0f,
- 0f,
- false,
- sourceMouseRelative
- )
+ GodotInputHandler.handleMouseEvent(event, event.actionMasked, MotionEvent.BUTTON_SECONDARY, false)
return true
+ } else if (!scaleInProgress) {
+ // The 'onScroll' event is triggered with a long delay.
+ // Force the 'InputEventScreenDrag' event earlier here.
+ // We don't toggle 'dragInProgress' here so that the scaling logic can override the drag operation if needed.
+ // Once the 'onScroll' event kicks-in, 'dragInProgress' will be properly set.
+ if (lastDragX != event.getX(0) || lastDragY != event.getY(0)) {
+ lastDragX = event.getX(0)
+ lastDragY = event.getY(0)
+ GodotInputHandler.handleMotionEvent(event)
+ return true
+ }
}
return false
}
@@ -197,7 +169,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
if (event.actionMasked == MotionEvent.ACTION_UP) {
nextDownIsDoubleTap = false
GodotInputHandler.handleMotionEvent(event)
- } else if (event.actionMasked == MotionEvent.ACTION_MOVE && panningAndScalingEnabled == false) {
+ } else if (event.actionMasked == MotionEvent.ACTION_MOVE && !panningAndScalingEnabled) {
GodotInputHandler.handleMotionEvent(event)
}
@@ -216,18 +188,14 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
distanceY: Float
): Boolean {
if (scaleInProgress) {
- if (dragInProgress) {
+ if (dragInProgress || lastDragX != 0.0f || lastDragY != 0.0f) {
if (originEvent != null) {
// Cancel the drag
- GodotInputHandler.handleMotionEvent(
- originEvent.source,
- MotionEvent.ACTION_CANCEL,
- originEvent.buttonState,
- originEvent.x,
- originEvent.y
- )
+ GodotInputHandler.handleMotionEvent(originEvent, MotionEvent.ACTION_CANCEL)
}
dragInProgress = false
+ lastDragX = 0.0f
+ lastDragY = 0.0f
}
}
@@ -235,8 +203,10 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
val y = terminusEvent.y
if (terminusEvent.pointerCount >= 2 && panningAndScalingEnabled && !pointerCaptureInProgress && !dragInProgress) {
GodotLib.pan(x, y, distanceX / 5f, distanceY / 5f)
- } else if (!scaleInProgress){
+ } else if (!scaleInProgress) {
dragInProgress = true
+ lastDragX = terminusEvent.getX(0)
+ lastDragY = terminusEvent.getY(0)
GodotInputHandler.handleMotionEvent(terminusEvent)
}
return true
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
index fe971cf442..83e76e49c9 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -62,6 +62,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
private final SparseIntArray mJoystickIds = new SparseIntArray(4);
private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<>(4);
+ private final HashSet<Integer> mHardwareKeyboardIds = new HashSet<>();
private final GodotRenderView mRenderView;
private final InputManager mInputManager;
@@ -114,6 +115,10 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
rotaryInputAxis = axis;
}
+ boolean hasHardwareKeyboard() {
+ return !mHardwareKeyboardIds.isEmpty();
+ }
+
private boolean isKeyEventGameDevice(int source) {
// Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD)
if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD))
@@ -195,7 +200,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
public boolean onTouchEvent(final MotionEvent event) {
- lastSeenToolType = event.getToolType(0);
+ lastSeenToolType = getEventToolType(event);
this.scaleGestureDetector.onTouchEvent(event);
if (this.gestureDetector.onTouchEvent(event)) {
@@ -221,17 +226,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
public boolean onGenericMotionEvent(MotionEvent event) {
- lastSeenToolType = event.getToolType(0);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && gestureDetector.onGenericMotionEvent(event)) {
- // The gesture detector has handled the event.
- return true;
- }
-
- if (godotGestureHandler.onMotionEvent(event)) {
- // The gesture handler has handled the event.
- return true;
- }
+ lastSeenToolType = getEventToolType(event);
if (event.isFromSource(InputDevice.SOURCE_JOYSTICK) && event.getActionMasked() == MotionEvent.ACTION_MOVE) {
// Check if the device exists
@@ -268,11 +263,20 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
return true;
}
- } else {
- return handleMouseEvent(event);
+ return false;
}
- return false;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && gestureDetector.onGenericMotionEvent(event)) {
+ // The gesture detector has handled the event.
+ return true;
+ }
+
+ if (godotGestureHandler.onMotionEvent(event)) {
+ // The gesture handler has handled the event.
+ return true;
+ }
+
+ return handleMouseEvent(event);
}
public void initInputDevices() {
@@ -310,11 +314,17 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return;
}
- int sources = device.getSources();
+ // Device may be an external keyboard; store the device id
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
+ device.supportsSource(InputDevice.SOURCE_KEYBOARD) &&
+ device.isExternal() &&
+ device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
+ mHardwareKeyboardIds.add(deviceId);
+ }
// Device may not be a joystick or gamepad
- if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD &&
- (sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
+ if (!device.supportsSource(InputDevice.SOURCE_GAMEPAD) &&
+ !device.supportsSource(InputDevice.SOURCE_JOYSTICK)) {
return;
}
@@ -359,6 +369,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
@Override
public void onInputDeviceRemoved(int deviceId) {
+ mHardwareKeyboardIds.remove(deviceId);
+
// Check if the device has not been already removed
if (mJoystickIds.indexOfKey(deviceId) < 0) {
return;
@@ -440,50 +452,65 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return button;
}
- static boolean isMouseEvent(MotionEvent event) {
- return isMouseEvent(event.getSource());
+ private static int getEventToolType(MotionEvent event) {
+ return event.getPointerCount() > 0 ? event.getToolType(0) : MotionEvent.TOOL_TYPE_UNKNOWN;
}
- private static boolean isMouseEvent(int eventSource) {
- boolean mouseSource = ((eventSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) || ((eventSource & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- mouseSource = mouseSource || ((eventSource & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE);
+ static boolean isMouseEvent(MotionEvent event) {
+ int toolType = getEventToolType(event);
+ int eventSource = event.getSource();
+
+ switch (toolType) {
+ case MotionEvent.TOOL_TYPE_FINGER:
+ return false;
+
+ case MotionEvent.TOOL_TYPE_MOUSE:
+ case MotionEvent.TOOL_TYPE_STYLUS:
+ case MotionEvent.TOOL_TYPE_ERASER:
+ return true;
+
+ case MotionEvent.TOOL_TYPE_UNKNOWN:
+ default:
+ boolean mouseSource =
+ ((eventSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) ||
+ ((eventSource & (InputDevice.SOURCE_TOUCHSCREEN | InputDevice.SOURCE_STYLUS)) == InputDevice.SOURCE_STYLUS);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mouseSource = mouseSource ||
+ ((eventSource & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE);
+ }
+ return mouseSource;
}
- return mouseSource;
}
static boolean handleMotionEvent(final MotionEvent event) {
- if (isMouseEvent(event)) {
- return handleMouseEvent(event);
- }
-
- return handleTouchEvent(event);
+ return handleMotionEvent(event, event.getActionMasked());
}
- static boolean handleMotionEvent(int eventSource, int eventAction, int buttonsMask, float x, float y) {
- return handleMotionEvent(eventSource, eventAction, buttonsMask, x, y, false);
+ static boolean handleMotionEvent(final MotionEvent event, int eventActionOverride) {
+ return handleMotionEvent(event, eventActionOverride, false);
}
- static boolean handleMotionEvent(int eventSource, int eventAction, int buttonsMask, float x, float y, boolean doubleTap) {
- return handleMotionEvent(eventSource, eventAction, buttonsMask, x, y, 0, 0, doubleTap);
+ static boolean handleMotionEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
+ if (isMouseEvent(event)) {
+ return handleMouseEvent(event, eventActionOverride, doubleTap);
+ }
+ return handleTouchEvent(event, eventActionOverride, doubleTap);
}
- static boolean handleMotionEvent(int eventSource, int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleTap) {
- if (isMouseEvent(eventSource)) {
- return handleMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleTap, false);
- }
+ private static float getEventTiltX(MotionEvent event) {
+ // Orientation is returned as a radian value between 0 to pi clockwise or 0 to -pi counterclockwise.
+ final float orientation = event.getOrientation();
- return handleTouchEvent(eventAction, x, y, doubleTap);
- }
+ // Tilt is zero is perpendicular to the screen and pi/2 is flat on the surface.
+ final float tilt = event.getAxisValue(MotionEvent.AXIS_TILT);
- static boolean handleMouseEvent(final MotionEvent event) {
- final int eventAction = event.getActionMasked();
- final float x = event.getX();
- final float y = event.getY();
- final int buttonsMask = event.getButtonState();
+ float tiltMult = (float)Math.sin(tilt);
- final float pressure = event.getPressure();
+ // To be consistent with expected tilt.
+ return (float)-Math.sin(orientation) * tiltMult;
+ }
+ private static float getEventTiltY(MotionEvent event) {
// Orientation is returned as a radian value between 0 to pi clockwise or 0 to -pi counterclockwise.
final float orientation = event.getOrientation();
@@ -493,8 +520,26 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
float tiltMult = (float)Math.sin(tilt);
// To be consistent with expected tilt.
- final float tiltX = (float)-Math.sin(orientation) * tiltMult;
- final float tiltY = (float)Math.cos(orientation) * tiltMult;
+ return (float)Math.cos(orientation) * tiltMult;
+ }
+
+ static boolean handleMouseEvent(final MotionEvent event) {
+ return handleMouseEvent(event, event.getActionMasked());
+ }
+
+ static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride) {
+ return handleMouseEvent(event, eventActionOverride, false);
+ }
+
+ static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
+ return handleMouseEvent(event, eventActionOverride, event.getButtonState(), doubleTap);
+ }
+
+ static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, int buttonMaskOverride, boolean doubleTap) {
+ final float x = event.getX();
+ final float y = event.getY();
+
+ final float pressure = event.getPressure();
float verticalFactor = 0;
float horizontalFactor = 0;
@@ -516,15 +561,11 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
sourceMouseRelative = event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE);
}
- return handleMouseEvent(eventAction, buttonsMask, x, y, horizontalFactor, verticalFactor, false, sourceMouseRelative, pressure, tiltX, tiltY);
- }
-
- static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y) {
- return handleMouseEvent(eventAction, buttonsMask, x, y, 0, 0, false, false);
+ return handleMouseEvent(eventActionOverride, buttonMaskOverride, x, y, horizontalFactor, verticalFactor, doubleTap, sourceMouseRelative, pressure, getEventTiltX(event), getEventTiltY(event));
}
- static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative) {
- return handleMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, 1, 0, 0);
+ static boolean handleMouseEvent(int eventAction, boolean sourceMouseRelative) {
+ return handleMouseEvent(eventAction, 0, 0f, 0f, 0f, 0f, false, sourceMouseRelative, 1f, 0f, 0f);
}
static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) {
@@ -563,37 +604,39 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
static boolean handleTouchEvent(final MotionEvent event) {
+ return handleTouchEvent(event, event.getActionMasked());
+ }
+
+ static boolean handleTouchEvent(final MotionEvent event, int eventActionOverride) {
+ return handleTouchEvent(event, eventActionOverride, false);
+ }
+
+ static boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
final int pointerCount = event.getPointerCount();
if (pointerCount == 0) {
return true;
}
- final float[] positions = new float[pointerCount * 3]; // pointerId1, x1, y1, pointerId2, etc...
+ final float[] positions = new float[pointerCount * 6]; // pointerId1, x1, y1, pressure1, tiltX1, tiltY1, pointerId2, etc...
for (int i = 0; i < pointerCount; i++) {
- positions[i * 3 + 0] = event.getPointerId(i);
- positions[i * 3 + 1] = event.getX(i);
- positions[i * 3 + 2] = event.getY(i);
+ positions[i * 6 + 0] = event.getPointerId(i);
+ positions[i * 6 + 1] = event.getX(i);
+ positions[i * 6 + 2] = event.getY(i);
+ positions[i * 6 + 3] = event.getPressure(i);
+ positions[i * 6 + 4] = getEventTiltX(event);
+ positions[i * 6 + 5] = getEventTiltY(event);
}
- final int action = event.getActionMasked();
final int actionPointerId = event.getPointerId(event.getActionIndex());
- return handleTouchEvent(action, actionPointerId, pointerCount, positions, false);
- }
-
- static boolean handleTouchEvent(int eventAction, float x, float y, boolean doubleTap) {
- return handleTouchEvent(eventAction, 0, 1, new float[] { 0, x, y }, doubleTap);
- }
-
- static boolean handleTouchEvent(int eventAction, int actionPointerId, int pointerCount, float[] positions, boolean doubleTap) {
- switch (eventAction) {
+ switch (eventActionOverride) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN: {
- GodotLib.dispatchTouchEvent(eventAction, actionPointerId, pointerCount, positions, doubleTap);
+ GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap);
return true;
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
index 0f447f0b05..11cf7b3566 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
@@ -36,7 +36,9 @@ import android.util.Log
import org.godotengine.godot.io.StorageScope
import java.io.IOException
import java.nio.ByteBuffer
+import java.nio.channels.ClosedChannelException
import java.nio.channels.FileChannel
+import java.nio.channels.NonWritableChannelException
import kotlin.math.max
/**
@@ -135,6 +137,21 @@ internal abstract class DataAccess(private val filePath: String) {
seek(positionFromBeginning)
}
+ fun resize(length: Long): Int {
+ return try {
+ fileChannel.truncate(length)
+ FileErrors.OK.nativeValue
+ } catch (e: NonWritableChannelException) {
+ FileErrors.FILE_CANT_OPEN.nativeValue
+ } catch (e: ClosedChannelException) {
+ FileErrors.FILE_CANT_OPEN.nativeValue
+ } catch (e: IllegalArgumentException) {
+ FileErrors.INVALID_PARAMETER.nativeValue
+ } catch (e: IOException) {
+ FileErrors.FAILED.nativeValue
+ }
+ }
+
fun position(): Long {
return try {
fileChannel.position()
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
index 984bf607d0..1d773467e8 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
@@ -45,7 +45,6 @@ class FileAccessHandler(val context: Context) {
companion object {
private val TAG = FileAccessHandler::class.java.simpleName
- private const val FILE_NOT_FOUND_ERROR_ID = -1
internal const val INVALID_FILE_ID = 0
private const val STARTING_FILE_ID = 1
@@ -56,7 +55,9 @@ class FileAccessHandler(val context: Context) {
}
return try {
- DataAccess.fileExists(storageScope, context, path!!)
+ path?.let {
+ DataAccess.fileExists(storageScope, context, it)
+ } ?: false
} catch (e: SecurityException) {
false
}
@@ -69,20 +70,22 @@ class FileAccessHandler(val context: Context) {
}
return try {
- DataAccess.removeFile(storageScope, context, path!!)
+ path?.let {
+ DataAccess.removeFile(storageScope, context, it)
+ } ?: false
} catch (e: Exception) {
false
}
}
- internal fun renameFile(context: Context, storageScopeIdentifier: StorageScope.Identifier, from: String?, to: String?): Boolean {
+ internal fun renameFile(context: Context, storageScopeIdentifier: StorageScope.Identifier, from: String, to: String): Boolean {
val storageScope = storageScopeIdentifier.identifyStorageScope(from)
if (storageScope == StorageScope.UNKNOWN) {
return false
}
return try {
- DataAccess.renameFile(storageScope, context, from!!, to!!)
+ DataAccess.renameFile(storageScope, context, from, to)
} catch (e: Exception) {
false
}
@@ -106,16 +109,18 @@ class FileAccessHandler(val context: Context) {
return INVALID_FILE_ID
}
- try {
- val dataAccess = DataAccess.generateDataAccess(storageScope, context, path!!, accessFlag) ?: return INVALID_FILE_ID
+ return try {
+ path?.let {
+ val dataAccess = DataAccess.generateDataAccess(storageScope, context, it, accessFlag) ?: return INVALID_FILE_ID
- files.put(++lastFileId, dataAccess)
- return lastFileId
+ files.put(++lastFileId, dataAccess)
+ lastFileId
+ } ?: INVALID_FILE_ID
} catch (e: FileNotFoundException) {
- return FILE_NOT_FOUND_ERROR_ID
+ FileErrors.FILE_NOT_FOUND.nativeValue
} catch (e: Exception) {
Log.w(TAG, "Error while opening $path", e)
- return INVALID_FILE_ID
+ INVALID_FILE_ID
}
}
@@ -176,12 +181,22 @@ class FileAccessHandler(val context: Context) {
}
return try {
- DataAccess.fileLastModified(storageScope, context, filepath!!)
+ filepath?.let {
+ DataAccess.fileLastModified(storageScope, context, it)
+ } ?: 0L
} catch (e: SecurityException) {
0L
}
}
+ fun fileResize(fileId: Int, length: Long): Int {
+ if (!hasFileId(fileId)) {
+ return FileErrors.FAILED.nativeValue
+ }
+
+ return files[fileId].resize(length)
+ }
+
fun fileGetPosition(fileId: Int): Long {
if (!hasFileId(fileId)) {
return 0L
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt
index d0e4279eeb..2df0195de7 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* GodotProjectManager.kt */
+/* FileErrors.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,18 +28,26 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-package org.godotengine.editor
+package org.godotengine.godot.io.file
/**
- * Launcher activity for the Godot Android Editor.
- *
- * It presents the user with the project manager interface.
- * Upon selection of a project, this activity (via its parent logic) starts the
- * [GodotEditor] activity.
+ * Set of errors that may occur when performing data access.
*/
-class GodotProjectManager : GodotEditor() {
- override fun checkForProjectPermissionsToEnable() {
- // Nothing to do here.. we have yet to select a project to load.
+internal enum class FileErrors(val nativeValue: Int) {
+ OK(0),
+ FAILED(-1),
+ FILE_NOT_FOUND(-2),
+ FILE_CANT_OPEN(-3),
+ INVALID_PARAMETER(-4);
+
+ companion object {
+ fun fromNativeError(error: Int): FileErrors? {
+ for (fileError in entries) {
+ if (fileError.nativeValue == error) {
+ return fileError
+ }
+ }
+ return null
+ }
}
}
-
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
index c0912ca4dc..c975c29e96 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
@@ -112,19 +112,18 @@ public abstract class GodotPlugin {
/**
* Register the plugin with Godot native code.
* <p>
- * This method is invoked by the Godot Engine on the render thread.
+ * This method is invoked on the render thread to register the plugin on engine startup.
*/
public final void onRegisterPluginWithGodotNative() {
- registeredSignals.putAll(
- registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals()));
- }
+ final String pluginName = getPluginName();
+ if (!nativeRegisterSingleton(pluginName, this)) {
+ return;
+ }
- private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject,
- String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals) {
- nativeRegisterSingleton(pluginName, pluginObject);
+ List<String> pluginMethods = getPluginMethods();
Set<Method> filteredMethods = new HashSet<>();
- Class<?> clazz = pluginObject.getClass();
+ Class<?> clazz = getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
@@ -156,15 +155,14 @@ public abstract class GodotPlugin {
nativeRegisterMethod(pluginName, method.getName(), method.getReturnType().getName(), pt);
}
+ Set<SignalInfo> pluginSignals = getPluginSignals();
+
// Register the signals for this plugin.
- Map<String, SignalInfo> registeredSignals = new HashMap<>();
for (SignalInfo signalInfo : pluginSignals) {
String signalName = signalInfo.getName();
nativeRegisterSignal(pluginName, signalName, signalInfo.getParamTypesNames());
registeredSignals.put(signalName, signalInfo);
}
-
- return registeredSignals;
}
/**
@@ -408,7 +406,7 @@ public abstract class GodotPlugin {
* Used to setup a {@link GodotPlugin} instance.
* @param p_name Name of the instance.
*/
- private static native void nativeRegisterSingleton(String p_name, Object object);
+ private static native boolean nativeRegisterSingleton(String p_name, Object object);
/**
* Used to complete registration of the {@link GodotPlugin} instance's methods.
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt
new file mode 100644
index 0000000000..ce5c5b6714
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt
@@ -0,0 +1,83 @@
+/**************************************************************************/
+/* CommandLineFileParser.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.godot.utils
+
+import java.io.InputStream
+import java.nio.charset.StandardCharsets
+import java.util.ArrayList
+
+/**
+ * A class that parses the content of file storing command line params. Usually, this file is saved
+ * in `assets/_cl_` on exporting an apk
+ *
+ * Returns a mutable list of command lines
+ */
+internal class CommandLineFileParser {
+ fun parseCommandLine(inputStream: InputStream): MutableList<String> {
+ return try {
+ val headerBytes = ByteArray(4)
+ var argBytes = inputStream.read(headerBytes)
+ if (argBytes < 4) {
+ return mutableListOf()
+ }
+ val argc = decodeHeaderIntValue(headerBytes)
+
+ val cmdline = ArrayList<String>(argc)
+ for (i in 0 until argc) {
+ argBytes = inputStream.read(headerBytes)
+ if (argBytes < 4) {
+ return mutableListOf()
+ }
+ val strlen = decodeHeaderIntValue(headerBytes)
+
+ if (strlen > 65535) {
+ return mutableListOf()
+ }
+
+ val arg = ByteArray(strlen)
+ argBytes = inputStream.read(arg)
+ if (argBytes == strlen) {
+ cmdline.add(String(arg, StandardCharsets.UTF_8))
+ }
+ }
+ cmdline
+ } catch (e: Exception) {
+ // The _cl_ file can be missing with no adverse effect
+ mutableListOf()
+ }
+ }
+
+ private fun decodeHeaderIntValue(headerBytes: ByteArray): Int =
+ (headerBytes[3].toInt() and 0xFF) shl 24 or
+ ((headerBytes[2].toInt() and 0xFF) shl 16) or
+ ((headerBytes[1].toInt() and 0xFF) shl 8) or
+ (headerBytes[0].toInt() and 0xFF)
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index 9df890e6bd..4e8e82a70a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -125,7 +125,7 @@ public final class PermissionsUtil {
}
activity.requestPermissions(requestedPermissions.toArray(new String[0]), REQUEST_ALL_PERMISSION_REQ_CODE);
- return true;
+ return false;
}
/**
@@ -281,8 +281,9 @@ public final class PermissionsUtil {
public static boolean hasManifestPermission(Context context, String permission) {
try {
for (String p : getManifestPermissions(context)) {
- if (permission.equals(p))
+ if (permission.equals(p)) {
return true;
+ }
}
} catch (PackageManager.NameNotFoundException ignored) {
}
@@ -299,8 +300,9 @@ public final class PermissionsUtil {
public static ArrayList<String> getManifestPermissions(Context context) throws PackageManager.NameNotFoundException {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
- if (packageInfo.requestedPermissions == null)
- return new ArrayList<String>();
+ if (packageInfo.requestedPermissions == null) {
+ return new ArrayList<>();
+ }
return new ArrayList<>(Arrays.asList(packageInfo.requestedPermissions));
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
index 4aba0c370d..8c0065b31e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
@@ -142,7 +142,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
fun onSurfaceChanged(width: Int, height: Int) {
lock.withLock {
hasSurface = true
- surfaceChanged = true;
+ surfaceChanged = true
this.width = width
this.height = height
@@ -179,7 +179,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
// blocking the thread lifecycle by holding onto the lock.
if (eventQueue.isNotEmpty()) {
event = eventQueue.removeAt(0)
- break;
+ break
}
if (readyToDraw) {
@@ -199,7 +199,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
}
// Break out of the loop so drawing can occur without holding onto the lock.
- break;
+ break
} else if (rendererResumed) {
// If we aren't ready to draw but are resumed, that means we either lost a surface
// or the app was paused.
diff --git a/platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt b/platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt
new file mode 100644
index 0000000000..8b0466848a
--- /dev/null
+++ b/platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt
@@ -0,0 +1,104 @@
+/**************************************************************************/
+/* CommandLineFileParserTest.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.godot.utils
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.io.ByteArrayInputStream
+import java.io.InputStream
+
+// Godot saves command line params in the `assets/_cl_` file on exporting an apk. By default,
+// without any other commands specified in `command_line/extra_args` in Export window, the content
+// of that _cl_ file consists of only the `--xr_mode_regular` and `--use_immersive` flags.
+// The `CL_` prefix here refers to that file
+private val CL_DEFAULT_NO_EXTRA_ARGS = byteArrayOf(2, 0, 0, 0, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101)
+private val CL_ONE_EXTRA_ARG = byteArrayOf(3, 0, 0, 0, 15, 0, 0, 0, 45, 45, 117, 110, 105, 116, 95, 116, 101, 115, 116, 95, 97, 114, 103, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101)
+private val CL_TWO_EXTRA_ARGS = byteArrayOf(4, 0, 0, 0, 16, 0, 0, 0, 45, 45, 117, 110, 105, 116, 95, 116, 101, 115, 116, 95, 97, 114, 103, 49, 16, 0, 0, 0, 45, 45, 117, 110, 105, 116, 95, 116, 101, 115, 116, 95, 97, 114, 103, 50, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101)
+private val CL_EMPTY = byteArrayOf()
+private val CL_HEADER_TOO_SHORT = byteArrayOf(0, 0, 0)
+private val CL_INCOMPLETE_FIRST_ARG = byteArrayOf(2, 0, 0, 0, 17, 0, 0)
+private val CL_LENGTH_TOO_LONG_IN_FIRST_ARG = byteArrayOf(2, 0, 0, 0, 17, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101)
+private val CL_MISMATCHED_ARG_LENGTH_AND_HEADER_ONE_ARG = byteArrayOf(2, 0, 0, 0, 10, 0, 0, 0, 45, 45, 120, 114)
+private val CL_MISMATCHED_ARG_LENGTH_AND_HEADER_IN_FIRST_ARG = byteArrayOf(2, 0, 0, 0, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101)
+
+@RunWith(Parameterized::class)
+class CommandLineFileParserTest(
+ private val inputStreamArg: InputStream,
+ private val expectedResult: List<String>,
+) {
+
+ private val commandLineFileParser = CommandLineFileParser()
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data() = listOf(
+ arrayOf(ByteArrayInputStream(CL_EMPTY), listOf<String>()),
+ arrayOf(ByteArrayInputStream(CL_HEADER_TOO_SHORT), listOf<String>()),
+
+ arrayOf(ByteArrayInputStream(CL_DEFAULT_NO_EXTRA_ARGS), listOf(
+ "--xr_mode_regular",
+ "--use_immersive",
+ )),
+
+ arrayOf(ByteArrayInputStream(CL_ONE_EXTRA_ARG), listOf(
+ "--unit_test_arg",
+ "--xr_mode_regular",
+ "--use_immersive",
+ )),
+
+ arrayOf(ByteArrayInputStream(CL_TWO_EXTRA_ARGS), listOf(
+ "--unit_test_arg1",
+ "--unit_test_arg2",
+ "--xr_mode_regular",
+ "--use_immersive",
+ )),
+
+ arrayOf(ByteArrayInputStream(CL_INCOMPLETE_FIRST_ARG), listOf<String>()),
+ arrayOf(ByteArrayInputStream(CL_LENGTH_TOO_LONG_IN_FIRST_ARG), listOf<String>()),
+ arrayOf(ByteArrayInputStream(CL_MISMATCHED_ARG_LENGTH_AND_HEADER_ONE_ARG), listOf<String>()),
+ arrayOf(ByteArrayInputStream(CL_MISMATCHED_ARG_LENGTH_AND_HEADER_IN_FIRST_ARG), listOf<String>()),
+ )
+ }
+
+ @Test
+ fun `Given inputStream, When parsing command line, Then a correct list is returned`() {
+ // given
+ val inputStream = inputStreamArg
+
+ // when
+ val result = commandLineFileParser.parseCommandLine(inputStream)
+
+ // then
+ assert(result == expectedResult) { "Expected: $expectedResult Actual: $result" }
+ }
+}
diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
index e1534c7685..96b6dfc9f3 100644
--- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
+++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
@@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(GODOT_ROOT_DIR ../../../..)
+set(ANDROID_ROOT_DIR "${GODOT_ROOT_DIR}/platform/android" CACHE STRING "")
# Get sources
file(GLOB_RECURSE SOURCES ${GODOT_ROOT_DIR}/*.c**)
@@ -15,6 +16,7 @@ file(GLOB_RECURSE HEADERS ${GODOT_ROOT_DIR}/*.h**)
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
target_include_directories(${PROJECT_NAME}
SYSTEM PUBLIC
- ${GODOT_ROOT_DIR})
+ ${GODOT_ROOT_DIR}
+ ${ANDROID_ROOT_DIR})
add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED)
diff --git a/platform/android/java/scripts/publish-root.gradle b/platform/android/java/scripts/publish-root.gradle
index ae88487c34..31338de21e 100644
--- a/platform/android/java/scripts/publish-root.gradle
+++ b/platform/android/java/scripts/publish-root.gradle
@@ -36,4 +36,3 @@ nexusPublishing {
}
}
}
-