/**************************************************************************/ /* GodotPluginRegistry.java */ /**************************************************************************/ /* This file is part of: */ /* REDOT ENGINE */ /* https://redotengine.org */ /**************************************************************************/ /* Copyright (c) 2024-present Redot Engine contributors */ /* (see REDOT_AUTHORS.md) */ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ package org.godotengine.godot.plugin; import org.godotengine.godot.Godot; import android.app.Activity; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import androidx.annotation.Nullable; import java.lang.reflect.Constructor; import java.util.Collection; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * Registry used to load and access the registered Godot Android plugins. */ public final class GodotPluginRegistry { private static final String TAG = GodotPluginRegistry.class.getSimpleName(); /** * 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.2+ */ private static final String GODOT_PLUGIN_V2_NAME_PREFIX = "org.godotengine.plugin.v2."; private static GodotPluginRegistry instance; private final ConcurrentHashMap registry; private GodotPluginRegistry() { registry = new ConcurrentHashMap<>(); } /** * Retrieve the plugin tied to the given plugin name. * @param pluginName Name of the plugin * @return {@link GodotPlugin} handle if it exists, null otherwise. */ @Nullable public GodotPlugin getPlugin(String pluginName) { return registry.get(pluginName); } /** * Retrieve the full set of loaded plugins. */ public Collection getAllPlugins() { if (registry.isEmpty()) { return Collections.emptyList(); } return registry.values(); } /** * Parse the manifest file and load all included Godot Android plugins. *

* A plugin manifest entry is a '' tag setup as described in the {@link GodotPlugin} * 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, Set runtimePlugins) { if (instance == null) { instance = new GodotPluginRegistry(); instance.loadPlugins(godot, runtimePlugins); } return instance; } /** * Return the plugin registry if it's initialized. * Throws a {@link IllegalStateException} exception if not. * * @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) { throw new IllegalStateException("Plugin registry hasn't been initialized."); } return instance; } private void loadPlugins(Godot godot, Set 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 .getPackageManager() .getApplicationInfo(activity.getPackageName(), PackageManager.GET_META_DATA); Bundle metaData = appInfo.metaData; if (metaData == null || metaData.isEmpty()) { return; } 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)) { 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, "Redot v1 plugin are deprecated in Redot 4.2 and higher: " + pluginName); } if (!TextUtils.isEmpty(pluginName)) { Log.i(TAG, "Initializing Redot plugin " + pluginName); // Retrieve the plugin class full name. String pluginHandleClassFullName = metaData.getString(metaDataName); if (!TextUtils.isEmpty(pluginHandleClassFullName)) { try { // Attempt to create the plugin init class via reflection. @SuppressWarnings("unchecked") Class pluginClass = (Class)Class .forName(pluginHandleClassFullName); Constructor pluginConstructor = pluginClass .getConstructor(Godot.class); GodotPlugin pluginHandle = pluginConstructor.newInstance(godot); // Load the plugin initializer into the registry using the plugin name as key. if (!pluginName.equals(pluginHandle.getPluginName())) { Log.w(TAG, "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 Redot plugin " + pluginHandle.getPluginName()); } catch (Exception e) { Log.w(TAG, "Unable to load Redot plugin " + pluginName, e); } } else { Log.w(TAG, "Invalid plugin loader class for " + pluginName); } } } } catch (Exception e) { Log.e(TAG, "Unable load Redot Android plugins from the manifest file.", e); } } }