diff options
Diffstat (limited to 'platform/android/java/lib/src')
11 files changed, 239 insertions, 53 deletions
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 e115494cfd..857f239e96 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -35,6 +35,7 @@ import android.app.Activity import android.app.AlertDialog import android.content.* import android.content.pm.PackageManager +import android.content.res.Configuration import android.content.res.Resources import android.graphics.Rect import android.hardware.Sensor @@ -83,7 +84,7 @@ class Godot(private val context: Context) : SensorEventListener { } private val pluginRegistry: GodotPluginRegistry by lazy { - GodotPluginRegistry.initializePluginRegistry(this) + GodotPluginRegistry.getPluginRegistry() } private val mSensorManager: SensorManager by lazy { requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager @@ -189,7 +190,7 @@ class Godot(private val context: Context) : SensorEventListener { val activity = requireActivity() val window = activity.window window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) - GodotPluginRegistry.initializePluginRegistry(this) + GodotPluginRegistry.initializePluginRegistry(this, primaryHost.getHostPlugins(this)) if (io == null) { io = GodotIO(activity) } @@ -249,11 +250,7 @@ class Godot(private val context: Context) : SensorEventListener { } i++ } - if (newArgs.isEmpty()) { - commandLine = mutableListOf() - } else { - commandLine = newArgs - } + commandLine = if (newArgs.isEmpty()) { mutableListOf() } else { newArgs } if (useApkExpansion && mainPackMd5 != null && mainPackKey != null) { // Build the full path to the app's expansion files try { @@ -391,6 +388,7 @@ class Godot(private val context: Context) : SensorEventListener { // Fallback to openGl GodotGLRenderView(host, this, xrMode, useDebugOpengl) } + if (host == primaryHost) { renderView!!.startRenderer() } @@ -574,6 +572,19 @@ class Godot(private val context: Context) : SensorEventListener { * Invoked on the render thread when the Godot setup is complete. */ private fun onGodotSetupCompleted() { + Log.d(TAG, "OnGodotSetupCompleted") + + // 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")) + + runOnUiThread { + renderView?.inputHandler?.apply { + enableLongPress(longPressEnabled) + enablePanningAndScalingGestures(panScaleEnabled) + } + } + for (plugin in pluginRegistry.allPlugins) { plugin.onGodotSetupCompleted() } @@ -584,6 +595,8 @@ class Godot(private val context: Context) : SensorEventListener { * Invoked on the render thread when the Godot main loop has started. */ private fun onGodotMainLoopStarted() { + Log.d(TAG, "OnGodotMainLoopStarted") + for (plugin in pluginRegistry.allPlugins) { plugin.onGodotMainLoopStarted() } @@ -615,7 +628,7 @@ class Godot(private val context: Context) : SensorEventListener { private fun alert(message: String, title: String, okCallback: Runnable?) { val activity: Activity = getActivity() ?: return - runOnUiThread(Runnable { + runOnUiThread { val builder = AlertDialog.Builder(activity) builder.setMessage(message).setTitle(title) builder.setPositiveButton( @@ -626,7 +639,7 @@ class Godot(private val context: Context) : SensorEventListener { } val dialog = builder.create() dialog.show() - }) + } } /** @@ -684,9 +697,9 @@ class Godot(private val context: Context) : SensorEventListener { return false } - private fun setKeepScreenOn(p_enabled: Boolean) { + private fun setKeepScreenOn(enabled: Boolean) { runOnUiThread { - if (p_enabled) { + if (enabled) { getActivity()?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } else { getActivity()?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -694,6 +707,25 @@ class Godot(private val context: Context) : SensorEventListener { } } + /** + * Returns true if dark mode is supported, false otherwise. + */ + @Keep + private fun isDarkModeSupported(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + } + + /** + * Returns true if dark mode is supported and enabled, false otherwise. + */ + @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 + } + fun hasClipboard(): Boolean { return mClipboard.hasPrimaryClip() } @@ -815,9 +847,7 @@ class Godot(private val context: Context) : SensorEventListener { } } - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - // Do something here if sensor accuracy changes. - } + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} /** * Used by the native code (java_godot_wrapper.h) to vibrate the device. @@ -845,7 +875,7 @@ class Godot(private val context: Context) : SensorEventListener { private fun getCommandLine(): MutableList<String> { val original: MutableList<String> = parseCommandLine() val hostCommandLine = primaryHost?.commandLine - if (hostCommandLine != null && hostCommandLine.isNotEmpty()) { + if (!hostCommandLine.isNullOrEmpty()) { original.addAll(hostCommandLine) } return original @@ -908,6 +938,19 @@ class Godot(private val context: Context) : SensorEventListener { } /** + * Return true if the given feature is supported. + */ + @Keep + private fun hasFeature(feature: String): Boolean { + for (plugin in pluginRegistry.allPlugins) { + if (plugin.supportsFeature(feature)) { + return true + } + } + return false + } + + /** * Get the list of gdextension modules to register. */ @Keep diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt index 4636f753af..a60f6e997e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -30,12 +30,15 @@ package org.godotengine.godot +import android.Manifest import android.app.Activity import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle import android.util.Log import androidx.annotation.CallSuper import androidx.fragment.app.FragmentActivity +import org.godotengine.godot.utils.PermissionsUtil import org.godotengine.godot.utils.ProcessPhoenix /** @@ -62,6 +65,10 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { private set override fun onCreate(savedInstanceState: Bundle?) { + // We exclude certain permissions from the set we request at startup, as they'll be + // requested on demand based on use-cases. + PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) + super.onCreate(savedInstanceState) setContentView(R.layout.godot_app_layout) @@ -148,6 +155,14 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) godotFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults) + + if (requestCode == PermissionsUtil.REQUEST_ALL_PERMISSION_REQ_CODE) { + Log.d(TAG, "Received permissions request result..") + for (i in permissions.indices) { + val permissionGranted = grantResults[i] == PackageManager.PERMISSION_GRANTED + Log.d(TAG, "Permission ${permissions[i]} ${if (permissionGranted) { "granted"} else { "denied" }}") + } + } } override fun onBackPressed() { @@ -158,6 +173,10 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { return this } + override fun getGodot(): Godot? { + return godotFragment?.godot + } + /** * Used to initialize the Godot fragment instance in [onCreate]. */ 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 9a8b10ea3e..120e1722e5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -30,6 +30,7 @@ package org.godotengine.godot; +import org.godotengine.godot.plugin.GodotPlugin; import org.godotengine.godot.utils.BenchmarkUtils; import android.app.Activity; @@ -65,6 +66,7 @@ import com.google.android.vending.expansion.downloader.IStub; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Set; /** * Base fragment for Android apps intending to use Godot for part of the app's UI. @@ -122,6 +124,7 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH } public ResultCallback resultCallback; + @Override public Godot getGodot() { return godot; } @@ -426,4 +429,13 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH } return 0; } + + @Override + @CallSuper + public Set<GodotPlugin> getHostPlugins(Godot engine) { + if (parentHost != null) { + return parentHost.getHostPlugins(engine); + } + return Collections.emptySet(); + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index e5333085dd..1862b9fa9b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -30,39 +30,42 @@ package org.godotengine.godot; +import org.godotengine.godot.plugin.GodotPlugin; + import android.app.Activity; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} engine. */ public interface GodotHost { /** - * Provides a set of command line parameters to setup the engine. + * Provides a set of command line parameters to setup the {@link Godot} engine. */ default List<String> getCommandLine() { return Collections.emptyList(); } /** - * Invoked on the render thread when the Godot setup is complete. + * Invoked on the render thread when setup of the {@link Godot} engine is complete. */ default void onGodotSetupCompleted() {} /** - * Invoked on the render thread when the Godot main loop has started. + * Invoked on the render thread when the {@link Godot} engine main loop has started. */ default void onGodotMainLoopStarted() {} /** - * Invoked on the render thread to terminate the given Godot instance. + * Invoked on the render thread to terminate the given {@link Godot} engine instance. */ default void onGodotForceQuit(Godot instance) {} /** - * Invoked on the render thread to terminate the Godot instance with the given id. + * Invoked on the render thread to terminate the {@link Godot} engine instance with the given id. * @param godotInstanceId id of the Godot instance to terminate. See {@code onNewGodotInstanceRequested} * * @return true if successful, false otherwise. @@ -90,7 +93,19 @@ public interface GodotHost { } /** - * Provide access to the Activity hosting the Godot engine. + * Provide access to the Activity hosting the {@link Godot} engine. */ Activity getActivity(); + + /** + * Provide access to the hosted {@link Godot} engine. + */ + Godot getGodot(); + + /** + * Returns a set of {@link GodotPlugin} to be registered with the hosted {@link Godot} engine. + */ + default Set<GodotPlugin> getHostPlugins(Godot engine) { + return Collections.emptySet(); + } } 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 b9ecd6971d..fee50e93c2 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -114,7 +114,7 @@ public class GodotLib { /** * Dispatch mouse events */ - public static native void dispatchMouseEvent(int event, int buttonMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative); + public static native void dispatchMouseEvent(int event, int buttonMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY); public static native void magnify(float x, float y, float factor); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt index 68cd2c1358..795dc921c7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt @@ -8,8 +8,10 @@ import android.util.Log /** * Godot service responsible for hosting the Godot engine instance. + * + * Note: Still in development, so it's made private and inaccessible until completed. */ -class GodotService : Service() { +private class GodotService : Service() { companion object { private val TAG = GodotService::class.java.simpleName 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 a7064dfc1d..3070a8a207 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 @@ -33,6 +33,7 @@ package org.godotengine.godot.input; import org.godotengine.godot.*; import android.content.Context; +import android.content.res.Configuration; import android.os.Handler; import android.os.Message; import android.text.InputFilter; @@ -209,6 +210,13 @@ public class GodotEditText extends EditText { mRenderView.getView().requestFocus(); } + // When a hardware keyboard is connected, all key events come through so we can route them + // directly to the engine. + // This is not the case when using a soft keyboard, requiring extra processing from this class. + if (hasHardwareKeyboard()) { + return mRenderView.getInputHandler().onKeyDown(keyCode, keyEvent); + } + // pass event to godot in special cases if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyDown(keyCode, keyEvent)) { return true; @@ -219,6 +227,13 @@ public class GodotEditText extends EditText { @Override public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { + // When a hardware keyboard is connected, all key events come through so we can route them + // directly to the engine. + // This is not the case when using a soft keyboard, requiring extra processing from this class. + if (hasHardwareKeyboard()) { + return mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent); + } + if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent)) { return true; } else { @@ -235,10 +250,20 @@ public class GodotEditText extends EditText { isModifiedKey; } + boolean hasHardwareKeyboard() { + Configuration config = getResources().getConfiguration(); + return config.keyboard != Configuration.KEYBOARD_NOKEYS && + config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; + } + // =========================================================== // Methods // =========================================================== public void showKeyboard(String p_existing_text, VirtualKeyboardType p_type, int p_max_input_length, int p_cursor_start, int p_cursor_end) { + if (hasHardwareKeyboard()) { + return; + } + int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length; if (p_cursor_start == -1) { // cursor position not given this.mOriginText = p_existing_text; @@ -262,6 +287,10 @@ public class GodotEditText extends EditText { } public void hideKeyboard() { + if (hasHardwareKeyboard()) { + return; + } + final Message msg = new Message(); msg.what = HANDLER_CLOSE_IME_KEYBOARD; msg.obj = this; 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 1a25be0460..38c115ad7f 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 @@ -95,7 +95,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { /** * Enable multi-fingers pan & scale gestures. This is false by default. - * + * <p> * Note: This may interfere with multi-touch handling / support. */ public void enablePanningAndScalingGestures(boolean enable) { @@ -433,7 +433,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { } 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); + boolean mouseSource = ((eventSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) || ((eventSource & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mouseSource = mouseSource || ((eventSource & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE); } @@ -470,13 +470,27 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { final float y = event.getY(); final int buttonsMask = event.getButtonState(); + final float pressure = event.getPressure(); + + // Orientation is returned as a radian value between 0 to pi clockwise or 0 to -pi counterclockwise. + final float orientation = event.getOrientation(); + + // Tilt is zero is perpendicular to the screen and pi/2 is flat on the surface. + final float tilt = event.getAxisValue(MotionEvent.AXIS_TILT); + + float tiltMult = (float)Math.sin(tilt); + + // To be consistent with expected tilt. + final float tiltX = (float)-Math.sin(orientation) * tiltMult; + final float tiltY = (float)Math.cos(orientation) * tiltMult; + 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); + return handleMouseEvent(eventAction, buttonsMask, x, y, horizontalFactor, verticalFactor, false, sourceMouseRelative, pressure, tiltX, tiltY); } static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y) { @@ -484,6 +498,10 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { } static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative) { + return handleMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, 1, 0, 0); + } + + static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) { // Fix the buttonsMask switch (eventAction) { case MotionEvent.ACTION_CANCEL: @@ -511,7 +529,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { case MotionEvent.ACTION_HOVER_MOVE: case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_SCROLL: { - GodotLib.dispatchMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative); + GodotLib.dispatchMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY); return true; } } 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 b4f0f44df4..c0912ca4dc 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 @@ -61,12 +61,15 @@ import javax.microedition.khronos.opengles.GL10; * <p> * A Godot Android plugin is an Android library with the following requirements: * <p> - * - The library must have a 'compileOnly' dependency on the Godot Android library: `compileOnly "org.godotengine:godot:<godotLibVersion>"` + * - The plugin must have a dependency on the Godot Android library: `implementation "org.godotengine:godot:<godotLibVersion>"` * <p> - * - The library must include a <meta-data> tag in its Android manifest with the following format: + * - The plugin must include a <meta-data> tag in its Android manifest with the following format: * <meta-data android:name="org.godotengine.plugin.v2.[PluginName]" android:value="[plugin.init.ClassFullName]" /> + * <p> * Where: + * <p> * - 'PluginName' is the name of the plugin. + * <p> * - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin init class * extending {@link GodotPlugin}. * <p> @@ -83,6 +86,10 @@ public abstract class GodotPlugin { private final Godot godot; private final ConcurrentHashMap<String, SignalInfo> registeredSignals = new ConcurrentHashMap<>(); + /** + * Base constructor passing a {@link Godot} instance through which the plugin can access Godot's + * APIs and lifecycle events. + */ public GodotPlugin(Godot godot) { this.godot = godot; } @@ -109,11 +116,11 @@ public abstract class GodotPlugin { */ public final void onRegisterPluginWithGodotNative() { registeredSignals.putAll( - registerPluginWithGodotNative(this, getPluginName(), getPluginSignals())); + registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals())); } private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject, - String pluginName, Set<SignalInfo> pluginSignals) { + String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals) { nativeRegisterSingleton(pluginName, pluginObject); Set<Method> filteredMethods = new HashSet<>(); @@ -124,6 +131,14 @@ public abstract class GodotPlugin { // Check if the method is annotated with {@link UsedByGodot}. if (method.getAnnotation(UsedByGodot.class) != null) { filteredMethods.add(method); + } else { + // For backward compatibility, process the methods from the given <pluginMethods> argument. + for (String methodName : pluginMethods) { + if (methodName.equals(method.getName())) { + filteredMethods.add(method); + break; + } + } } } @@ -261,6 +276,17 @@ public abstract class GodotPlugin { public abstract String getPluginName(); /** + * Returns the list of methods to be exposed to Godot. + * + * @deprecated Use the {@link UsedByGodot} annotation instead. + */ + @NonNull + @Deprecated + public List<String> getPluginMethods() { + return Collections.emptyList(); + } + + /** * Returns the list of signals to be exposed to Godot. */ @NonNull @@ -290,6 +316,15 @@ public abstract class GodotPlugin { } /** + * Returns whether the plugin supports the given feature tag. + * + * @see <a href="https://docs.godotengine.org/en/stable/tutorials/export/feature_tags.html">Feature tags</a> + */ + public boolean supportsFeature(String featureTag) { + return false; + } + + /** * Runs the specified action on the UI thread. If the current thread is the UI * thread, then the action is executed immediately. If the current thread is * not the UI thread, the action is posted to the event queue of the UI thread. diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java index d81d996c42..711bca02e7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java @@ -42,8 +42,8 @@ import android.util.Log; import androidx.annotation.Nullable; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.Collection; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -53,20 +53,19 @@ public final class GodotPluginRegistry { private static final String TAG = GodotPluginRegistry.class.getSimpleName(); /** - * Prefix used for version 1 of the Godot plugin, compatible with Godot 3.x + * Prefix used for version 1 of the Godot plugin, mostly compatible with Godot 3.x */ private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1."; /** - * Prefix used for version 2 of the Godot plugin, compatible with Godot 4.x + * Prefix used for version 2 of the Godot plugin, compatible with Godot 4.2+ */ private static final String GODOT_PLUGIN_V2_NAME_PREFIX = "org.godotengine.plugin.v2."; private static GodotPluginRegistry instance; private final ConcurrentHashMap<String, GodotPlugin> registry; - private GodotPluginRegistry(Godot godot) { + private GodotPluginRegistry() { registry = new ConcurrentHashMap<>(); - loadPlugins(godot); } /** @@ -93,12 +92,14 @@ public final class GodotPluginRegistry { * documentation. * * @param godot Godot instance + * @param runtimePlugins Set of plugins provided at runtime for registration * @return A singleton instance of {@link GodotPluginRegistry}. This ensures that only one instance * of each Godot Android plugins is available at runtime. */ - public static GodotPluginRegistry initializePluginRegistry(Godot godot) { + public static GodotPluginRegistry initializePluginRegistry(Godot godot, Set<GodotPlugin> runtimePlugins) { if (instance == null) { - instance = new GodotPluginRegistry(godot); + instance = new GodotPluginRegistry(); + instance.loadPlugins(godot, runtimePlugins); } return instance; @@ -108,7 +109,7 @@ public final class GodotPluginRegistry { * Return the plugin registry if it's initialized. * Throws a {@link IllegalStateException} exception if not. * - * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot)} has not been called prior to calling this method. + * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot, Set)} has not been called prior to calling this method. */ public static GodotPluginRegistry getPluginRegistry() throws IllegalStateException { if (instance == null) { @@ -118,7 +119,16 @@ public final class GodotPluginRegistry { return instance; } - private void loadPlugins(Godot godot) { + private void loadPlugins(Godot godot, Set<GodotPlugin> runtimePlugins) { + // Register the runtime plugins + if (runtimePlugins != null && !runtimePlugins.isEmpty()) { + for (GodotPlugin plugin : runtimePlugins) { + Log.i(TAG, "Registering runtime plugin " + plugin.getPluginName()); + registry.put(plugin.getPluginName(), plugin); + } + } + + // Register the manifest plugins try { final Activity activity = godot.getActivity(); ApplicationInfo appInfo = activity @@ -130,12 +140,18 @@ public final class GodotPluginRegistry { return; } - int godotPluginV2NamePrefixLength = GODOT_PLUGIN_V2_NAME_PREFIX.length(); for (String metaDataName : metaData.keySet()) { // Parse the meta-data looking for entry with the Godot plugin name prefix. + String pluginName = null; if (metaDataName.startsWith(GODOT_PLUGIN_V2_NAME_PREFIX)) { - String pluginName = metaDataName.substring(godotPluginV2NamePrefixLength).trim(); - Log.i(TAG, "Initializing Godot v2 plugin " + pluginName); + pluginName = metaDataName.substring(GODOT_PLUGIN_V2_NAME_PREFIX.length()).trim(); + } else if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) { + pluginName = metaDataName.substring(GODOT_PLUGIN_V1_NAME_PREFIX.length()).trim(); + Log.w(TAG, "Godot v1 plugin are deprecated in Godot 4.2 and higher: " + pluginName); + } + + if (!TextUtils.isEmpty(pluginName)) { + Log.i(TAG, "Initializing Godot plugin " + pluginName); // Retrieve the plugin class full name. String pluginHandleClassFullName = metaData.getString(metaDataName); @@ -155,22 +171,17 @@ public final class GodotPluginRegistry { "Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName()); } registry.put(pluginName, pluginHandle); - Log.i(TAG, "Completed initialization for Godot v2 plugin " + pluginHandle.getPluginName()); - } catch (ClassNotFoundException | IllegalAccessException | - InstantiationException | NoSuchMethodException | - InvocationTargetException e) { - Log.w(TAG, "Unable to load Godot v2 plugin " + pluginName, e); + Log.i(TAG, "Completed initialization for Godot plugin " + pluginHandle.getPluginName()); + } catch (Exception e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); } } else { Log.w(TAG, "Invalid plugin loader class for " + pluginName); } - } else if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) { - String v1PluginName = metaDataName.substring(GODOT_PLUGIN_V1_NAME_PREFIX.length()).trim(); - Log.w(TAG, "Godot 4 does not support Godot 3 (v1) plugin: " + v1PluginName); } } - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Unable load Godot Android v2 plugins from the manifest file.", e); + } catch (Exception e) { + Log.e(TAG, "Unable load Godot Android plugins from the manifest file.", e); } } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java index 8353fc8dc6..9a82204467 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -160,6 +160,7 @@ public final class PermissionsUtil { try { if (manifestPermission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { + Log.d(TAG, "Requesting permission " + manifestPermission); try { Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); intent.setData(Uri.parse(String.format("package:%s", activity.getPackageName()))); @@ -173,6 +174,7 @@ public final class PermissionsUtil { PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission); int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel; if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Requesting permission " + manifestPermission); requestedPermissions.add(manifestPermission); } } |
