diff options
Diffstat (limited to 'platform/android/java')
10 files changed, 116 insertions, 9 deletions
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index f084c60209..7797f4bc9d 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -116,6 +116,14 @@ android { if (shouldNotStrip()) { doNotStrip '**/*.so' } + + jniLibs { + // Setting this to true causes AGP to package compressed native libraries when building the app + // For more background, see: + // - https://developer.android.com/build/releases/past-releases/agp-3-6-0-release-notes#extractNativeLibs + // - https://stackoverflow.com/a/44704840 + useLegacyPackaging shouldUseLegacyPackaging() + } } signingConfigs { diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 7224765f28..f2c4a5d1b6 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -361,3 +361,26 @@ ext.shouldSign = { -> ext.shouldNotStrip = { -> return isAndroidStudio() || project.hasProperty("doNotStrip") } + +/** + * Whether to use the legacy convention of compressing all .so files in the APK. + * + * For more background, see: + * - https://developer.android.com/build/releases/past-releases/agp-3-6-0-release-notes#extractNativeLibs + * - https://stackoverflow.com/a/44704840 + */ +ext.shouldUseLegacyPackaging = { -> + int minSdk = getExportMinSdkVersion() + if (minSdk < 23) { + // Enforce the default behavior for compatibility with device running api < 23 + return true + } + + String legacyPackagingFlag = project.hasProperty("compress_native_libraries") ? project.property("compress_native_libraries") : "" + if (legacyPackagingFlag != null && !legacyPackagingFlag.isEmpty()) { + return Boolean.parseBoolean(legacyPackagingFlag) + } + + // Default behavior for minSdk >= 23 + return false +} diff --git a/platform/android/java/app/gradle.properties b/platform/android/java/app/gradle.properties index d9f79b6818..2b6468c95e 100644 --- a/platform/android/java/app/gradle.properties +++ b/platform/android/java/app/gradle.properties @@ -23,3 +23,6 @@ org.gradle.warning.mode=all # Enable resource optimizations for release build. # NOTE: This is turned off for template release build in order to support the build legacy process. android.enableResourceOptimizations=true + +# Fix gradle build errors when the build path contains non-ASCII characters +android.overridePathCheck=true diff --git a/platform/android/java/gradle.properties b/platform/android/java/gradle.properties index 39a0dcda16..c8abb52614 100644 --- a/platform/android/java/gradle.properties +++ b/platform/android/java/gradle.properties @@ -26,3 +26,6 @@ org.gradle.warning.mode=all # Disable resource optimizations for template release build. # NOTE: This is turned on for Godot Editor's gradle builds in order to improve the release build. android.enableResourceOptimizations=false + +# Fix gradle build errors when the build path contains non-ASCII characters +android.overridePathCheck=true diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 61ae0cd58a..ed967b9660 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -101,6 +101,7 @@ android { } boolean devBuild = buildType == "dev" + boolean debugSymbols = devBuild || isAndroidStudio() boolean runTests = devBuild boolean productionBuild = !devBuild boolean storeRelease = buildType == "release" @@ -168,7 +169,7 @@ android { def taskName = getSconsTaskName(flavorName, buildType, selectedAbi) tasks.create(name: taskName, type: Exec) { executable sconsExecutableFile.absolutePath - args "--directory=${pathToRootDir}", "platform=android", "store_release=${storeRelease}", "production=${productionBuild}", "dev_mode=${devBuild}", "dev_build=${devBuild}", "tests=${runTests}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors() + args "--directory=${pathToRootDir}", "platform=android", "store_release=${storeRelease}", "production=${productionBuild}", "dev_mode=${devBuild}", "dev_build=${devBuild}", "debug_symbols=${debugSymbols}", "tests=${runTests}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors() } // Schedule the tasks so the generated libs are present before the aar file is packaged. 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 da86e67c7d..e2e77e7796 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -37,6 +37,7 @@ import android.content.* 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 @@ -149,6 +150,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 containerLayout: FrameLayout? = null var renderView: GodotRenderView? = null @@ -184,6 +186,8 @@ class Godot(private val context: Context) : SensorEventListener { return } + darkMode = context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + beginBenchmarkMeasure("Startup", "Godot::onCreate") try { this.primaryHost = primaryHost @@ -376,6 +380,8 @@ class Godot(private val context: Context) : SensorEventListener { ViewGroup.LayoutParams.MATCH_PARENT, activity.resources.getDimension(R.dimen.text_edit_height).toInt() ) + // Prevent GodotEditText from showing on splash screen on devices with Android 14 or newer. + editText.setBackgroundColor(Color.TRANSPARENT) // ...add to FrameLayout containerLayout?.addView(editText) renderView = if (usesVulkan()) { @@ -560,6 +566,17 @@ 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 + if (darkMode != newDarkMode) { + darkMode = newDarkMode + GodotLib.onNightModeChanged() + } + } + + /** * Activity result callback */ fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -596,11 +613,13 @@ 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")); runOnUiThread { renderView?.inputHandler?.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) + setRotaryInputAxis(rotaryInputAxis) } } @@ -731,7 +750,7 @@ class Godot(private val context: Context) : SensorEventListener { */ @Keep private fun isDarkModeSupported(): Boolean { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + return context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) != Configuration.UI_MODE_NIGHT_UNDEFINED } /** @@ -739,10 +758,7 @@ class Godot(private val context: Context) : SensorEventListener { */ @Keep private fun isDarkMode(): Boolean { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - return context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES - } - return false + return darkMode } fun hasClipboard(): Boolean { diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java index 643c9a658e..a323045e1b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -38,6 +38,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.os.Messenger; @@ -147,6 +148,13 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH @CallSuper @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + godot.onConfigurationChanged(newConfig); + } + + @CallSuper + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCallback != null) { 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 fee50e93c2..d0c3d4a687 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -220,6 +220,11 @@ public class GodotLib { public static native void requestPermissionResult(String p_permission, boolean p_result); /** + * Invoked on the theme light/dark mode change. + */ + public static native void onNightModeChanged(); + + /** * Invoked on the GL thread to configure the height of the virtual keyboard. */ public static native void setVirtualKeyboardHeight(int p_height); 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 3070a8a207..dc8a0e54bb 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 @@ -34,10 +34,13 @@ import org.godotengine.godot.*; import android.content.Context; import android.content.res.Configuration; +import android.os.Build; import android.os.Handler; import android.os.Message; import android.text.InputFilter; import android.text.InputType; +import android.text.TextUtils; +import android.text.method.DigitsKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.inputmethod.EditorInfo; @@ -45,6 +48,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import java.lang.ref.WeakReference; +import java.util.Locale; public class GodotEditText extends EditText { // =========================================================== @@ -137,6 +141,7 @@ public class GodotEditText extends EditText { } int inputType = InputType.TYPE_CLASS_TEXT; + String acceptCharacters = null; switch (edit.getKeyboardType()) { case KEYBOARD_TYPE_DEFAULT: inputType = InputType.TYPE_CLASS_TEXT; @@ -148,7 +153,8 @@ public class GodotEditText extends EditText { inputType = InputType.TYPE_CLASS_NUMBER; break; case KEYBOARD_TYPE_NUMBER_DECIMAL: - inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED; + inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL; + acceptCharacters = "0123456789,.- "; break; case KEYBOARD_TYPE_PHONE: inputType = InputType.TYPE_CLASS_PHONE; @@ -165,6 +171,14 @@ public class GodotEditText extends EditText { } edit.setInputType(inputType); + if (!TextUtils.isEmpty(acceptCharacters)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + edit.setKeyListener(DigitsKeyListener.getInstance(Locale.getDefault())); + } else { + edit.setKeyListener(DigitsKeyListener.getInstance(acceptCharacters)); + } + } + edit.mInputWrapper.setOriginText(text); edit.addTextChangedListener(edit.mInputWrapper); final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 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 38c115ad7f..fe971cf442 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 @@ -57,6 +57,9 @@ import java.util.Set; public class GodotInputHandler implements InputManager.InputDeviceListener { private static final String TAG = GodotInputHandler.class.getSimpleName(); + private static final int ROTARY_INPUT_VERTICAL_AXIS = 1; + private static final int ROTARY_INPUT_HORIZONTAL_AXIS = 0; + private final SparseIntArray mJoystickIds = new SparseIntArray(4); private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<>(4); @@ -71,6 +74,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { */ private int lastSeenToolType = MotionEvent.TOOL_TYPE_UNKNOWN; + private static int rotaryInputAxis = ROTARY_INPUT_VERTICAL_AXIS; + public GodotInputHandler(GodotRenderView godotView) { final Context context = godotView.getView().getContext(); mRenderView = godotView; @@ -102,6 +107,13 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { this.godotGestureHandler.setPanningAndScalingEnabled(enable); } + /** + * On Wear OS devices, sets which axis of the mouse wheel rotary input is mapped to. This is 1 (vertical axis) by default. + */ + public void setRotaryInputAxis(int axis) { + rotaryInputAxis = axis; + } + private boolean isKeyEventGameDevice(int source) { // Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD) if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD)) @@ -484,8 +496,22 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { final float tiltX = (float)-Math.sin(orientation) * tiltMult; final float tiltY = (float)Math.cos(orientation) * tiltMult; - final float verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL); - final float horizontalFactor = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + float verticalFactor = 0; + float horizontalFactor = 0; + + // If event came from RotaryEncoder (Bezel or Crown rotate event on Wear OS smart watches), + // convert it to mouse wheel event. + if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) { + if (rotaryInputAxis == ROTARY_INPUT_HORIZONTAL_AXIS) { + horizontalFactor = -event.getAxisValue(MotionEvent.AXIS_SCROLL); + } else { + // If rotaryInputAxis is not ROTARY_INPUT_HORIZONTAL_AXIS then use default ROTARY_INPUT_VERTICAL_AXIS axis. + verticalFactor = -event.getAxisValue(MotionEvent.AXIS_SCROLL); + } + } else { + verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + 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); |