diff options
Diffstat (limited to 'platform/android/java')
29 files changed, 843 insertions, 344 deletions
| diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 2d4c4763a2..8c8608cbbb 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -13,13 +13,14 @@          android:xlargeScreens="true" />      <uses-feature -        android:glEsVersion="0x00020000" +        android:glEsVersion="0x00030000"          android:required="true" />      <application          android:label="@string/godot_project_name_string"          android:allowBackup="false"          android:icon="@mipmap/icon" +        android:appCategory="game"          android:isGame="true"          android:hasFragileUserData="false"          android:requestLegacyExternalStorage="false" diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index fbd97fae0b..f1b4bfd534 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,14 +1,17 @@  ext.versions = [ -    androidGradlePlugin: '7.0.3', +    androidGradlePlugin: '7.2.1',      compileSdk         : 32, -    minSdk             : 19, // Also update 'platform/android/java/lib/AndroidManifest.xml#minSdkVersion' & 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION' -    targetSdk          : 32, // Also update 'platform/android/java/lib/AndroidManifest.xml#targetSdkVersion' & 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION' +    // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION' +    minSdk             : 21, +    // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION' +    targetSdk          : 32,      buildTools         : '32.0.0', -    kotlinVersion      : '1.6.21', +    kotlinVersion      : '1.7.0',      fragmentVersion    : '1.3.6',      nexusPublishVersion: '1.1.0',      javaVersion        : 11, -    ndkVersion         : '23.2.8568313' // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated. +    // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated. +    ndkVersion         : '23.2.8568313'  ] @@ -127,16 +130,36 @@ ext.generateGodotLibraryVersion = { List<String> requiredKeys ->          if (requiredKeys.empty) {              libraryVersionName = map.values().join(".")              try { +                if (map.containsKey("status")) { +                    int statusCode = 0 +                    String statusValue = map["status"] +                    if (statusValue == null) { +                        statusCode = 0 +                    } else if (statusValue.startsWith("alpha")) { +                        statusCode = 1 +                    } else if (statusValue.startsWith("beta")) { +                        statusCode = 2 +                    } else if (statusValue.startsWith("rc")) { +                        statusCode = 3 +                    } else if (statusValue.startsWith("stable")) { +                        statusCode = 4 +                    } else { +                        statusCode = 0 +                    } + +                    libraryVersionCode = statusCode +                } +                  if (map.containsKey("patch")) { -                    libraryVersionCode = Integer.parseInt(map["patch"]) +                    libraryVersionCode += Integer.parseInt(map["patch"]) * 10                  }                  if (map.containsKey("minor")) { -                    libraryVersionCode += (Integer.parseInt(map["minor"]) * 100) +                    libraryVersionCode += (Integer.parseInt(map["minor"]) * 1000)                  }                  if (map.containsKey("major")) { -                    libraryVersionCode += (Integer.parseInt(map["major"]) * 10000) +                    libraryVersionCode += (Integer.parseInt(map["major"]) * 100000)                  }              } catch (NumberFormatException ignore) {                  libraryVersionCode = 1 diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index da30bd3a95..5a91e5ce32 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -28,16 +28,21 @@ allprojects {  }  ext { -    supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"] -    supportedTargetsMap = [release: "release", dev: "debug", debug: "release_debug"] +    supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"]      supportedFlavors = ["editor", "template"] +    supportedFlavorsBuildTypes = [ +        // The editor can't be used with target=release as debugging tools are then not +        // included, and it would crash on errors instead of reporting them. +        "editor": ["dev", "debug"], +        "template": ["dev", "debug", "release"] +    ]      // Used by gradle to specify which architecture to build for by default when running      // `./gradlew build` (this command is usually used by Android Studio).      // If building manually on the command line, it's recommended to use the      // `./gradlew generateGodotTemplates` build command instead after running the `scons` command(s).      // The {selectedAbis} values must be from the {supportedAbis} values. -    selectedAbis = ["arm64v8"] +    selectedAbis = ["arm64"]  }  def rootDir = "../../.." @@ -88,7 +93,7 @@ task copyDebugAARToAppModule(type: Copy) {      dependsOn ':lib:assembleTemplateDebug'      from('lib/build/outputs/aar')      into('app/libs/debug') -    include('godot-lib.debug.aar') +    include('godot-lib.template_debug.aar')  }  /** @@ -99,7 +104,7 @@ task copyDebugAARToBin(type: Copy) {      dependsOn ':lib:assembleTemplateDebug'      from('lib/build/outputs/aar')      into(binDir) -    include('godot-lib.debug.aar') +    include('godot-lib.template_debug.aar')  }  /** @@ -110,7 +115,7 @@ task copyDevAARToAppModule(type: Copy) {      dependsOn ':lib:assembleTemplateDev'      from('lib/build/outputs/aar')      into('app/libs/dev') -    include('godot-lib.dev.aar') +    include('godot-lib.template_debug.dev.aar')  }  /** @@ -121,7 +126,7 @@ task copyDevAARToBin(type: Copy) {      dependsOn ':lib:assembleTemplateDev'      from('lib/build/outputs/aar')      into(binDir) -    include('godot-lib.dev.aar') +    include('godot-lib.template_debug.dev.aar')  }  /** @@ -132,7 +137,7 @@ task copyReleaseAARToAppModule(type: Copy) {      dependsOn ':lib:assembleTemplateRelease'      from('lib/build/outputs/aar')      into('app/libs/release') -    include('godot-lib.release.aar') +    include('godot-lib.template_release.aar')  }  /** @@ -143,7 +148,7 @@ task copyReleaseAARToBin(type: Copy) {      dependsOn ':lib:assembleTemplateRelease'      from('lib/build/outputs/aar')      into(binDir) -    include('godot-lib.release.aar') +    include('godot-lib.template_release.aar')  }  /** @@ -168,13 +173,8 @@ def templateExcludedBuildTask() {      if (!isAndroidStudio()) {          logger.lifecycle("Excluding Android studio build tasks")          for (String flavor : supportedFlavors) { -            for (String buildType : supportedTargetsMap.keySet()) { -                if (buildType == "release" && flavor == "editor") { -                    // The editor can't be used with target=release as debugging tools are then not -                    // included, and it would crash on errors instead of reporting them. -                    continue -                } - +            String[] supportedBuildTypes = supportedFlavorsBuildTypes[flavor] +            for (String buildType : supportedBuildTypes) {                  for (String abi : selectedAbis) {                      excludedTasks += ":lib:" + getSconsTaskName(flavor, buildType, abi)                  } @@ -188,7 +188,7 @@ def templateBuildTasks() {      def tasks = []      // Only build the apks and aar files for which we have native shared libraries. -    for (String target : supportedTargetsMap.keySet()) { +    for (String target : supportedFlavorsBuildTypes["template"]) {          File targetLibs = new File("lib/libs/" + target)          if (targetLibs != null              && targetLibs.isDirectory() @@ -240,12 +240,7 @@ task generateGodotEditor {      def tasks = [] -    for (String target : supportedTargetsMap.keySet()) { -        if (target == "release") { -            // The editor can't be used with target=release as debugging tools are then not -            // included, and it would crash on errors instead of reporting them. -            continue -        } +    for (String target : supportedFlavorsBuildTypes["editor"]) {          File targetLibs = new File("lib/libs/tools/" + target)          if (targetLibs != null              && targetLibs.isDirectory() @@ -281,6 +276,11 @@ task generateDevTemplate {      finalizedBy 'zipCustomBuild'  } +task clean(type: Delete) { +    dependsOn 'cleanGodotEditor' +    dependsOn 'cleanGodotTemplates' +} +  /**   * Clean the generated editor artifacts.   */ @@ -297,8 +297,6 @@ task cleanGodotEditor(type: Delete) {      // Delete the Godot editor apks in the Godot bin directory      delete("$binDir/android_editor.apk")      delete("$binDir/android_editor_dev.apk") - -    finalizedBy getTasksByName("clean", true)  }  /** @@ -322,9 +320,12 @@ task cleanGodotTemplates(type: Delete) {      delete("$binDir/android_dev.apk")      delete("$binDir/android_release.apk")      delete("$binDir/android_source.zip") +    delete("$binDir/godot-lib.template_debug.aar") +    delete("$binDir/godot-lib.template_debug.dev.aar") +    delete("$binDir/godot-lib.template_release.aar") + +    // Cover deletion for the libs using the previous naming scheme      delete("$binDir/godot-lib.debug.aar")      delete("$binDir/godot-lib.dev.aar")      delete("$binDir/godot-lib.release.aar") - -    finalizedBy getTasksByName("clean", true)  } diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle index 729966ee69..9152492e9d 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -12,6 +12,25 @@ dependencies {      implementation "androidx.window:window:1.0.0"  } +ext { +    // Build number added as a suffix to the version code, and incremented for each build/upload to +    // the Google Play store. +    // This should be reset on each stable release of Godot. +    editorBuildNumber = 0 +    // Value by which the Godot version code should be offset by to make room for the build number +    editorBuildNumberOffset = 100 +} + +def generateVersionCode() { +    int libraryVersionCode = getGodotLibraryVersionCode() +    return (libraryVersionCode * editorBuildNumberOffset) + editorBuildNumber +} + +def generateVersionName() { +    String libraryVersionName = getGodotLibraryVersionName() +    return libraryVersionName + ".$editorBuildNumber" +} +  android {      compileSdkVersion versions.compileSdk      buildToolsVersion versions.buildTools @@ -20,8 +39,8 @@ android {      defaultConfig {          // The 'applicationId' suffix allows to install Godot 3.x(v3) and 4.x(v4) on the same device          applicationId "org.godotengine.editor.v4" -        versionCode getGodotLibraryVersionCode() -        versionName getGodotLibraryVersionName() +        versionCode generateVersionCode() +        versionName generateVersionName()          minSdkVersion versions.minSdk          targetSdkVersion versions.targetSdk diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml index abf506a83c..80ef10b6a4 100644 --- a/platform/android/java/editor/src/main/AndroidManifest.xml +++ b/platform/android/java/editor/src/main/AndroidManifest.xml @@ -7,11 +7,11 @@      <supports-screens          android:largeScreens="true"          android:normalScreens="true" -        android:smallScreens="true" +        android:smallScreens="false"          android:xlargeScreens="true" />      <uses-feature -        android:glEsVersion="0x00020000" +        android:glEsVersion="0x00030000"          android:required="true" />      <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" @@ -27,6 +27,7 @@          android:icon="@mipmap/icon"          android:label="@string/godot_editor_name_string"          tools:ignore="GoogleAppIndexingWarning" +        android:theme="@style/GodotEditorTheme"          android:requestLegacyExternalStorage="true">          <activity @@ -35,7 +36,6 @@              android:launchMode="singleTask"              android:screenOrientation="userLandscape"              android:exported="true" -            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"              android:process=":GodotProjectManager">              <layout android:defaultHeight="@dimen/editor_default_window_height" @@ -53,8 +53,7 @@              android:process=":GodotEditor"              android:launchMode="singleTask"              android:screenOrientation="userLandscape" -            android:exported="false" -            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"> +            android:exported="false">              <layout android:defaultHeight="@dimen/editor_default_window_height"                  android:defaultWidth="@dimen/editor_default_window_width" />          </activity> @@ -66,8 +65,7 @@              android:process=":GodotGame"              android:launchMode="singleTask"              android:exported="false" -            android:screenOrientation="userLandscape" -            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"> +            android:screenOrientation="userLandscape">              <layout android:defaultHeight="@dimen/editor_default_window_height"                  android:defaultWidth="@dimen/editor_default_window_width" />          </activity> 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 740f3f48d3..46a334ef38 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 @@ -37,10 +37,12 @@ import android.os.Build  import android.os.Bundle  import android.os.Debug  import android.os.Environment +import android.util.Log  import android.widget.Toast  import androidx.window.layout.WindowMetricsCalculator  import org.godotengine.godot.FullScreenGodotApp  import org.godotengine.godot.utils.PermissionsUtil +import org.godotengine.godot.utils.ProcessPhoenix  import java.util.*  import kotlin.math.min @@ -56,12 +58,17 @@ import kotlin.math.min  open class GodotEditor : FullScreenGodotApp() {  	companion object { +		private val TAG = GodotEditor::class.java.simpleName +  		private const val WAIT_FOR_DEBUGGER = false  		private const val COMMAND_LINE_PARAMS = "command_line_params"  		private const val EDITOR_ARG = "--editor" +		private const val EDITOR_ARG_SHORT = "-e" +  		private const val PROJECT_MANAGER_ARG = "--project-manager" +		private const val PROJECT_MANAGER_ARG_SHORT = "-p"  	}  	private val commandLineParams = ArrayList<String>() @@ -77,6 +84,12 @@ open class GodotEditor : FullScreenGodotApp() {  		}  		super.onCreate(savedInstanceState) + +		// Enable long press, panning and scaling gestures +		godotFragment?.renderView?.inputHandler?.apply { +			enableLongPress(enableLongPressGestures()) +			enablePanningAndScalingGestures(enablePanAndScaleGestures()) +		}  	}  	private fun updateCommandLineParams(args: Array<String>?) { @@ -99,13 +112,13 @@ open class GodotEditor : FullScreenGodotApp() {  			Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && (isInMultiWindowMode || isLargeScreen)  		for (arg in args) { -			if (EDITOR_ARG == arg) { +			if (EDITOR_ARG == arg || EDITOR_ARG_SHORT == arg) {  				targetClass = GodotEditor::class.java  				launchAdjacent = false  				break  			} -			if (PROJECT_MANAGER_ARG == arg) { +			if (PROJECT_MANAGER_ARG == arg || PROJECT_MANAGER_ARG_SHORT == arg) {  				targetClass = GodotProjectManager::class.java  				launchAdjacent = false  				break @@ -119,7 +132,13 @@ open class GodotEditor : FullScreenGodotApp() {  		if (launchAdjacent) {  			newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)  		} -		startActivity(newInstance) +		if (targetClass == javaClass) { +			Log.d(TAG, "Restarting $targetClass") +			ProcessPhoenix.triggerRebirth(this, newInstance) +		} else { +			Log.d(TAG, "Starting $targetClass") +			startActivity(newInstance) +		}  	}  	// Get the screen's density scale @@ -148,6 +167,16 @@ open class GodotEditor : FullScreenGodotApp() {  	 */  	protected open fun overrideOrientationRequest() = true +	/** +	 * Enable long press gestures for the Godot Android editor. +	 */ +	protected open fun enableLongPressGestures() = true + +	/** +	 * Enable pan and scale gestures for the Godot Android editor. +	 */ +	protected open fun enablePanAndScaleGestures() = true +  	override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {  		super.onActivityResult(requestCode, resultCode, data)  		// Check if we got the MANAGE_EXTERNAL_STORAGE permission 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 783095f93a..b9536a7066 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 @@ -35,4 +35,8 @@ package org.godotengine.editor   */  class GodotGame : GodotEditor() {  	override fun overrideOrientationRequest() = false + +	override fun enableLongPressGestures() = false + +	override fun enablePanAndScaleGestures() = false  } diff --git a/platform/android/java/editor/src/main/res/values/themes.xml b/platform/android/java/editor/src/main/res/values/themes.xml new file mode 100644 index 0000000000..fda04d6dc7 --- /dev/null +++ b/platform/android/java/editor/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> +	<style name="GodotEditorTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen"> +	</style> +</resources> diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties index ffed3a254e..41dfb87909 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@  distributionBase=GRADLE_USER_HOME  distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip  zipStoreBase=GRADLE_USER_HOME  zipStorePath=wrapper/dists diff --git a/platform/android/java/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml index 79b5aadf2a..1f77e2fc34 100644 --- a/platform/android/java/lib/AndroidManifest.xml +++ b/platform/android/java/lib/AndroidManifest.xml @@ -4,9 +4,6 @@      android:versionCode="1"      android:versionName="1.0"> -    <!-- Should match the mindSdk and targetSdk values in platform/android/java/app/config.gradle --> -    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="32" /> -      <application>          <!-- Records the version of the Godot library --> diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 6b82326a27..841656a240 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -100,25 +100,34 @@ android {              throw new GradleException("Invalid product flavor: $flavorName")          } -        boolean toolsFlag = flavorName == "editor" -          def buildType = variant.buildType.name -        if (buildType == null || buildType == "" || !supportedTargetsMap.containsKey(buildType)) { +        if (buildType == null || buildType == "" || !supportedFlavorsBuildTypes[flavorName].contains(buildType)) {              throw new GradleException("Invalid build type: $buildType")          } -        def sconsTarget = supportedTargetsMap[buildType] -        if (sconsTarget == null || sconsTarget == "") { -            throw new GradleException("Invalid scons target: $sconsTarget") +        boolean devBuild = buildType == "dev" + +        def sconsTarget = flavorName +        if (sconsTarget == "template") { +            switch (buildType) { +                case "release": +                    sconsTarget += "_release" +                    break +                case "debug": +                case "dev": +                default: +                    sconsTarget += "_debug" +                    break; +            }          }          // Update the name of the generated library -        def outputSuffix = "${buildType}.aar" -        if (toolsFlag) { -            outputSuffix = "tools.$outputSuffix" +        def outputSuffix = "${sconsTarget}" +        if (devBuild) { +            outputSuffix = "${outputSuffix}.dev"          }          variant.outputs.all { output -> -            output.outputFileName = "godot-lib.${outputSuffix}" +            output.outputFileName = "godot-lib.${outputSuffix}.aar"          }          // Find scons' executable path @@ -159,7 +168,7 @@ android {              def taskName = getSconsTaskName(flavorName, buildType, selectedAbi)              tasks.create(name: taskName, type: Exec) {                  executable sconsExecutableFile.absolutePath -                args "--directory=${pathToRootDir}", "platform=android", "tools=${toolsFlag}", "target=${sconsTarget}", "android_arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors() +                args "--directory=${pathToRootDir}", "platform=android", "dev_mode=${devBuild}", "dev_build=${devBuild}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors()              }              // Schedule the tasks so the generated libs are present before the aar file is packaged. @@ -167,11 +176,10 @@ android {          }      } -    // TODO: Enable when issues with AGP 7.1+ are resolved (https://github.com/GodotVR/godot_openxr/issues/187). -//    publishing { -//        singleVariant("templateRelease") { -//            withSourcesJar() -//            withJavadocJar() -//        } -//    } +    publishing { +        singleVariant("templateRelease") { +            withSourcesJar() +            withJavadocJar() +        } +    }  } diff --git a/platform/android/java/lib/res/values/strings.xml b/platform/android/java/lib/res/values/strings.xml index 010006b81e..f5a4ab1071 100644 --- a/platform/android/java/lib/res/values/strings.xml +++ b/platform/android/java/lib/res/values/strings.xml @@ -12,6 +12,8 @@      <string name="text_button_resume">Resume Download</string>      <string name="text_button_cancel">Cancel</string>      <string name="text_button_cancel_verify">Cancel Verification</string> +    <string name="text_error_title">Error!</string> +    <string name="error_engine_setup_message">Unable to setup the Godot Engine! Aborting…</string>      <!-- APK Expansion Strings --> diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 28e689e63a..3487e5019c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -57,6 +57,7 @@ import android.content.SharedPreferences.Editor;  import android.content.pm.ConfigurationInfo;  import android.content.pm.PackageManager;  import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources;  import android.graphics.Point;  import android.graphics.Rect;  import android.hardware.Sensor; @@ -69,6 +70,7 @@ import android.os.Environment;  import android.os.Messenger;  import android.os.VibrationEffect;  import android.os.Vibrator; +import android.util.Log;  import android.view.Display;  import android.view.LayoutInflater;  import android.view.Surface; @@ -85,6 +87,8 @@ import android.widget.TextView;  import androidx.annotation.CallSuper;  import androidx.annotation.Keep;  import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes;  import androidx.fragment.app.Fragment;  import com.google.android.vending.expansion.downloader.DownloadProgressInfo; @@ -105,6 +109,8 @@ import java.util.List;  import java.util.Locale;  public class Godot extends Fragment implements SensorEventListener, IDownloaderClient { +	private static final String TAG = Godot.class.getSimpleName(); +  	private IStub mDownloaderClientStub;  	private TextView mStatusText;  	private TextView mProgressFraction; @@ -169,6 +175,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  	public GodotIO io;  	public GodotNetUtils netUtils;  	public GodotTTS tts; +	DirectoryAccessHandler directoryAccessHandler;  	public interface ResultCallback {  		void callback(int requestCode, int resultCode, Intent data); @@ -250,7 +257,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  	 * Used by the native code (java_godot_lib_jni.cpp) to complete initialization of the GLSurfaceView view and renderer.  	 */  	@Keep -	private void onVideoInit() { +	private boolean onVideoInit() {  		final Activity activity = getActivity();  		containerLayout = new FrameLayout(activity);  		containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); @@ -262,13 +269,17 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  		// ...add to FrameLayout  		containerLayout.addView(editText); -		GodotLib.setup(command_line); +		if (!GodotLib.setup(command_line)) { +			Log.e(TAG, "Unable to setup the Godot engine! Aborting..."); +			alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit); +			return false; +		} -		final String videoDriver = GodotLib.getGlobal("rendering/driver/driver_name"); -		if (videoDriver.equals("vulkan")) { -			mRenderView = new GodotVulkanRenderView(activity, this); -		} else { +		final String renderer = GodotLib.getGlobal("rendering/renderer/rendering_method"); +		if (renderer.equals("gl_compatibility")) {  			mRenderView = new GodotGLRenderView(activity, this, xrMode, use_debug_opengl); +		} else { +			mRenderView = new GodotVulkanRenderView(activity, this);  		}  		View view = mRenderView.getView(); @@ -289,7 +300,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  			for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {  				plugin.onRegisterPluginWithGodotNative();  			} -			setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); +			setKeepScreenOn(Boolean.parseBoolean(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on")));  		});  		// Include the returned non-null views in the Godot view hierarchy. @@ -303,6 +314,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  				}  			}  		} +		return true;  	}  	public void setKeepScreenOn(final boolean p_enabled) { @@ -344,13 +356,27 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  	}  	public void alert(final String message, final String title) { +		alert(message, title, null); +	} + +	private void alert(@StringRes int messageResId, @StringRes int titleResId, @Nullable Runnable okCallback) { +		Resources res = getResources(); +		alert(res.getString(messageResId), res.getString(titleResId), okCallback); +	} + +	private void alert(final String message, final String title, @Nullable Runnable okCallback) {  		final Activity activity = getActivity();  		runOnUiThread(() -> {  			AlertDialog.Builder builder = new AlertDialog.Builder(activity);  			builder.setMessage(message).setTitle(title);  			builder.setPositiveButton(  					"OK", -					(dialog, id) -> dialog.cancel()); +					(dialog, id) -> { +						if (okCallback != null) { +							okCallback.run(); +						} +						dialog.cancel(); +					});  			AlertDialog dialog = builder.create();  			dialog.show();  		}); @@ -463,7 +489,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  		netUtils = new GodotNetUtils(activity);  		tts = new GodotTTS(activity);  		Context context = getContext(); -		DirectoryAccessHandler directoryAccessHandler = new DirectoryAccessHandler(context); +		directoryAccessHandler = new DirectoryAccessHandler(context);  		FileAccessHandler fileAccessHandler = new FileAccessHandler(context);  		mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);  		mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); @@ -471,7 +497,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  		mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);  		mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); -		GodotLib.initialize(activity, +		godot_initialized = GodotLib.initialize(activity,  				this,  				activity.getAssets(),  				io, @@ -482,8 +508,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  				tts);  		result_callback = null; - -		godot_initialized = true;  	}  	@Override @@ -1023,7 +1047,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  	}  	@Keep -	private GodotRenderView getRenderView() { // used by native side to get renderView +	public GodotRenderView getRenderView() { // used by native side to get renderView  		return mRenderView;  	} diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index 08da1b1832..f8d937521b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -31,7 +31,6 @@  package org.godotengine.godot;  import org.godotengine.godot.gl.GLSurfaceView;  import org.godotengine.godot.gl.GodotRenderer; -import org.godotengine.godot.input.GodotGestureHandler;  import org.godotengine.godot.input.GodotInputHandler;  import org.godotengine.godot.utils.GLUtils;  import org.godotengine.godot.xr.XRMode; @@ -44,9 +43,13 @@ import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser;  import android.annotation.SuppressLint;  import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory;  import android.graphics.PixelFormat;  import android.os.Build; -import android.view.GestureDetector; +import android.text.TextUtils; +import android.util.SparseArray;  import android.view.KeyEvent;  import android.view.MotionEvent;  import android.view.PointerIcon; @@ -54,6 +57,8 @@ import android.view.SurfaceView;  import androidx.annotation.Keep; +import java.io.InputStream; +  /**   * A simple GLSurfaceView sub-class that demonstrate how to perform   * OpenGL ES 2.0 rendering into a GL Surface. Note the following important @@ -63,7 +68,7 @@ import androidx.annotation.Keep;   *   See ContextFactory class definition below.   *   * - The class must use a custom EGLConfigChooser to be able to select - *   an EGLConfig that supports 2.0. This is done by providing a config + *   an EGLConfig that supports 3.0. This is done by providing a config   *   specification to eglChooseConfig() that has the attribute   *   EGL10.ELG_RENDERABLE_TYPE containing the EGL_OPENGL_ES2_BIT flag   *   set. See ConfigChooser class definition below. @@ -75,9 +80,8 @@ import androidx.annotation.Keep;  public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {  	private final Godot godot;  	private final GodotInputHandler inputHandler; -	private final GestureDetector detector;  	private final GodotRenderer godotRenderer; -	private PointerIcon pointerIcon; +	private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();  	public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_debug_opengl) {  		super(context); @@ -85,10 +89,9 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView  		this.godot = godot;  		this.inputHandler = new GodotInputHandler(this); -		this.detector = new GestureDetector(context, new GodotGestureHandler(this));  		this.godotRenderer = new GodotRenderer();  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { -			pointerIcon = PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT); +			setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));  		}  		init(xrMode, false);  	} @@ -132,7 +135,6 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView  	@Override  	public boolean onTouchEvent(MotionEvent event) {  		super.onTouchEvent(event); -		this.detector.onTouchEvent(event);  		return inputHandler.onTouchEvent(event);  	} @@ -156,19 +158,77 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView  		return inputHandler.onGenericMotionEvent(event);  	} +	@Override +	public void onPointerCaptureChange(boolean hasCapture) { +		super.onPointerCaptureChange(hasCapture); +		inputHandler.onPointerCaptureChange(hasCapture); +	} + +	@Override +	public void requestPointerCapture() { +		super.requestPointerCapture(); +		inputHandler.onPointerCaptureChange(true); +	} + +	@Override +	public void releasePointerCapture() { +		super.releasePointerCapture(); +		inputHandler.onPointerCaptureChange(false); +	} + +	/** +	 * Used to configure the PointerIcon for the given type. +	 * +	 * Called from JNI +	 */ +	@Keep +	@Override +	public void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY) { +		if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { +			try { +				Bitmap bitmap = null; +				if (!TextUtils.isEmpty(imagePath)) { +					if (godot.directoryAccessHandler.filesystemFileExists(imagePath)) { +						// Try to load the bitmap from the file system +						bitmap = BitmapFactory.decodeFile(imagePath); +					} else if (godot.directoryAccessHandler.assetsFileExists(imagePath)) { +						// Try to load the bitmap from the assets directory +						AssetManager am = getContext().getAssets(); +						InputStream imageInputStream = am.open(imagePath); +						bitmap = BitmapFactory.decodeStream(imageInputStream); +					} +				} + +				PointerIcon customPointerIcon = PointerIcon.create(bitmap, hotSpotX, hotSpotY); +				customPointerIcons.put(pointerType, customPointerIcon); +			} catch (Exception e) { +				// Reset the custom pointer icon +				customPointerIcons.delete(pointerType); +			} +		} +	} +  	/**  	 * called from JNI to change pointer icon  	 */  	@Keep +	@Override  	public void setPointerIcon(int pointerType) {  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { -			pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); +			PointerIcon pointerIcon = customPointerIcons.get(pointerType); +			if (pointerIcon == null) { +				pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); +			} +			setPointerIcon(pointerIcon);  		}  	}  	@Override  	public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) { -		return pointerIcon; +		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { +			return getPointerIcon(); +		} +		return super.onResolvePointerIcon(me, pointerIndex);  	}  	private void init(XRMode xrMode, boolean translucent) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index f855fc6cf6..33896ecb95 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -54,7 +54,7 @@ public class GodotLib {  	/**  	 * Invoked on the main thread to initialize Godot native layer.  	 */ -	public static native void initialize(Activity activity, +	public static native boolean initialize(Activity activity,  			Godot p_instance,  			AssetManager p_asset_manager,  			GodotIO godotIO, @@ -74,7 +74,7 @@ public class GodotLib {  	 * Invoked on the GL thread to complete setup for the Godot native layer logic.  	 * @param p_cmdline Command line arguments used to configure Godot native layer components.  	 */ -	public static native void setup(String[] p_cmdline); +	public static native boolean setup(String[] p_cmdline);  	/**  	 * Invoked on the GL thread when the underlying Android surface has changed size. @@ -92,7 +92,7 @@ public class GodotLib {  	public static native void newcontext(Surface p_surface);  	/** -	 * Forward {@link Activity#onBackPressed()} event from the main thread to the GL thread. +	 * Forward {@link Activity#onBackPressed()} event.  	 */  	public static native void back(); @@ -108,63 +108,60 @@ public class GodotLib {  	public static native void ttsCallback(int event, int id, int pos);  	/** -	 * Forward touch events from the main thread to the GL thread. +	 * Forward touch events.  	 */ -	public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions); -	public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions, int buttonsMask); -	public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions, int buttonsMask, float verticalFactor, float horizontalFactor); +	public static native void dispatchTouchEvent(int event, int pointer, int pointerCount, float[] positions, boolean doubleTap);  	/** -	 * Forward hover events from the main thread to the GL thread. +	 * Dispatch mouse events  	 */ -	public static native void hover(int type, float x, float y); +	public static native void dispatchMouseEvent(int event, int buttonMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative); -	/** -	 * Forward double_tap events from the main thread to the GL thread. -	 */ -	public static native void doubleTap(int buttonMask, int x, int y); +	public static native void magnify(float x, float y, float factor); + +	public static native void pan(float x, float y, float deltaX, float deltaY);  	/** -	 * Forward accelerometer sensor events from the main thread to the GL thread. +	 * Forward accelerometer sensor events.  	 * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent)  	 */  	public static native void accelerometer(float x, float y, float z);  	/** -	 * Forward gravity sensor events from the main thread to the GL thread. +	 * Forward gravity sensor events.  	 * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent)  	 */  	public static native void gravity(float x, float y, float z);  	/** -	 * Forward magnetometer sensor events from the main thread to the GL thread. +	 * Forward magnetometer sensor events.  	 * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent)  	 */  	public static native void magnetometer(float x, float y, float z);  	/** -	 * Forward gyroscope sensor events from the main thread to the GL thread. +	 * Forward gyroscope sensor events.  	 * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent)  	 */  	public static native void gyroscope(float x, float y, float z);  	/** -	 * Forward regular key events from the main thread to the GL thread. +	 * Forward regular key events.  	 */  	public static native void key(int p_keycode, int p_physical_keycode, int p_unicode, boolean p_pressed);  	/** -	 * Forward game device's key events from the main thread to the GL thread. +	 * Forward game device's key events.  	 */  	public static native void joybutton(int p_device, int p_but, boolean p_pressed);  	/** -	 * Forward joystick devices axis motion events from the main thread to the GL thread. +	 * Forward joystick devices axis motion events.  	 */  	public static native void joyaxis(int p_device, int p_axis, float p_value);  	/** -	 * Forward joystick devices hat motion events from the main thread to the GL thread. +	 * Forward joystick devices hat motion events.  	 */  	public static native void joyhat(int p_device, int p_hat_x, int p_hat_y); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java index cb63fd885f..ab74ba037d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -48,5 +48,7 @@ public interface GodotRenderView {  	GodotInputHandler getInputHandler(); +	void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY); +  	void setPointerIcon(int pointerType);  } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index c386a2d2eb..56bc7f9e76 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -30,15 +30,18 @@  package org.godotengine.godot; -import org.godotengine.godot.input.GodotGestureHandler;  import org.godotengine.godot.input.GodotInputHandler;  import org.godotengine.godot.vulkan.VkRenderer;  import org.godotengine.godot.vulkan.VkSurfaceView;  import android.annotation.SuppressLint;  import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory;  import android.os.Build; -import android.view.GestureDetector; +import android.text.TextUtils; +import android.util.SparseArray;  import android.view.KeyEvent;  import android.view.MotionEvent;  import android.view.PointerIcon; @@ -46,22 +49,22 @@ import android.view.SurfaceView;  import androidx.annotation.Keep; +import java.io.InputStream; +  public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {  	private final Godot godot;  	private final GodotInputHandler mInputHandler; -	private final GestureDetector mGestureDetector;  	private final VkRenderer mRenderer; -	private PointerIcon pointerIcon; +	private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();  	public GodotVulkanRenderView(Context context, Godot godot) {  		super(context);  		this.godot = godot;  		mInputHandler = new GodotInputHandler(this); -		mGestureDetector = new GestureDetector(context, new GodotGestureHandler(this));  		mRenderer = new VkRenderer();  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { -			pointerIcon = PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT); +			setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));  		}  		setFocusableInTouchMode(true);  		startRenderer(mRenderer); @@ -106,7 +109,6 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV  	@Override  	public boolean onTouchEvent(MotionEvent event) {  		super.onTouchEvent(event); -		mGestureDetector.onTouchEvent(event);  		return mInputHandler.onTouchEvent(event);  	} @@ -130,19 +132,77 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV  		return mInputHandler.onGenericMotionEvent(event);  	} +	@Override +	public void requestPointerCapture() { +		super.requestPointerCapture(); +		mInputHandler.onPointerCaptureChange(true); +	} + +	@Override +	public void releasePointerCapture() { +		super.releasePointerCapture(); +		mInputHandler.onPointerCaptureChange(false); +	} + +	@Override +	public void onPointerCaptureChange(boolean hasCapture) { +		super.onPointerCaptureChange(hasCapture); +		mInputHandler.onPointerCaptureChange(hasCapture); +	} + +	/** +	 * Used to configure the PointerIcon for the given type. +	 * +	 * Called from JNI +	 */ +	@Keep +	@Override +	public void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY) { +		if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { +			try { +				Bitmap bitmap = null; +				if (!TextUtils.isEmpty(imagePath)) { +					if (godot.directoryAccessHandler.filesystemFileExists(imagePath)) { +						// Try to load the bitmap from the file system +						bitmap = BitmapFactory.decodeFile(imagePath); +					} else if (godot.directoryAccessHandler.assetsFileExists(imagePath)) { +						// Try to load the bitmap from the assets directory +						AssetManager am = getContext().getAssets(); +						InputStream imageInputStream = am.open(imagePath); +						bitmap = BitmapFactory.decodeStream(imageInputStream); +					} +				} + +				PointerIcon customPointerIcon = PointerIcon.create(bitmap, hotSpotX, hotSpotY); +				customPointerIcons.put(pointerType, customPointerIcon); +			} catch (Exception e) { +				// Reset the custom pointer icon +				customPointerIcons.delete(pointerType); +			} +		} +	} +  	/**  	 * called from JNI to change pointer icon  	 */  	@Keep +	@Override  	public void setPointerIcon(int pointerType) {  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { -			pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); +			PointerIcon pointerIcon = customPointerIcons.get(pointerType); +			if (pointerIcon == null) { +				pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); +			} +			setPointerIcon(pointerIcon);  		}  	}  	@Override  	public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) { -		return pointerIcon; +		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { +			return getPointerIcon(); +		} +		return super.onResolvePointerIcon(me, pointerIndex);  	}  	@Override 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 7925b54fc4..804dbaf165 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 @@ -127,7 +127,9 @@ public class GodotEditText extends EditText {  					edit.setText("");  					edit.append(text);  					if (msg.arg2 != -1) { -						edit.setSelection(msg.arg1, msg.arg2); +						int selectionStart = Math.min(msg.arg1, edit.length()); +						int selectionEnd = Math.min(msg.arg2, edit.length()); +						edit.setSelection(selectionStart, selectionEnd);  						edit.mInputWrapper.setSelection(true);  					} else {  						edit.mInputWrapper.setSelection(false); diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java deleted file mode 100644 index 778efa914a..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java +++ /dev/null @@ -1,87 +0,0 @@ -/*************************************************************************/ -/*  GodotGestureHandler.java                                             */ -/*************************************************************************/ -/*                       This file is part of:                           */ -/*                           GODOT ENGINE                                */ -/*                      https://godotengine.org                          */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */ -/*                                                                       */ -/* 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.input; - -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.GodotRenderView; - -import android.view.GestureDetector; -import android.view.MotionEvent; - -/** - * Handles gesture input related events for the {@link GodotRenderView} view. - * https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener - */ -public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener { -	private final GodotRenderView mRenderView; - -	public GodotGestureHandler(GodotRenderView godotView) { -		mRenderView = godotView; -	} - -	private void queueEvent(Runnable task) { -		mRenderView.queueOnRenderThread(task); -	} - -	@Override -	public boolean onDown(MotionEvent event) { -		super.onDown(event); -		//Log.i("GodotGesture", "onDown"); -		return true; -	} - -	@Override -	public boolean onSingleTapConfirmed(MotionEvent event) { -		super.onSingleTapConfirmed(event); -		return true; -	} - -	@Override -	public void onLongPress(MotionEvent event) { -		//Log.i("GodotGesture", "onLongPress"); -	} - -	@Override -	public boolean onDoubleTap(MotionEvent event) { -		//Log.i("GodotGesture", "onDoubleTap"); -		final int x = Math.round(event.getX()); -		final int y = Math.round(event.getY()); -		final int buttonMask = event.getButtonState(); -		GodotLib.doubleTap(buttonMask, x, y); -		return true; -	} - -	@Override -	public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { -		//Log.i("GodotGesture", "onFling"); -		return true; -	} -} 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 new file mode 100644 index 0000000000..cde8d7cdae --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt @@ -0,0 +1,265 @@ +/*************************************************************************/ +/*  GodotGestureHandler.kt                                               */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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.input + +import android.os.Build +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.InputDevice +import android.view.MotionEvent +import android.view.ScaleGestureDetector +import android.view.ScaleGestureDetector.OnScaleGestureListener +import org.godotengine.godot.GodotLib + +/** + * Handles regular and scale gesture input related events for the [GodotView] view. + * + * @See https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener + * @See https://developer.android.com/reference/android/view/ScaleGestureDetector.OnScaleGestureListener + */ +internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureListener { + +	companion object { +		private val TAG = GodotGestureHandler::class.java.simpleName +	} + +	/** +	 * Enable pan and scale gestures +	 */ +	var panningAndScalingEnabled = false + +	private var nextDownIsDoubleTap = false +	private var dragInProgress = false +	private var scaleInProgress = false +	private var contextClickInProgress = false +	private var pointerCaptureInProgress = false + +	override fun onDown(event: MotionEvent): Boolean { +		GodotInputHandler.handleMotionEvent(event.source, MotionEvent.ACTION_DOWN, event.buttonState, event.x, event.y, nextDownIsDoubleTap) +		nextDownIsDoubleTap = false +		return true +	} + +	override fun onSingleTapUp(event: MotionEvent): Boolean { +		GodotInputHandler.handleMotionEvent(event) +		return true +	} + +	override fun onLongPress(event: MotionEvent) { +		contextClickRouter(event) +	} + +	private fun contextClickRouter(event: MotionEvent) { +		if (scaleInProgress || nextDownIsDoubleTap) { +			return +		} + +		// Cancel the previous down event +		GodotInputHandler.handleMotionEvent( +			event.source, +			MotionEvent.ACTION_CANCEL, +			event.buttonState, +			event.x, +			event.y +		) + +		// Turn a context click into a single tap right mouse button click. +		GodotInputHandler.handleMouseEvent( +			MotionEvent.ACTION_DOWN, +			MotionEvent.BUTTON_SECONDARY, +			event.x, +			event.y +		) +		contextClickInProgress = true +	} + +	fun onPointerCaptureChange(hasCapture: Boolean) { +		if (pointerCaptureInProgress == hasCapture) { +			return +		} + +		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 +			) +		} +		pointerCaptureInProgress = hasCapture +	} + +	fun onMotionEvent(event: MotionEvent): Boolean { +		return when (event.actionMasked) { +			MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_BUTTON_RELEASE -> { +				onActionUp(event) +			} +			MotionEvent.ACTION_MOVE -> { +				onActionMove(event) +			} +			else -> false +		} +	} + +	private fun onActionUp(event: MotionEvent): Boolean { +		if (event.actionMasked == MotionEvent.ACTION_CANCEL && pointerCaptureInProgress) { +			// Don't dispatch the ACTION_CANCEL while a capture is in progress +			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 +				) +			} else { +				GodotInputHandler.handleTouchEvent(event) +			} +			pointerCaptureInProgress = false +			dragInProgress = false +			contextClickInProgress = false +			return true +		} + +		return false +	} + +	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 +			) +			return true +		} +		return false +	} + +	override fun onDoubleTapEvent(event: MotionEvent): Boolean { +		if (event.actionMasked == MotionEvent.ACTION_UP) { +			nextDownIsDoubleTap = false +			GodotInputHandler.handleMotionEvent(event) +		} +		return true +	} + +	override fun onDoubleTap(event: MotionEvent): Boolean { +		nextDownIsDoubleTap = true +		return true +	} + +	override fun onScroll( +		originEvent: MotionEvent, +		terminusEvent: MotionEvent, +		distanceX: Float, +		distanceY: Float +	): Boolean { +		if (scaleInProgress) { +			if (dragInProgress) { +				// Cancel the drag +				GodotInputHandler.handleMotionEvent( +					originEvent.source, +					MotionEvent.ACTION_CANCEL, +					originEvent.buttonState, +					originEvent.x, +					originEvent.y +				) +				dragInProgress = false +			} +			return true +		} + +		dragInProgress = true + +		val x = terminusEvent.x +		val y = terminusEvent.y +		if (terminusEvent.pointerCount >= 2 && panningAndScalingEnabled && !pointerCaptureInProgress) { +			GodotLib.pan(x, y, distanceX / 5f, distanceY / 5f) +		} else { +			GodotInputHandler.handleMotionEvent(terminusEvent) +		} +		return true +	} + +	override fun onScale(detector: ScaleGestureDetector?): Boolean { +		if (detector == null || !panningAndScalingEnabled || pointerCaptureInProgress) { +			return false +		} +		GodotLib.magnify( +			detector.focusX, +			detector.focusY, +			detector.scaleFactor +		) +		return true +	} + +	override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean { +		if (detector == null || !panningAndScalingEnabled || pointerCaptureInProgress) { +			return false +		} +		scaleInProgress = true +		return true +	} + +	override fun onScaleEnd(detector: ScaleGestureDetector?) { +		scaleInProgress = false +	} +} 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 da15b2490c..0ba86e4316 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 @@ -41,13 +41,13 @@ import android.os.Build;  import android.util.Log;  import android.util.SparseArray;  import android.util.SparseIntArray; +import android.view.GestureDetector;  import android.view.InputDevice; -import android.view.InputDevice.MotionRange;  import android.view.KeyEvent;  import android.view.MotionEvent; +import android.view.ScaleGestureDetector;  import java.util.Collections; -import java.util.Comparator;  import java.util.HashSet;  import java.util.Set; @@ -55,21 +55,49 @@ import java.util.Set;   * Handles input related events for the {@link GodotRenderView} view.   */  public class GodotInputHandler implements InputManager.InputDeviceListener { -	private final GodotRenderView mRenderView; -	private final InputManager mInputManager; - -	private final String tag = this.getClass().getSimpleName(); +	private static final String TAG = GodotInputHandler.class.getSimpleName();  	private final SparseIntArray mJoystickIds = new SparseIntArray(4);  	private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<>(4); +	private final GodotRenderView mRenderView; +	private final InputManager mInputManager; +	private final GestureDetector gestureDetector; +	private final ScaleGestureDetector scaleGestureDetector; +	private final GodotGestureHandler godotGestureHandler; +  	public GodotInputHandler(GodotRenderView godotView) { +		final Context context = godotView.getView().getContext();  		mRenderView = godotView; -		mInputManager = (InputManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_SERVICE); +		mInputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE);  		mInputManager.registerInputDeviceListener(this, null); + +		this.godotGestureHandler = new GodotGestureHandler(); +		this.gestureDetector = new GestureDetector(context, godotGestureHandler); +		this.gestureDetector.setIsLongpressEnabled(false); +		this.scaleGestureDetector = new ScaleGestureDetector(context, godotGestureHandler); +		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { +			this.scaleGestureDetector.setStylusScaleEnabled(true); +		}  	} -	private boolean isKeyEvent_GameDevice(int source) { +	/** +	 * Enable long press events. This is false by default. +	 */ +	public void enableLongPress(boolean enable) { +		this.gestureDetector.setIsLongpressEnabled(enable); +	} + +	/** +	 * Enable multi-fingers pan & scale gestures. This is false by default. +	 * +	 * Note: This may interfere with multi-touch handling / support. +	 */ +	public void enablePanningAndScalingGestures(boolean enable) { +		this.godotGestureHandler.setPanningAndScalingEnabled(enable); +	} + +	private boolean isKeyEventGameDevice(int source) {  		// Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD)  		if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD))  			return false; @@ -77,6 +105,10 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  		return (source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD;  	} +	public void onPointerCaptureChange(boolean hasCapture) { +		godotGestureHandler.onPointerCaptureChange(hasCapture); +	} +  	public boolean onKeyUp(final int keyCode, KeyEvent event) {  		if (keyCode == KeyEvent.KEYCODE_BACK) {  			return true; @@ -87,7 +119,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  		}  		int source = event.getSource(); -		if (isKeyEvent_GameDevice(source)) { +		if (isKeyEventGameDevice(source)) {  			// Check if the device exists  			final int deviceId = event.getDeviceId();  			if (mJoystickIds.indexOfKey(deviceId) >= 0) { @@ -121,11 +153,10 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  		}  		int source = event.getSource(); -		//Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD)));  		final int deviceId = event.getDeviceId();  		// Check if source is a game device and that the device is a registered gamepad -		if (isKeyEvent_GameDevice(source)) { +		if (isKeyEventGameDevice(source)) {  			if (event.getRepeatCount() > 0) // ignore key echo  				return true; @@ -145,47 +176,41 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  	}  	public boolean onTouchEvent(final MotionEvent event) { -		// Mouse drag (mouse pressed and move) doesn't fire onGenericMotionEvent so this is needed -		if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { -			if (event.getAction() != MotionEvent.ACTION_MOVE) { -				// we return true because every time a mouse event is fired, the event is already handled -				// in onGenericMotionEvent, so by touch event we can say that the event is also handled -				return true; -			} -			return handleMouseEvent(event); +		this.scaleGestureDetector.onTouchEvent(event); +		if (this.gestureDetector.onTouchEvent(event)) { +			// The gesture detector has handled the event. +			return true;  		} -		final int evcount = event.getPointerCount(); -		if (evcount == 0) +		if (godotGestureHandler.onMotionEvent(event)) { +			// The gesture handler has handled the event.  			return true; +		} -		if (mRenderView != null) { -			final float[] arr = new float[event.getPointerCount() * 3]; // pointerId1, x1, y1, pointerId2, etc... +		// Drag events are handled by the [GodotGestureHandler] +		if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { +			return true; +		} -			for (int i = 0; i < event.getPointerCount(); i++) { -				arr[i * 3 + 0] = event.getPointerId(i); -				arr[i * 3 + 1] = event.getX(i); -				arr[i * 3 + 2] = event.getY(i); -			} -			final int action = event.getActionMasked(); -			final int pointer_idx = event.getPointerId(event.getActionIndex()); - -			switch (action) { -				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.touch(event.getSource(), action, pointer_idx, evcount, arr); -				} break; -			} +		if (isMouseEvent(event)) { +			return handleMouseEvent(event);  		} -		return true; + +		return handleTouchEvent(event);  	}  	public boolean onGenericMotionEvent(MotionEvent event) { -		if (event.isFromSource(InputDevice.SOURCE_JOYSTICK) && event.getAction() == MotionEvent.ACTION_MOVE) { +		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; +		} + +		if (event.isFromSource(InputDevice.SOURCE_JOYSTICK) && event.getActionMasked() == MotionEvent.ACTION_MOVE) {  			// Check if the device exists  			final int deviceId = event.getDeviceId();  			if (mJoystickIds.indexOfKey(deviceId) >= 0) { @@ -198,15 +223,14 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  				for (int i = 0; i < joystick.axes.size(); i++) {  					final int axis = joystick.axes.get(i);  					final float value = event.getAxisValue(axis); -					/** -					 * As all axes are polled for each event, only fire an axis event if the value has actually changed. -					 * Prevents flooding Godot with repeated events. +					/* +					  As all axes are polled for each event, only fire an axis event if the value has actually changed. +					  Prevents flooding Godot with repeated events.  					 */  					if (joystick.axesValues.indexOfKey(axis) < 0 || (float)joystick.axesValues.get(axis) != value) {  						// save value to prevent repeats  						joystick.axesValues.put(axis, value); -						final int godotAxisIdx = i; -						GodotLib.joyaxis(godotJoyId, godotAxisIdx, value); +						GodotLib.joyaxis(godotJoyId, i, value);  					}  				} @@ -221,17 +245,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  				}  				return true;  			} -		} else if (event.isFromSource(InputDevice.SOURCE_STYLUS)) { -			final float x = event.getX(); -			final float y = event.getY(); -			final int type = event.getAction(); -			GodotLib.hover(type, x, y); -			return true; - -		} else if (event.isFromSource(InputDevice.SOURCE_MOUSE) || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) { -			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { -				return handleMouseEvent(event); -			} +		} else { +			return handleMouseEvent(event);  		}  		return false; @@ -243,7 +258,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  		for (int deviceId : deviceIds) {  			InputDevice device = mInputManager.getInputDevice(deviceId);  			if (DEBUG) { -				Log.v("GodotInputHandler", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName())); +				Log.v(TAG, String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName()));  			}  			onInputDeviceAdded(deviceId);  		} @@ -288,13 +303,12 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  		joystick.name = device.getName();  		//Helps with creating new joypad mappings. -		Log.i(tag, "=== New Input Device: " + joystick.name); +		Log.i(TAG, "=== New Input Device: " + joystick.name);  		Set<Integer> already = new HashSet<>();  		for (InputDevice.MotionRange range : device.getMotionRanges()) {  			boolean isJoystick = range.isFromSource(InputDevice.SOURCE_JOYSTICK);  			boolean isGamepad = range.isFromSource(InputDevice.SOURCE_GAMEPAD); -			//Log.i(tag, "axis: "+range.getAxis()+ ", isJoystick: "+isJoystick+", isGamepad: "+isGamepad);  			if (!isJoystick && !isGamepad) {  				continue;  			} @@ -306,14 +320,14 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  					already.add(axis);  					joystick.axes.add(axis);  				} else { -					Log.w(tag, " - DUPLICATE AXIS VALUE IN LIST: " + axis); +					Log.w(TAG, " - DUPLICATE AXIS VALUE IN LIST: " + axis);  				}  			}  		}  		Collections.sort(joystick.axes);  		for (int idx = 0; idx < joystick.axes.size(); idx++) {  			//Helps with creating new joypad mappings. -			Log.i(tag, " - Mapping Android axis " + joystick.axes.get(idx) + " to Godot axis " + idx); +			Log.i(TAG, " - Mapping Android axis " + joystick.axes.get(idx) + " to Godot axis " + idx);  		}  		mJoysticksDevices.put(deviceId, joystick); @@ -338,13 +352,6 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  		onInputDeviceAdded(deviceId);  	} -	private static class RangeComparator implements Comparator<MotionRange> { -		@Override -		public int compare(MotionRange arg0, MotionRange arg1) { -			return arg0.getAxis() - arg1.getAxis(); -		} -	} -  	public static int getGodotButton(int keyCode) {  		int button;  		switch (keyCode) { @@ -410,39 +417,116 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {  		return button;  	} -	private boolean handleMouseEvent(final MotionEvent event) { -		switch (event.getActionMasked()) { +	static boolean isMouseEvent(MotionEvent event) { +		return isMouseEvent(event.getSource()); +	} + +	private static boolean isMouseEvent(int eventSource) { +		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; +	} + +	static boolean handleMotionEvent(final MotionEvent event) { +		if (isMouseEvent(event)) { +			return handleMouseEvent(event); +		} + +		return handleTouchEvent(event); +	} + +	static boolean handleMotionEvent(int eventSource, int eventAction, int buttonsMask, float x, float y) { +		return handleMotionEvent(eventSource, eventAction, buttonsMask, x, y, 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(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); +		} + +		return handleTouchEvent(eventAction, x, y, doubleTap); +	} + +	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(); + +		final float verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL); +		final float horizontalFactor = event.getAxisValue(MotionEvent.AXIS_HSCROLL); +		boolean sourceMouseRelative = false; +		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); +	} + +	static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y) { +		return handleMouseEvent(eventAction, buttonsMask, x, y, 0, 0, false, false); +	} + +	static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative) { +		// We don't handle ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE events as they typically +		// follow ACTION_DOWN and ACTION_UP events. As such, handling them would result in duplicate +		// stream of events to the engine. +		switch (eventAction) { +			case MotionEvent.ACTION_CANCEL: +			case MotionEvent.ACTION_UP: +				// Zero-up the button state +				buttonsMask = 0; +				// FALL THROUGH +			case MotionEvent.ACTION_DOWN:  			case MotionEvent.ACTION_HOVER_ENTER: +			case MotionEvent.ACTION_HOVER_EXIT:  			case MotionEvent.ACTION_HOVER_MOVE: -			case MotionEvent.ACTION_HOVER_EXIT: { -				final float x = event.getX(); -				final float y = event.getY(); -				final int type = event.getAction(); -				GodotLib.hover(type, x, y); -				return true; -			} -			case MotionEvent.ACTION_BUTTON_PRESS: -			case MotionEvent.ACTION_BUTTON_RELEASE: -			case MotionEvent.ACTION_MOVE: { -				final float x = event.getX(); -				final float y = event.getY(); -				final int buttonsMask = event.getButtonState(); -				final int action = event.getAction(); -				GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask); -				return true; -			} +			case MotionEvent.ACTION_MOVE:  			case MotionEvent.ACTION_SCROLL: { -				final float x = event.getX(); -				final float y = event.getY(); -				final int buttonsMask = event.getButtonState(); -				final int action = event.getAction(); -				final float verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL); -				final float horizontalFactor = event.getAxisValue(MotionEvent.AXIS_HSCROLL); -				GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask, verticalFactor, horizontalFactor); +				GodotLib.dispatchMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative); +				return true;  			} +		} +		return false; +	} + +	static boolean handleTouchEvent(final MotionEvent event) { +		final int pointerCount = event.getPointerCount(); +		if (pointerCount == 0) { +			return true; +		} + +		final float[] positions = new float[pointerCount * 3]; // pointerId1, x1, y1, 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); +		} +		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) {  			case MotionEvent.ACTION_DOWN: -			case MotionEvent.ACTION_UP: { -				// we can safely ignore these cases because they are always come beside ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE +			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);  				return true;  			}  		} diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java index c959b5f28c..01ad5ee415 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java @@ -122,7 +122,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene  	@Override  	public boolean onEditorAction(final TextView pTextView, final int pActionID, final KeyEvent pKeyEvent) { -		if (mEdit == pTextView && isFullScreenEdit()) { +		if (mEdit == pTextView && isFullScreenEdit() && pKeyEvent != null) {  			final String characters = pKeyEvent.getCharacters();  			for (int i = 0; i < characters.length(); i++) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt index c9282dd247..1a3576a6a9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt @@ -90,6 +90,11 @@ internal enum class StorageScope {  				return APP  			} +			var rootDir: String? = System.getenv("ANDROID_ROOT") +			if (rootDir != null && canonicalPathFile.startsWith(rootDir)) { +				return APP +			} +  			if (sharedDir != null && canonicalPathFile.startsWith(sharedDir)) {  				if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {  					// Before R, apps had access to shared storage so long as they have the right diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt index fedcf4843f..6bc317415f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt @@ -79,6 +79,9 @@ class DirectoryAccessHandler(context: Context) {  	private val assetsDirAccess = AssetsDirectoryAccess(context)  	private val fileSystemDirAccess = FilesystemDirectoryAccess(context) +	fun assetsFileExists(assetsPath: String) = assetsDirAccess.fileExists(assetsPath) +	fun filesystemFileExists(path: String) = fileSystemDirAccess.fileExists(path) +  	private fun hasDirId(accessType: AccessType, dirId: Int): Boolean {  		return when (accessType) {  			ACCESS_RESOURCES -> assetsDirAccess.hasDirId(dirId) 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 bb5042fa09..8ca5bcaa6e 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 @@ -71,11 +71,11 @@ import javax.microedition.khronos.opengles.GL10;   * - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin class   * extending {@link GodotPlugin}.   * - * A plugin can also define and provide c/c++ gdnative libraries and nativescripts for the target + * A plugin can also define and provide c/c++ gdextension libraries and nativescripts for the target   * app/game to leverage. - * The shared library for the gdnative library will be automatically bundled by the aar build + * The shared library for the gdextension library will be automatically bundled by the aar build   * system. - * Godot '*.gdnlib' and '*.gdns' resource files must however be manually defined in the project + * Godot '*.gdextension' resource files must however be manually defined in the project   * 'assets' directory. The recommended path for these resources in the 'assets' directory should be:   * 'godot/plugin/v1/[PluginName]/'   */ @@ -112,7 +112,7 @@ public abstract class GodotPlugin {  	public final void onRegisterPluginWithGodotNative() {  		registeredSignals.putAll(  				registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals(), -						getPluginGDNativeLibrariesPaths())); +						getPluginGDExtensionLibrariesPaths()));  	}  	/** @@ -124,7 +124,7 @@ public abstract class GodotPlugin {  			GodotPluginInfoProvider pluginInfoProvider) {  		registerPluginWithGodotNative(pluginObject, pluginInfoProvider.getPluginName(),  				Collections.emptyList(), pluginInfoProvider.getPluginSignals(), -				pluginInfoProvider.getPluginGDNativeLibrariesPaths()); +				pluginInfoProvider.getPluginGDExtensionLibrariesPaths());  		// Notify that registration is complete.  		pluginInfoProvider.onPluginRegistered(); @@ -132,7 +132,7 @@ public abstract class GodotPlugin {  	private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject,  			String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals, -			Set<String> pluginGDNativeLibrariesPaths) { +			Set<String> pluginGDExtensionLibrariesPaths) {  		nativeRegisterSingleton(pluginName, pluginObject);  		Set<Method> filteredMethods = new HashSet<>(); @@ -176,9 +176,9 @@ public abstract class GodotPlugin {  			registeredSignals.put(signalName, signalInfo);  		} -		// Get the list of gdnative libraries to register. -		if (!pluginGDNativeLibrariesPaths.isEmpty()) { -			nativeRegisterGDNativeLibraries(pluginGDNativeLibrariesPaths.toArray(new String[0])); +		// Get the list of gdextension libraries to register. +		if (!pluginGDExtensionLibrariesPaths.isEmpty()) { +			nativeRegisterGDExtensionLibraries(pluginGDExtensionLibrariesPaths.toArray(new String[0]));  		}  		return registeredSignals; @@ -304,12 +304,12 @@ public abstract class GodotPlugin {  	}  	/** -	 * Returns the paths for the plugin's gdnative libraries. +	 * Returns the paths for the plugin's gdextension libraries.  	 * -	 * The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file. +	 * The paths must be relative to the 'assets' directory and point to a '*.gdextension' file.  	 */  	@NonNull -	protected Set<String> getPluginGDNativeLibrariesPaths() { +	protected Set<String> getPluginGDExtensionLibrariesPaths() {  		return Collections.emptySet();  	} @@ -420,10 +420,10 @@ public abstract class GodotPlugin {  	private static native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params);  	/** -	 * Used to register gdnative libraries bundled by the plugin. -	 * @param gdnlibPaths Paths to the libraries relative to the 'assets' directory. +	 * Used to register gdextension libraries bundled by the plugin. +	 * @param gdextensionPaths Paths to the libraries relative to the 'assets' directory.  	 */ -	private static native void nativeRegisterGDNativeLibraries(String[] gdnlibPaths); +	private static native void nativeRegisterGDExtensionLibraries(String[] gdextensionPaths);  	/**  	 * Used to complete registration of the {@link GodotPlugin} instance's methods. diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java index cfb84c3931..53b78aebfb 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java @@ -54,12 +54,12 @@ public interface GodotPluginInfoProvider {  	}  	/** -	 * Returns the paths for the plugin's gdnative libraries (if any). +	 * Returns the paths for the plugin's gdextension libraries (if any).  	 * -	 * The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file. +	 * The paths must be relative to the 'assets' directory and point to a '*.gdextension' file.  	 */  	@NonNull -	default Set<String> getPluginGDNativeLibrariesPaths() { +	default Set<String> getPluginGDExtensionLibrariesPaths() {  		return Collections.emptySet();  	} diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java index 445238b1c2..9834fdfb88 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java @@ -45,20 +45,18 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser {  	private int[] mValue = new int[1]; -	// FIXME: Add support for Vulkan. - -	/* This EGL config specification is used to specify 2.0 rendering. +	/* This EGL config specification is used to specify 3.0 rendering.  	 * We use a minimum size of 4 bits for red/green/blue, but will  	 * perform actual matching in chooseConfig() below.  	 */  	private static int EGL_OPENGL_ES2_BIT = 4; -	private static int[] s_configAttribs2 = { +	private static int[] s_configAttribs = {  		EGL10.EGL_RED_SIZE, 4,  		EGL10.EGL_GREEN_SIZE, 4,  		EGL10.EGL_BLUE_SIZE, 4, -		//  EGL10.EGL_DEPTH_SIZE,     16, +		// EGL10.EGL_DEPTH_SIZE,     16,  		// EGL10.EGL_STENCIL_SIZE,   EGL10.EGL_DONT_CARE, -		EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, +		EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT  		EGL10.EGL_NONE  	}; @@ -75,7 +73,7 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser {  		/* Get the number of minimally matching EGL configurations  		 */  		int[] num_config = new int[1]; -		egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config); +		egl.eglChooseConfig(display, s_configAttribs, null, 0, num_config);  		int numConfigs = num_config[0]; @@ -86,7 +84,7 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser {  		/* Allocate then read the array of minimally matching EGL configs  		 */  		EGLConfig[] configs = new EGLConfig[numConfigs]; -		egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config); +		egl.eglChooseConfig(display, s_configAttribs, configs, numConfigs, num_config);  		if (GLUtils.DEBUG) {  			GLUtils.printConfigs(egl, display, configs); diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java index 5d62723170..8fb86bf6d0 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java @@ -52,17 +52,16 @@ public class RegularContextFactory implements GLSurfaceView.EGLContextFactory {  	private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;  	public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { -		// FIXME: Add support for Vulkan. -		Log.w(TAG, "creating OpenGL ES 2.0 context :"); +		Log.w(TAG, "creating OpenGL ES 3.0 context :");  		GLUtils.checkEglError(TAG, "Before eglCreateContext", egl);  		EGLContext context;  		if (GLUtils.use_debug_opengl) { -			int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; -			context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list2); +			int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; +			context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);  		} else { -			int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; -			context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list2); +			int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE }; +			context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);  		}  		GLUtils.checkEglError(TAG, "After eglCreateContext", egl);  		return context; diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt index 711f7cd502..e1534c7685 100644 --- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt +++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt @@ -17,4 +17,4 @@ target_include_directories(${PROJECT_NAME}          SYSTEM PUBLIC          ${GODOT_ROOT_DIR}) -add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED) +add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED) |