Browse Source

Implementation of the Godot Android Plugin configuration file

Fredia Huya-Kouadio 5 years ago
parent
commit
14e6696c8e

+ 54 - 2
editor/editor_export.cpp

@@ -1162,6 +1162,7 @@ void EditorExport::save_presets() {
 }
 }
 
 
 void EditorExport::_bind_methods() {
 void EditorExport::_bind_methods() {
+	ADD_SIGNAL(MethodInfo("export_presets_updated"));
 }
 }
 
 
 void EditorExport::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
 void EditorExport::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
@@ -1229,8 +1230,13 @@ Vector<Ref<EditorExportPlugin>> EditorExport::get_export_plugins() {
 }
 }
 
 
 void EditorExport::_notification(int p_what) {
 void EditorExport::_notification(int p_what) {
-	if (p_what == NOTIFICATION_ENTER_TREE) {
-		load_config();
+	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE: {
+			load_config();
+		} break;
+		case NOTIFICATION_PROCESS: {
+			update_export_presets();
+		} break;
 	}
 	}
 }
 }
 
 
@@ -1332,6 +1338,49 @@ void EditorExport::load_config() {
 	block_save = false;
 	block_save = false;
 }
 }
 
 
+void EditorExport::update_export_presets() {
+	Map<StringName, List<EditorExportPlatform::ExportOption>> platform_options;
+
+	for (int i = 0; i < export_platforms.size(); i++) {
+		Ref<EditorExportPlatform> platform = export_platforms[i];
+
+		if (platform->should_update_export_options()) {
+			List<EditorExportPlatform::ExportOption> options;
+			platform->get_export_options(&options);
+
+			platform_options[platform->get_name()] = options;
+		}
+	}
+
+	bool export_presets_updated = false;
+	for (int i = 0; i < export_presets.size(); i++) {
+		Ref<EditorExportPreset> preset = export_presets[i];
+		if (platform_options.has(preset->get_platform()->get_name())) {
+			export_presets_updated = true;
+
+			List<EditorExportPlatform::ExportOption> options = platform_options[preset->get_platform()->get_name()];
+
+			// Copy the previous preset values
+			Map<StringName, Variant> previous_values = preset->values;
+
+			// Clear the preset properties and values prior to reloading
+			preset->properties.clear();
+			preset->values.clear();
+
+			for (List<EditorExportPlatform::ExportOption>::Element *E = options.front(); E; E = E->next()) {
+				preset->properties.push_back(E->get().option);
+
+				StringName option_name = E->get().option.name;
+				preset->values[option_name] = previous_values.has(option_name) ? previous_values[option_name] : E->get().default_value;
+			}
+		}
+	}
+
+	if (export_presets_updated) {
+		emit_signal(_export_presets_updated);
+	}
+}
+
 bool EditorExport::poll_export_platforms() {
 bool EditorExport::poll_export_platforms() {
 	bool changed = false;
 	bool changed = false;
 	for (int i = 0; i < export_platforms.size(); i++) {
 	for (int i = 0; i < export_platforms.size(); i++) {
@@ -1351,7 +1400,10 @@ EditorExport::EditorExport() {
 	save_timer->connect("timeout", callable_mp(this, &EditorExport::_save));
 	save_timer->connect("timeout", callable_mp(this, &EditorExport::_save));
 	block_save = false;
 	block_save = false;
 
 
+	_export_presets_updated = "export_presets_updated";
+
 	singleton = this;
 	singleton = this;
+	set_process(true);
 }
 }
 
 
 EditorExport::~EditorExport() {
 EditorExport::~EditorExport() {

+ 4 - 1
editor/editor_export.h

@@ -227,6 +227,7 @@ public:
 	virtual Ref<EditorExportPreset> create_preset();
 	virtual Ref<EditorExportPreset> create_preset();
 
 
 	virtual void get_export_options(List<ExportOption> *r_options) = 0;
 	virtual void get_export_options(List<ExportOption> *r_options) = 0;
+	virtual bool should_update_export_options() { return false; }
 	virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { return true; }
 	virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { return true; }
 
 
 	virtual String get_os_name() const = 0;
 	virtual String get_os_name() const = 0;
@@ -350,6 +351,8 @@ class EditorExport : public Node {
 	Vector<Ref<EditorExportPreset>> export_presets;
 	Vector<Ref<EditorExportPreset>> export_presets;
 	Vector<Ref<EditorExportPlugin>> export_plugins;
 	Vector<Ref<EditorExportPlugin>> export_plugins;
 
 
+	StringName _export_presets_updated;
+
 	Timer *save_timer;
 	Timer *save_timer;
 	bool block_save;
 	bool block_save;
 
 
@@ -381,7 +384,7 @@ public:
 	Vector<Ref<EditorExportPlugin>> get_export_plugins();
 	Vector<Ref<EditorExportPlugin>> get_export_plugins();
 
 
 	void load_config();
 	void load_config();
-
+	void update_export_presets();
 	bool poll_export_platforms();
 	bool poll_export_platforms();
 
 
 	EditorExport();
 	EditorExport();

+ 7 - 0
editor/project_export.cpp

@@ -133,6 +133,12 @@ void ProjectExportDialog::_add_preset(int p_platform) {
 	_edit_preset(EditorExport::get_singleton()->get_export_preset_count() - 1);
 	_edit_preset(EditorExport::get_singleton()->get_export_preset_count() - 1);
 }
 }
 
 
+void ProjectExportDialog::_force_update_current_preset_parameters() {
+	// Force the parameters section to refresh its UI.
+	parameters->edit(nullptr);
+	_update_current_preset();
+}
+
 void ProjectExportDialog::_update_current_preset() {
 void ProjectExportDialog::_update_current_preset() {
 	_edit_preset(presets->get_current());
 	_edit_preset(presets->get_current());
 }
 }
@@ -1101,6 +1107,7 @@ ProjectExportDialog::ProjectExportDialog() {
 	parameters->set_name(TTR("Options"));
 	parameters->set_name(TTR("Options"));
 	parameters->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 	parameters->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 	parameters->connect("property_edited", callable_mp(this, &ProjectExportDialog::_update_parameters));
 	parameters->connect("property_edited", callable_mp(this, &ProjectExportDialog::_update_parameters));
+	EditorExport::get_singleton()->connect("export_presets_updated", callable_mp(this, &ProjectExportDialog::_force_update_current_preset_parameters));
 
 
 	// Resources export parameters.
 	// Resources export parameters.
 
 

+ 1 - 0
editor/project_export.h

@@ -123,6 +123,7 @@ private:
 	void _delete_preset_confirm();
 	void _delete_preset_confirm();
 	void _update_export_all();
 	void _update_export_all();
 
 
+	void _force_update_current_preset_parameters();
 	void _update_current_preset();
 	void _update_current_preset();
 	void _update_presets();
 	void _update_presets();
 
 

+ 131 - 13
platform/android/export/export.cpp

@@ -44,6 +44,7 @@
 #include "editor/editor_node.h"
 #include "editor/editor_node.h"
 #include "editor/editor_settings.h"
 #include "editor/editor_settings.h"
 #include "platform/android/logo.gen.h"
 #include "platform/android/logo.gen.h"
+#include "platform/android/plugin/godot_plugin_config.h"
 #include "platform/android/run_icon.gen.h"
 #include "platform/android/run_icon.gen.h"
 
 
 #include <string.h>
 #include <string.h>
@@ -252,16 +253,45 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
 		EditorProgress *ep;
 		EditorProgress *ep;
 	};
 	};
 
 
+	Vector<PluginConfig> plugins;
+	volatile bool plugins_changed;
+	Mutex plugins_lock;
 	Vector<Device> devices;
 	Vector<Device> devices;
 	volatile bool devices_changed;
 	volatile bool devices_changed;
 	Mutex device_lock;
 	Mutex device_lock;
-	Thread *device_thread;
+	Thread *check_for_changes_thread;
 	volatile bool quit_request;
 	volatile bool quit_request;
 
 
-	static void _device_poll_thread(void *ud) {
+	static void _check_for_changes_poll_thread(void *ud) {
 		EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud;
 		EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud;
 
 
 		while (!ea->quit_request) {
 		while (!ea->quit_request) {
+			// Check for plugins updates
+			{
+				// Nothing to do if we already know the plugins have changed.
+				if (!ea->plugins_changed) {
+					Vector<PluginConfig> loaded_plugins = get_plugins();
+
+					MutexLock lock(ea->plugins_lock);
+
+					if (ea->plugins.size() != loaded_plugins.size()) {
+						ea->plugins_changed = true;
+					} else {
+						for (int i = 0; i < ea->plugins.size(); i++) {
+							if (ea->plugins[i].name != loaded_plugins[i].name) {
+								ea->plugins_changed = true;
+								break;
+							}
+						}
+					}
+
+					if (ea->plugins_changed) {
+						ea->plugins = loaded_plugins;
+					}
+				}
+			}
+
+			// Check for devices updates
 			String adb = EditorSettings::get_singleton()->get("export/android/adb");
 			String adb = EditorSettings::get_singleton()->get("export/android/adb");
 			if (FileAccess::exists(adb)) {
 			if (FileAccess::exists(adb)) {
 				String devices;
 				String devices;
@@ -573,6 +603,73 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
 		return abis;
 		return abis;
 	}
 	}
 
 
+	/// List the gdap files in the directory specified by the p_path parameter.
+	static Vector<String> list_gdap_files(const String &p_path) {
+		Vector<String> dir_files;
+		DirAccessRef da = DirAccess::open(p_path);
+		if (da) {
+			da->list_dir_begin();
+			while (true) {
+				String file = da->get_next();
+				if (file == "") {
+					break;
+				}
+
+				if (da->current_is_dir() || da->current_is_hidden()) {
+					continue;
+				}
+
+				if (file.ends_with(PLUGIN_CONFIG_EXT)) {
+					dir_files.push_back(file);
+				}
+			}
+			da->list_dir_end();
+		}
+
+		return dir_files;
+	}
+
+	static Vector<PluginConfig> get_plugins() {
+		Vector<PluginConfig> loaded_plugins;
+
+		String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins");
+
+		// Add the prebuilt plugins
+		loaded_plugins.append_array(get_prebuilt_plugins(plugins_dir));
+
+		if (DirAccess::exists(plugins_dir)) {
+			Vector<String> plugins_filenames = list_gdap_files(plugins_dir);
+
+			if (!plugins_filenames.empty()) {
+				Ref<ConfigFile> config_file = memnew(ConfigFile);
+				for (int i = 0; i < plugins_filenames.size(); i++) {
+					PluginConfig config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
+					if (config.valid_config) {
+						loaded_plugins.push_back(config);
+					} else {
+						print_error("Invalid plugin config file " + plugins_filenames[i]);
+					}
+				}
+			}
+		}
+
+		return loaded_plugins;
+	}
+
+	static Vector<PluginConfig> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
+		Vector<PluginConfig> enabled_plugins;
+		Vector<PluginConfig> all_plugins = get_plugins();
+		for (int i = 0; i < all_plugins.size(); i++) {
+			PluginConfig plugin = all_plugins[i];
+			bool enabled = p_presets->get("plugins/" + plugin.name);
+			if (enabled) {
+				enabled_plugins.push_back(plugin);
+			}
+		}
+
+		return enabled_plugins;
+	}
+
 	static Error store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method = Z_DEFLATED) {
 	static Error store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method = Z_DEFLATED) {
 		zip_fileinfo zipfi = get_zip_fileinfo();
 		zip_fileinfo zipfi = get_zip_fileinfo();
 		zipOpenNewFileInZip(ed->apk,
 		zipOpenNewFileInZip(ed->apk,
@@ -674,7 +771,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
 
 
 		int xr_mode_index = p_preset->get("xr_features/xr_mode");
 		int xr_mode_index = p_preset->get("xr_features/xr_mode");
 
 
-		String plugins = p_preset->get("custom_template/plugins");
+		String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
 
 
 		Vector<String> perms;
 		Vector<String> perms;
 
 
@@ -829,9 +926,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
 							}
 							}
 						}
 						}
 
 
-						if (tname == "meta-data" && attrname == "value" && value == "custom_template_plugins_value") {
+						if (tname == "meta-data" && attrname == "value" && value == "plugins_value" && !plugins_names.empty()) {
 							// Update the meta-data 'android:value' attribute with the list of enabled plugins.
 							// Update the meta-data 'android:value' attribute with the list of enabled plugins.
-							string_table.write[attr_value] = plugins;
+							string_table.write[attr_value] = plugins_names;
 						}
 						}
 
 
 						iofs += 20;
 						iofs += 20;
@@ -1354,7 +1451,14 @@ public:
 		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false));
-		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/plugins", PROPERTY_HINT_PLACEHOLDER_TEXT, "Plugin1,Plugin2,..."), ""));
+
+		Vector<PluginConfig> plugins_configs = get_plugins();
+		for (int i = 0; i < plugins_configs.size(); i++) {
+			print_verbose("Found Android plugin " + plugins_configs[i].name);
+			r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + plugins_configs[i].name), false));
+		}
+		plugins_changed = false;
+
 		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), ""));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), ""));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0"));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0"));
@@ -1409,6 +1513,15 @@ public:
 		return logo;
 		return logo;
 	}
 	}
 
 
+	virtual bool should_update_export_options() {
+		bool export_options_changed = plugins_changed;
+		if (export_options_changed) {
+			// don't clear unless we're reporting true, to avoid race
+			plugins_changed = false;
+		}
+		return export_options_changed;
+	}
+
 	virtual bool poll_export() {
 	virtual bool poll_export() {
 		bool dc = devices_changed;
 		bool dc = devices_changed;
 		if (dc) {
 		if (dc) {
@@ -1755,18 +1868,22 @@ public:
 #endif
 #endif
 
 
 			String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
 			String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
-			String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins");
 
 
 			build_command = build_path.plus_file(build_command);
 			build_command = build_path.plus_file(build_command);
 
 
 			String package_name = get_package_name(p_preset->get("package/unique_name"));
 			String package_name = get_package_name(p_preset->get("package/unique_name"));
-			String plugins = p_preset->get("custom_template/plugins");
+
+			Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);
+			String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins);
+			String remote_plugins_binaries = get_plugins_binaries(BINARY_TYPE_REMOTE, enabled_plugins);
+			String custom_maven_repos = get_plugins_custom_maven_repos(enabled_plugins);
 
 
 			List<String> cmdline;
 			List<String> cmdline;
 			cmdline.push_back("build");
 			cmdline.push_back("build");
 			cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
 			cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
-			cmdline.push_back("-Pcustom_template_plugins_dir=" + plugins_dir); // argument to specify the plugins directory.
-			cmdline.push_back("-Pcustom_template_plugins=" + plugins); // argument to specify the list of plugins to enable.
+			cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.
+			cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.
+			cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies.
 			cmdline.push_back("-p"); // argument to specify the start directory.
 			cmdline.push_back("-p"); // argument to specify the start directory.
 			cmdline.push_back(build_path); // start directory.
 			cmdline.push_back(build_path); // start directory.
 			/*{ used for debug
 			/*{ used for debug
@@ -2283,14 +2400,15 @@ public:
 		run_icon->create_from_image(img);
 		run_icon->create_from_image(img);
 
 
 		devices_changed = true;
 		devices_changed = true;
+		plugins_changed = true;
 		quit_request = false;
 		quit_request = false;
-		device_thread = Thread::create(_device_poll_thread, this);
+		check_for_changes_thread = Thread::create(_check_for_changes_poll_thread, this);
 	}
 	}
 
 
 	~EditorExportPlatformAndroid() {
 	~EditorExportPlatformAndroid() {
 		quit_request = true;
 		quit_request = true;
-		Thread::wait_to_finish(device_thread);
-		memdelete(device_thread);
+		Thread::wait_to_finish(check_for_changes_thread);
+		memdelete(check_for_changes_thread);
 	}
 	}
 };
 };
 
 

+ 2 - 2
platform/android/java/app/AndroidManifest.xml

@@ -32,8 +32,8 @@
 
 
         <!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. -->
         <!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. -->
         <meta-data
         <meta-data
-            android:name="custom_template_plugins"
-            android:value="custom_template_plugins_value"/>
+            android:name="plugins"
+            android:value="plugins_value"/>
 
 
         <activity
         <activity
             android:name=".GodotApp"
             android:name=".GodotApp"

+ 21 - 8
platform/android/java/app/build.gradle

@@ -21,6 +21,16 @@ allprojects {
         mavenCentral()
         mavenCentral()
         google()
         google()
         jcenter()
         jcenter()
+
+        // Godot user plugins custom maven repos
+        String[] mavenRepos = getGodotPluginsMavenRepos()
+        if (mavenRepos != null && mavenRepos.size() > 0) {
+            for (String repoUrl : mavenRepos) {
+                maven {
+                    url repoUrl
+                }
+            }
+        }
     }
     }
 }
 }
 
 
@@ -40,15 +50,18 @@ dependencies {
         releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar'])
         releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar'])
     }
     }
 
 
-    // Godot prebuilt plugins
-    implementation fileTree(dir: 'libs/plugins', include: ["GodotPayment*.aar"])
+    // Godot user plugins remote dependencies
+    String[] remoteDeps = getGodotPluginsRemoteBinaries()
+    if (remoteDeps != null && remoteDeps.size() > 0) {
+        for (String dep : remoteDeps) {
+            implementation dep
+        }
+    }
 
 
-    // Godot user plugins dependencies
-    String pluginsDir = getGodotPluginsDirectory()
-    String[] pluginsBinaries = getGodotPluginsBinaries()
-    if (pluginsDir != null && !pluginsDir.isEmpty() &&
-        pluginsBinaries != null && pluginsBinaries.size() > 0) {
-        implementation fileTree(dir: pluginsDir, include: pluginsBinaries)
+    // Godot user plugins local dependencies
+    String[] pluginsBinaries = getGodotPluginsLocalBinaries()
+    if (pluginsBinaries != null && pluginsBinaries.size() > 0) {
+        implementation files(pluginsBinaries)
     }
     }
 }
 }
 
 

+ 51 - 22
platform/android/java/app/config.gradle

@@ -28,39 +28,68 @@ ext.getExportPackageName = { ->
     return appId
     return appId
 }
 }
 
 
+final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"
+
 /**
 /**
- * Parse the project properties for the 'custom_template_plugins' property and return
- * their binaries for inclusion in the build dependencies.
- *
- * The listed plugins must have their binaries in the project plugins directory.
+ * Parse the project properties for the 'plugins_maven_repos' property and return the list
+ * of maven repos.
  */
  */
-ext.getGodotPluginsBinaries = { ->
-    String[] binDeps = []
+ext.getGodotPluginsMavenRepos = { ->
+    Set<String> mavenRepos = []
 
 
-    // Retrieve the list of enabled plugins.
-    if (project.hasProperty("custom_template_plugins")) {
-        String pluginsList = project.property("custom_template_plugins")
-        if (pluginsList != null && !pluginsList.trim().isEmpty()) {
-            for (String plugin : pluginsList.split(",")) {
-                binDeps += plugin.trim() + "*.aar"
+    // Retrieve the list of maven repos.
+    if (project.hasProperty("plugins_maven_repos")) {
+        String mavenReposProperty = project.property("plugins_maven_repos")
+        if (mavenReposProperty != null && !mavenReposProperty.trim().isEmpty()) {
+            for (String mavenRepoUrl : mavenReposProperty.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+                mavenRepos += mavenRepoUrl.trim()
             }
             }
         }
         }
     }
     }
 
 
-    return binDeps
+    return mavenRepos
+}
+
+/**
+ * Parse the project properties for the 'plugins_remote_binaries' property and return
+ * it for inclusion in the build dependencies.
+ */
+ext.getGodotPluginsRemoteBinaries = { ->
+    Set<String> remoteDeps = []
+
+    // Retrieve the list of remote plugins binaries.
+    if (project.hasProperty("plugins_remote_binaries")) {
+        String remoteDepsList = project.property("plugins_remote_binaries")
+        if (remoteDepsList != null && !remoteDepsList.trim().isEmpty()) {
+            for (String dep: remoteDepsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+                remoteDeps += dep.trim()
+            }
+        }
+    }
+    return remoteDeps
 }
 }
 
 
 /**
 /**
- * Parse the project properties for the 'custom_template_plugins_dir' property and return
- * its value.
+ * Parse the project properties for the 'plugins_local_binaries' property and return
+ * their binaries for inclusion in the build dependencies.
  *
  *
- * The returned value is the directory containing user plugins.
+ * Returns the prebuilt plugins if the 'plugins_local_binaries' property is unavailable.
  */
  */
-ext.getGodotPluginsDirectory = { ->
-    // The plugins directory is provided by the 'custom_template_plugins_dir' property.
-    String pluginsDir = project.hasProperty("custom_template_plugins_dir")
-        ? project.property("custom_template_plugins_dir")
-        : ""
+ext.getGodotPluginsLocalBinaries = { ->
+    // Set the prebuilt plugins as default. If custom build is enabled,
+    // the 'plugins_local_binaries' will be defined so we can use it instead.
+    Set<String> binDeps = ["libs/plugins/GodotPayment.release.aar"]
+
+    // Retrieve the list of local plugins binaries.
+    if (project.hasProperty("plugins_local_binaries")) {
+        binDeps.clear()
+        String pluginsList = project.property("plugins_local_binaries")
+        if (pluginsList != null && !pluginsList.trim().isEmpty()) {
+            for (String plugin : pluginsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+                binDeps += plugin.trim()
+            }
+        }
+    }
 
 
-    return pluginsDir
+    return binDeps
 }
 }

+ 2 - 1
platform/android/java/build.gradle

@@ -140,7 +140,7 @@ task generateGodotTemplates(type: GradleBuild) {
         startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType)
         startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType)
     }
     }
 
 
-    tasks = ["copyGodotPaymentPluginToAppModule"]
+    tasks = []
 
 
     // Only build the apks and aar files for which we have native shared libraries.
     // Only build the apks and aar files for which we have native shared libraries.
     for (String target : supportedTargets) {
     for (String target : supportedTargets) {
@@ -161,6 +161,7 @@ task generateGodotTemplates(type: GradleBuild) {
         }
         }
     }
     }
 
 
+    dependsOn 'copyGodotPaymentPluginToAppModule'
     finalizedBy 'zipCustomBuild'
     finalizedBy 'zipCustomBuild'
 }
 }
 
 

+ 8 - 3
platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java

@@ -58,7 +58,9 @@ public final class GodotPluginRegistry {
 	/**
 	/**
 	 * Name for the metadata containing the list of Godot plugins to enable.
 	 * Name for the metadata containing the list of Godot plugins to enable.
 	 */
 	 */
-	private static final String GODOT_ENABLED_PLUGINS_LABEL = "custom_template_plugins";
+	private static final String GODOT_ENABLED_PLUGINS_LABEL = "plugins";
+
+	private static final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|";
 
 
 	private static GodotPluginRegistry instance;
 	private static GodotPluginRegistry instance;
 	private final ConcurrentHashMap<String, GodotPlugin> registry;
 	private final ConcurrentHashMap<String, GodotPlugin> registry;
@@ -128,13 +130,13 @@ public final class GodotPluginRegistry {
 			}
 			}
 
 
 			// When using the Godot editor for building and exporting the apk, this is used to check
 			// When using the Godot editor for building and exporting the apk, this is used to check
-			// which plugins to enable since the custom build template may contain prebuilt plugins.
+			// which plugins to enable.
 			// When using a custom process to generate the apk, the metadata is not needed since
 			// When using a custom process to generate the apk, the metadata is not needed since
 			// it's assumed that the developer is aware of the dependencies included in the apk.
 			// it's assumed that the developer is aware of the dependencies included in the apk.
 			final Set<String> enabledPluginsSet;
 			final Set<String> enabledPluginsSet;
 			if (metaData.containsKey(GODOT_ENABLED_PLUGINS_LABEL)) {
 			if (metaData.containsKey(GODOT_ENABLED_PLUGINS_LABEL)) {
 				String enabledPlugins = metaData.getString(GODOT_ENABLED_PLUGINS_LABEL, "");
 				String enabledPlugins = metaData.getString(GODOT_ENABLED_PLUGINS_LABEL, "");
-				String[] enabledPluginsList = enabledPlugins.split(",");
+				String[] enabledPluginsList = enabledPlugins.split(PLUGIN_VALUE_SEPARATOR_REGEX);
 				if (enabledPluginsList.length == 0) {
 				if (enabledPluginsList.length == 0) {
 					// No plugins to enable. Aborting early.
 					// No plugins to enable. Aborting early.
 					return;
 					return;
@@ -158,6 +160,8 @@ public final class GodotPluginRegistry {
 						continue;
 						continue;
 					}
 					}
 
 
+					Log.i(TAG, "Initializing Godot plugin " + pluginName);
+
 					// Retrieve the plugin class full name.
 					// Retrieve the plugin class full name.
 					String pluginHandleClassFullName = metaData.getString(metaDataName);
 					String pluginHandleClassFullName = metaData.getString(metaDataName);
 					if (!TextUtils.isEmpty(pluginHandleClassFullName)) {
 					if (!TextUtils.isEmpty(pluginHandleClassFullName)) {
@@ -177,6 +181,7 @@ public final class GodotPluginRegistry {
 										"Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName());
 										"Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName());
 							}
 							}
 							registry.put(pluginName, pluginHandle);
 							registry.put(pluginName, pluginHandle);
+							Log.i(TAG, "Completed initialization for Godot plugin " + pluginHandle.getPluginName());
 						} catch (ClassNotFoundException e) {
 						} catch (ClassNotFoundException e) {
 							Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
 							Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
 						} catch (IllegalAccessException e) {
 						} catch (IllegalAccessException e) {

+ 251 - 0
platform/android/plugin/godot_plugin_config.h

@@ -0,0 +1,251 @@
+/*************************************************************************/
+/*  godot_plugin_config.h                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 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.                */
+/*************************************************************************/
+
+#ifndef GODOT_PLUGIN_CONFIG_H
+#define GODOT_PLUGIN_CONFIG_H
+
+#include "core/error_list.h"
+#include "core/io/config_file.h"
+#include "core/ustring.h"
+
+static const char *PLUGIN_CONFIG_EXT = ".gdap";
+
+static const char *CONFIG_SECTION = "config";
+static const char *CONFIG_NAME_KEY = "name";
+static const char *CONFIG_BINARY_TYPE_KEY = "binary_type";
+static const char *CONFIG_BINARY_KEY = "binary";
+
+static const char *DEPENDENCIES_SECTION = "dependencies";
+static const char *DEPENDENCIES_LOCAL_KEY = "local";
+static const char *DEPENDENCIES_REMOTE_KEY = "remote";
+static const char *DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY = "custom_maven_repos";
+
+static const char *BINARY_TYPE_LOCAL = "local";
+static const char *BINARY_TYPE_REMOTE = "remote";
+
+static const char *PLUGIN_VALUE_SEPARATOR = "|";
+
+/*
+ The `config` section and fields are required and defined as follow:
+- **name**: name of the plugin
+- **binary_type**: can be either `local` or `remote`.  The type affects the **binary** field
+- **binary**:
+  - if **binary_type** is `local`, then this should be the filename of the plugin `aar` file in the `res://android/plugins` directory (e.g: `MyPlugin.aar`).
+  - if **binary_type** is `remote`, then this should be a declaration for a remote gradle binary (e.g: "org.godot.example:my-plugin:0.0.0").
+
+The `dependencies` section and fields are optional and defined as follow:
+- **local**: contains a list of local `.aar` binary files the plugin depends on. The local binary dependencies must also be located in the `res://android/plugins` directory.
+- **remote**: contains a list of remote binary gradle dependencies for the plugin.
+- **custom_maven_repos**: contains a list of urls specifying custom maven repos required for the plugin's dependencies.
+
+ See https://github.com/godotengine/godot/issues/38157#issuecomment-618773871
+ */
+struct PluginConfig {
+	// Set to true when the config file is properly loaded.
+	bool valid_config = false;
+
+	// Required config section
+	String name;
+	String binary_type;
+	String binary;
+
+	// Optional dependencies section
+	Vector<String> local_dependencies;
+	Vector<String> remote_dependencies;
+	Vector<String> custom_maven_repos;
+};
+
+/*
+ * Set of prebuilt plugins.
+ */
+static const PluginConfig GODOT_PAYMENT = {
+	/*.valid_config =*/true,
+	/*.name =*/"GodotPayment",
+	/*.binary_type =*/"local",
+	/*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar",
+	/*.local_dependencies =*/{},
+	/*.remote_dependencies =*/{},
+	/*.custom_maven_repos =*/{}
+};
+
+static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
+	String absolute_path;
+	if (!dependency_path.empty()) {
+		if (dependency_path.is_abs_path()) {
+			absolute_path = ProjectSettings::get_singleton()->globalize_path(dependency_path);
+		} else {
+			absolute_path = plugin_config_dir.plus_file(dependency_path);
+		}
+	}
+
+	return absolute_path;
+}
+
+static inline PluginConfig resolve_prebuilt_plugin(PluginConfig prebuilt_plugin, String plugin_config_dir) {
+	PluginConfig resolved = prebuilt_plugin;
+	resolved.binary = resolved.binary_type == BINARY_TYPE_LOCAL ? resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.binary) : prebuilt_plugin.binary;
+	if (!prebuilt_plugin.local_dependencies.empty()) {
+		resolved.local_dependencies.clear();
+		for (int i = 0; i < prebuilt_plugin.local_dependencies.size(); i++) {
+			resolved.local_dependencies.push_back(resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.local_dependencies[i]));
+		}
+	}
+	return resolved;
+}
+
+static inline Vector<PluginConfig> get_prebuilt_plugins(String plugins_base_dir) {
+	Vector<PluginConfig> prebuilt_plugins;
+	prebuilt_plugins.push_back(resolve_prebuilt_plugin(GODOT_PAYMENT, plugins_base_dir));
+	return prebuilt_plugins;
+}
+
+static inline bool is_plugin_config_valid(PluginConfig plugin_config) {
+	bool valid_name = !plugin_config.name.empty();
+	bool valid_binary_type = plugin_config.binary_type == BINARY_TYPE_LOCAL ||
+							 plugin_config.binary_type == BINARY_TYPE_REMOTE;
+
+	bool valid_binary = false;
+	if (valid_binary_type) {
+		valid_binary = !plugin_config.binary.empty() &&
+					   (plugin_config.binary_type == BINARY_TYPE_REMOTE ||
+							   FileAccess::exists(plugin_config.binary));
+	}
+
+	bool valid_local_dependencies = true;
+	if (!plugin_config.local_dependencies.empty()) {
+		for (int i = 0; i < plugin_config.local_dependencies.size(); i++) {
+			if (!FileAccess::exists(plugin_config.local_dependencies[i])) {
+				valid_local_dependencies = false;
+				break;
+			}
+		}
+	}
+	return valid_name && valid_binary && valid_binary_type && valid_local_dependencies;
+}
+
+static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
+	PluginConfig plugin_config = {};
+
+	if (config_file.is_valid()) {
+		Error err = config_file->load(path);
+		if (err == OK) {
+			String config_base_dir = path.get_base_dir();
+
+			plugin_config.name = config_file->get_value(CONFIG_SECTION, CONFIG_NAME_KEY, String());
+			plugin_config.binary_type = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_TYPE_KEY, String());
+
+			String binary_path = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_KEY, String());
+			plugin_config.binary = plugin_config.binary_type == BINARY_TYPE_LOCAL ? resolve_local_dependency_path(config_base_dir, binary_path) : binary_path;
+
+			if (config_file->has_section(DEPENDENCIES_SECTION)) {
+				Vector<String> local_dependencies_paths = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_LOCAL_KEY, Vector<String>());
+				if (!local_dependencies_paths.empty()) {
+					for (int i = 0; i < local_dependencies_paths.size(); i++) {
+						plugin_config.local_dependencies.push_back(resolve_local_dependency_path(config_base_dir, local_dependencies_paths[i]));
+					}
+				}
+
+				plugin_config.remote_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_REMOTE_KEY, Vector<String>());
+				plugin_config.custom_maven_repos = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY, Vector<String>());
+			}
+
+			plugin_config.valid_config = is_plugin_config_valid(plugin_config);
+		}
+	}
+
+	return plugin_config;
+}
+
+static inline String get_plugins_binaries(String binary_type, Vector<PluginConfig> plugins_configs) {
+	String plugins_binaries;
+	if (!plugins_configs.empty()) {
+		Vector<String> binaries;
+		for (int i = 0; i < plugins_configs.size(); i++) {
+			PluginConfig config = plugins_configs[i];
+			if (!config.valid_config) {
+				continue;
+			}
+
+			if (config.binary_type == binary_type) {
+				binaries.push_back(config.binary);
+			}
+
+			if (binary_type == BINARY_TYPE_LOCAL) {
+				binaries.append_array(config.local_dependencies);
+			}
+
+			if (binary_type == BINARY_TYPE_REMOTE) {
+				binaries.append_array(config.remote_dependencies);
+			}
+		}
+
+		plugins_binaries = String(PLUGIN_VALUE_SEPARATOR).join(binaries);
+	}
+
+	return plugins_binaries;
+}
+
+static inline String get_plugins_custom_maven_repos(Vector<PluginConfig> plugins_configs) {
+	String custom_maven_repos;
+	if (!plugins_configs.empty()) {
+		Vector<String> repos_urls;
+		for (int i = 0; i < plugins_configs.size(); i++) {
+			PluginConfig config = plugins_configs[i];
+			if (!config.valid_config) {
+				continue;
+			}
+
+			repos_urls.append_array(config.custom_maven_repos);
+		}
+
+		custom_maven_repos = String(PLUGIN_VALUE_SEPARATOR).join(repos_urls);
+	}
+	return custom_maven_repos;
+}
+
+static inline String get_plugins_names(Vector<PluginConfig> plugins_configs) {
+	String plugins_names;
+	if (!plugins_configs.empty()) {
+		Vector<String> names;
+		for (int i = 0; i < plugins_configs.size(); i++) {
+			PluginConfig config = plugins_configs[i];
+			if (!config.valid_config) {
+				continue;
+			}
+
+			names.push_back(config.name);
+		}
+		plugins_names = String(PLUGIN_VALUE_SEPARATOR).join(names);
+	}
+
+	return plugins_names;
+}
+
+#endif // GODOT_PLUGIN_CONFIG_H