1 /*************************************************************************/ 2 /* GodotPluginRegistry.java */ 3 /*************************************************************************/ 4 /* This file is part of: */ 5 /* GODOT ENGINE */ 6 /* https://godotengine.org */ 7 /*************************************************************************/ 8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ 9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ 10 /* */ 11 /* Permission is hereby granted, free of charge, to any person obtaining */ 12 /* a copy of this software and associated documentation files (the */ 13 /* "Software"), to deal in the Software without restriction, including */ 14 /* without limitation the rights to use, copy, modify, merge, publish, */ 15 /* distribute, sublicense, and/or sell copies of the Software, and to */ 16 /* permit persons to whom the Software is furnished to do so, subject to */ 17 /* the following conditions: */ 18 /* */ 19 /* The above copyright notice and this permission notice shall be */ 20 /* included in all copies or substantial portions of the Software. */ 21 /* */ 22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29 /*************************************************************************/ 30 31 package org.godotengine.godot.plugin; 32 33 import org.godotengine.godot.Godot; 34 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.PackageManager; 37 import android.os.Bundle; 38 import android.text.TextUtils; 39 import android.util.Log; 40 41 import androidx.annotation.Nullable; 42 43 import java.lang.reflect.Constructor; 44 import java.lang.reflect.InvocationTargetException; 45 import java.util.Collection; 46 import java.util.HashSet; 47 import java.util.Set; 48 import java.util.concurrent.ConcurrentHashMap; 49 50 /** 51 * Registry used to load and access the registered Godot Android plugins. 52 */ 53 public final class GodotPluginRegistry { 54 55 private static final String TAG = GodotPluginRegistry.class.getSimpleName(); 56 57 private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1."; 58 59 /** 60 * Name for the metadata containing the list of Godot plugins to enable. 61 */ 62 private static final String GODOT_ENABLED_PLUGINS_LABEL = "plugins"; 63 64 private static final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"; 65 66 private static GodotPluginRegistry instance; 67 private final ConcurrentHashMap<String, GodotPlugin> registry; 68 GodotPluginRegistry(Godot godot)69 private GodotPluginRegistry(Godot godot) { 70 registry = new ConcurrentHashMap<>(); 71 loadPlugins(godot); 72 } 73 74 /** 75 * Retrieve the plugin tied to the given plugin name. 76 * @param pluginName Name of the plugin 77 * @return {@link GodotPlugin} handle if it exists, null otherwise. 78 */ 79 @Nullable getPlugin(String pluginName)80 public GodotPlugin getPlugin(String pluginName) { 81 return registry.get(pluginName); 82 } 83 84 /** 85 * Retrieve the full set of loaded plugins. 86 */ getAllPlugins()87 public Collection<GodotPlugin> getAllPlugins() { 88 return registry.values(); 89 } 90 91 /** 92 * Parse the manifest file and load all included Godot Android plugins. 93 * <p> 94 * A plugin manifest entry is a '<meta-data>' tag setup as described in the {@link GodotPlugin} 95 * documentation. 96 * 97 * @param godot Godot instance 98 * @return A singleton instance of {@link GodotPluginRegistry}. This ensures that only one instance 99 * of each Godot Android plugins is available at runtime. 100 */ initializePluginRegistry(Godot godot)101 public static GodotPluginRegistry initializePluginRegistry(Godot godot) { 102 if (instance == null) { 103 instance = new GodotPluginRegistry(godot); 104 } 105 106 return instance; 107 } 108 109 /** 110 * Return the plugin registry if it's initialized. 111 * Throws a {@link IllegalStateException} exception if not. 112 * 113 * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot)} has not been called prior to calling this method. 114 */ getPluginRegistry()115 public static GodotPluginRegistry getPluginRegistry() throws IllegalStateException { 116 if (instance == null) { 117 throw new IllegalStateException("Plugin registry hasn't been initialized."); 118 } 119 120 return instance; 121 } 122 loadPlugins(Godot godot)123 private void loadPlugins(Godot godot) { 124 try { 125 ApplicationInfo appInfo = godot 126 .getPackageManager() 127 .getApplicationInfo(godot.getPackageName(), PackageManager.GET_META_DATA); 128 Bundle metaData = appInfo.metaData; 129 if (metaData == null || metaData.isEmpty()) { 130 return; 131 } 132 133 // When using the Godot editor for building and exporting the apk, this is used to check 134 // which plugins to enable. 135 // When using a custom process to generate the apk, the metadata is not needed since 136 // it's assumed that the developer is aware of the dependencies included in the apk. 137 final Set<String> enabledPluginsSet; 138 if (metaData.containsKey(GODOT_ENABLED_PLUGINS_LABEL)) { 139 String enabledPlugins = metaData.getString(GODOT_ENABLED_PLUGINS_LABEL, ""); 140 String[] enabledPluginsList = enabledPlugins.split(PLUGIN_VALUE_SEPARATOR_REGEX); 141 if (enabledPluginsList.length == 0) { 142 // No plugins to enable. Aborting early. 143 return; 144 } 145 146 enabledPluginsSet = new HashSet<>(); 147 for (String enabledPlugin : enabledPluginsList) { 148 enabledPluginsSet.add(enabledPlugin.trim()); 149 } 150 } else { 151 enabledPluginsSet = null; 152 } 153 154 int godotPluginV1NamePrefixLength = GODOT_PLUGIN_V1_NAME_PREFIX.length(); 155 for (String metaDataName : metaData.keySet()) { 156 // Parse the meta-data looking for entry with the Godot plugin name prefix. 157 if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) { 158 String pluginName = metaDataName.substring(godotPluginV1NamePrefixLength).trim(); 159 if (enabledPluginsSet != null && !enabledPluginsSet.contains(pluginName)) { 160 Log.w(TAG, "Plugin " + pluginName + " is listed in the dependencies but is not enabled."); 161 continue; 162 } 163 164 Log.i(TAG, "Initializing Godot plugin " + pluginName); 165 166 // Retrieve the plugin class full name. 167 String pluginHandleClassFullName = metaData.getString(metaDataName); 168 if (!TextUtils.isEmpty(pluginHandleClassFullName)) { 169 try { 170 // Attempt to create the plugin init class via reflection. 171 @SuppressWarnings("unchecked") 172 Class<GodotPlugin> pluginClass = (Class<GodotPlugin>)Class 173 .forName(pluginHandleClassFullName); 174 Constructor<GodotPlugin> pluginConstructor = pluginClass 175 .getConstructor(Godot.class); 176 GodotPlugin pluginHandle = pluginConstructor.newInstance(godot); 177 178 // Load the plugin initializer into the registry using the plugin name 179 // as key. 180 if (!pluginName.equals(pluginHandle.getPluginName())) { 181 Log.w(TAG, 182 "Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName()); 183 } 184 registry.put(pluginName, pluginHandle); 185 Log.i(TAG, "Completed initialization for Godot plugin " + pluginHandle.getPluginName()); 186 } catch (ClassNotFoundException e) { 187 Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); 188 } catch (IllegalAccessException e) { 189 Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); 190 } catch (InstantiationException e) { 191 Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); 192 } catch (NoSuchMethodException e) { 193 Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); 194 } catch (InvocationTargetException e) { 195 Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); 196 } 197 } else { 198 Log.w(TAG, "Invalid plugin loader class for " + pluginName); 199 } 200 } 201 } 202 } catch (PackageManager.NameNotFoundException e) { 203 Log.e(TAG, "Unable load Godot Android plugins from the manifest file.", e); 204 } 205 } 206 } 207