Explorar el Código

Android now (optionally) builds the template when exporting

Added new way to create add-ons
Removed old way to create add-ons
Juan Linietsky hace 6 años
padre
commit
dd03dcbd5a

+ 0 - 3
.gitignore

@@ -8,13 +8,10 @@ doc/_build/
 *.bc
 
 # Android specific
-platform/android/java/build.gradle
 platform/android/java/.gradle
 platform/android/java/.gradletasknamecache
 platform/android/java/local.properties
 platform/android/java/project.properties
-platform/android/java/build.gradle
-platform/android/java/AndroidManifest.xml
 platform/android/java/libs/*
 platform/android/java/assets
 platform/android/java/.idea/*

+ 0 - 28
SConstruct

@@ -66,20 +66,6 @@ if 'TERM' in os.environ:
     env_base['ENV']['TERM'] = os.environ['TERM']
 env_base.AppendENVPath('PATH', os.getenv('PATH'))
 env_base.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
-env_base.android_maven_repos = []
-env_base.android_flat_dirs = []
-env_base.android_dependencies = []
-env_base.android_gradle_plugins = []
-env_base.android_gradle_classpath = []
-env_base.android_java_dirs = []
-env_base.android_res_dirs = []
-env_base.android_asset_dirs = []
-env_base.android_aidl_dirs = []
-env_base.android_jni_dirs = []
-env_base.android_default_config = []
-env_base.android_manifest_chunk = ""
-env_base.android_permission_chunk = ""
-env_base.android_appattributes_chunk = ""
 env_base.disabled_modules = []
 env_base.use_ptrcall = False
 env_base.split_drivers = False
@@ -87,20 +73,6 @@ env_base.split_modules = False
 env_base.module_version_string = ""
 env_base.msvc = False
 
-env_base.__class__.android_add_maven_repository = methods.android_add_maven_repository
-env_base.__class__.android_add_flat_dir = methods.android_add_flat_dir
-env_base.__class__.android_add_dependency = methods.android_add_dependency
-env_base.__class__.android_add_java_dir = methods.android_add_java_dir
-env_base.__class__.android_add_res_dir = methods.android_add_res_dir
-env_base.__class__.android_add_asset_dir = methods.android_add_asset_dir
-env_base.__class__.android_add_aidl_dir = methods.android_add_aidl_dir
-env_base.__class__.android_add_jni_dir = methods.android_add_jni_dir
-env_base.__class__.android_add_default_config = methods.android_add_default_config
-env_base.__class__.android_add_to_manifest = methods.android_add_to_manifest
-env_base.__class__.android_add_to_permissions = methods.android_add_to_permissions
-env_base.__class__.android_add_to_attributes = methods.android_add_to_attributes
-env_base.__class__.android_add_gradle_plugin = methods.android_add_gradle_plugin
-env_base.__class__.android_add_gradle_classpath = methods.android_add_gradle_classpath
 env_base.__class__.disable_module = methods.disable_module
 
 env_base.__class__.add_module_version_string = methods.add_module_version_string

+ 108 - 3
editor/editor_node.cpp

@@ -47,6 +47,7 @@
 #include "core/translation.h"
 #include "core/version.h"
 #include "main/input_default.h"
+#include "main/main.h"
 #include "scene/resources/packed_scene.h"
 #include "servers/physics_2d_server.h"
 
@@ -2270,6 +2271,23 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
 
 			OS::get_singleton()->shell_open(String("file://") + OS::get_singleton()->get_user_data_dir());
 		} break;
+		case FILE_INSTALL_ANDROID_SOURCE: {
+
+			if (p_confirmed) {
+				export_template_manager->install_android_template();
+			} else {
+				if (DirAccess::exists("res://android/build")) {
+					remove_android_build_template->popup_centered_minsize();
+				} else if (export_template_manager->can_install_android_template()) {
+					install_android_build_template->popup_centered_minsize();
+				} else {
+					custom_build_manage_templates->popup_centered_minsize();
+				}
+			}
+		} break;
+		case FILE_EXPLORE_ANDROID_BUILD_TEMPLATES: {
+			OS::get_singleton()->shell_open(String("file://") + ProjectSettings::get_singleton()->get_resource_path().plus_file("android"));
+		} break;
 		case FILE_QUIT:
 		case RUN_PROJECT_MANAGER: {
 
@@ -5044,6 +5062,68 @@ void EditorNode::_print_handler(void *p_this, const String &p_string, bool p_err
 	en->log->add_message(p_string, p_error ? EditorLog::MSG_TYPE_ERROR : EditorLog::MSG_TYPE_STD);
 }
 
+static void _execute_thread(void *p_ud) {
+
+	EditorNode::ExecuteThreadArgs *eta = (EditorNode::ExecuteThreadArgs *)p_ud;
+	Error err = OS::get_singleton()->execute(eta->path, eta->args, true, NULL, &eta->output, &eta->exitcode, true, eta->execute_output_mutex);
+	print_verbose("Thread exit status: " + itos(eta->exitcode));
+	if (err != OK) {
+		eta->exitcode = err;
+	}
+
+	eta->done = true;
+}
+
+int EditorNode::execute_and_show_output(const String &p_title, const String &p_path, const List<String> &p_arguments, bool p_close_on_ok, bool p_close_on_errors) {
+
+	execute_output_dialog->set_title(p_title);
+	execute_output_dialog->get_ok()->set_disabled(true);
+	execute_outputs->clear();
+	execute_outputs->set_scroll_follow(true);
+	execute_output_dialog->popup_centered_ratio();
+
+	ExecuteThreadArgs eta;
+	eta.path = p_path;
+	eta.args = p_arguments;
+	eta.execute_output_mutex = Mutex::create();
+	eta.exitcode = 255;
+	eta.done = false;
+
+	int prev_len = 0;
+
+	eta.execute_output_thread = Thread::create(_execute_thread, &eta);
+
+	ERR_FAIL_COND_V(!eta.execute_output_thread, 0);
+
+	while (!eta.done) {
+		eta.execute_output_mutex->lock();
+		if (prev_len != eta.output.length()) {
+			String to_add = eta.output.substr(prev_len, eta.output.length());
+			prev_len = eta.output.length();
+			execute_outputs->add_text(to_add);
+			Main::iteration();
+		}
+		eta.execute_output_mutex->unlock();
+		OS::get_singleton()->delay_usec(1000);
+	}
+
+	Thread::wait_to_finish(eta.execute_output_thread);
+	memdelete(eta.execute_output_thread);
+	memdelete(eta.execute_output_mutex);
+	execute_outputs->add_text("\nExit Code: " + itos(eta.exitcode));
+
+	if (p_close_on_errors && eta.exitcode != 0) {
+		execute_output_dialog->hide();
+	}
+	if (p_close_on_ok && eta.exitcode == 0) {
+		execute_output_dialog->hide();
+	}
+
+	execute_output_dialog->get_ok()->set_disabled(false);
+
+	return eta.exitcode;
+}
+
 EditorNode::EditorNode() {
 
 	Input::get_singleton()->set_use_accumulated_input(true);
@@ -5618,12 +5698,13 @@ EditorNode::EditorNode() {
 	tool_menu = memnew(PopupMenu);
 	tool_menu->set_name("Tools");
 	tool_menu->connect("index_pressed", this, "_tool_menu_option");
+	p->add_separator();
 	p->add_child(tool_menu);
 	p->add_submenu_item(TTR("Tools"), "Tools");
-	tool_menu->add_shortcut(ED_SHORTCUT("editor/orphan_resource_explorer", TTR("Orphan Resource Explorer")), TOOLS_ORPHAN_RESOURCES);
+	tool_menu->add_item(TTR("Orphan Resource Explorer"), TOOLS_ORPHAN_RESOURCES);
+	tool_menu->add_item(TTR("Open Project Data Folder"), RUN_PROJECT_DATA_FOLDER);
 	p->add_separator();
-
-	p->add_shortcut(ED_SHORTCUT("editor/open_project_data_folder", TTR("Open Project Data Folder")), RUN_PROJECT_DATA_FOLDER);
+	p->add_item(TTR("Install Android Build Template"), FILE_INSTALL_ANDROID_SOURCE);
 	p->add_separator();
 
 #ifdef OSX_ENABLED
@@ -5970,6 +6051,24 @@ EditorNode::EditorNode() {
 	save_confirmation->connect("confirmed", this, "_menu_confirm_current");
 	save_confirmation->connect("custom_action", this, "_discard_changes");
 
+	custom_build_manage_templates = memnew(ConfirmationDialog);
+	custom_build_manage_templates->set_text(TTR("Android build template is missing, please install relevant templates."));
+	custom_build_manage_templates->get_ok()->set_text(TTR("Manage Templates"));
+	custom_build_manage_templates->connect("confirmed", this, "_menu_option", varray(SETTINGS_MANAGE_EXPORT_TEMPLATES));
+	gui_base->add_child(custom_build_manage_templates);
+
+	install_android_build_template = memnew(ConfirmationDialog);
+	install_android_build_template->set_text(TTR("This will install the Android project for custom builds.\nNote that, in order to use it, it needs to be enabled per export preset."));
+	install_android_build_template->get_ok()->set_text(TTR("Install"));
+	install_android_build_template->connect("confirmed", this, "_menu_confirm_current");
+	gui_base->add_child(install_android_build_template);
+
+	remove_android_build_template = memnew(ConfirmationDialog);
+	remove_android_build_template->set_text(TTR("Android build template is already installed and it won't be overwritten.\nRemove the \"build\" directory manually before attempting this operation again."));
+	remove_android_build_template->get_ok()->set_text(TTR("Show in File Manager"));
+	remove_android_build_template->connect("confirmed", this, "_menu_option", varray(FILE_EXPLORE_ANDROID_BUILD_TEMPLATES));
+	gui_base->add_child(remove_android_build_template);
+
 	file_templates = memnew(EditorFileDialog);
 	file_templates->set_title(TTR("Import Templates From ZIP File"));
 
@@ -6184,6 +6283,12 @@ EditorNode::EditorNode() {
 	load_error_dialog->set_title(TTR("Load Errors"));
 	gui_base->add_child(load_error_dialog);
 
+	execute_outputs = memnew(RichTextLabel);
+	execute_output_dialog = memnew(AcceptDialog);
+	execute_output_dialog->add_child(execute_outputs);
+	execute_output_dialog->set_title(TTR(""));
+	gui_base->add_child(execute_output_dialog);
+
 	EditorFileSystem::get_singleton()->connect("sources_changed", this, "_sources_changed");
 	EditorFileSystem::get_singleton()->connect("filesystem_changed", this, "_fs_changed");
 	EditorFileSystem::get_singleton()->connect("resources_reimported", this, "_resources_reimported");

+ 21 - 0
editor/editor_node.h

@@ -112,6 +112,16 @@ public:
 		DOCK_SLOT_MAX
 	};
 
+	struct ExecuteThreadArgs {
+		String path;
+		List<String> args;
+		String output;
+		Thread *execute_output_thread;
+		Mutex *execute_output_mutex;
+		int exitcode;
+		volatile bool done;
+	};
+
 private:
 	enum {
 		HISTORY_SIZE = 64
@@ -130,6 +140,8 @@ private:
 		FILE_IMPORT_SUBSCENE,
 		FILE_EXPORT_PROJECT,
 		FILE_EXPORT_MESH_LIBRARY,
+		FILE_INSTALL_ANDROID_SOURCE,
+		FILE_EXPLORE_ANDROID_BUILD_TEMPLATES,
 		FILE_EXPORT_TILESET,
 		FILE_SAVE_OPTIMIZED,
 		FILE_OPEN_RECENT,
@@ -267,6 +279,9 @@ private:
 	RichTextLabel *load_errors;
 	AcceptDialog *load_error_dialog;
 
+	RichTextLabel *execute_outputs;
+	AcceptDialog *execute_output_dialog;
+
 	Ref<Theme> theme;
 
 	PopupMenu *recent_scenes;
@@ -290,6 +305,10 @@ private:
 	PopupMenu *editor_layouts;
 	EditorNameDialog *layout_dialog;
 
+	ConfirmationDialog *custom_build_manage_templates;
+	ConfirmationDialog *install_android_build_template;
+	ConfirmationDialog *remove_android_build_template;
+
 	EditorSettingsDialog *settings_config_dialog;
 	RunSettingsDialog *run_settings_dialog;
 	ProjectSettingsEditor *project_settings;
@@ -800,6 +819,8 @@ public:
 	void update_keying() const { inspector_dock->update_keying(); };
 	bool has_scenes_in_session();
 
+	int execute_and_show_output(const String &p_title, const String &p_path, const List<String> &p_arguments, bool p_close_on_ok = true, bool p_close_on_errors = false);
+
 	EditorNode();
 	~EditorNode();
 	void get_singleton(const char *arg1, bool arg2);

+ 112 - 1
editor/export_template_manager.cpp

@@ -308,7 +308,8 @@ bool ExportTemplateManager::_install_from_file(const String &p_file, bool p_use_
 			p->step(TTR("Importing:") + " " + file, fc);
 		}
 
-		FileAccess *f = FileAccess::open(template_path.plus_file(file), FileAccess::WRITE);
+		String to_write = template_path.plus_file(file);
+		FileAccess *f = FileAccess::open(to_write, FileAccess::WRITE);
 
 		if (!f) {
 			ret = unzGoToNextFile(pkg);
@@ -320,6 +321,10 @@ bool ExportTemplateManager::_install_from_file(const String &p_file, bool p_use_
 
 		memdelete(f);
 
+#ifndef WINDOWS_ENABLED
+		FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF);
+#endif
+
 		ret = unzGoToNextFile(pkg);
 		fc++;
 	}
@@ -541,6 +546,112 @@ void ExportTemplateManager::_notification(int p_what) {
 	}
 }
 
+bool ExportTemplateManager::can_install_android_template() {
+
+	return FileAccess::exists(EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG).plus_file("android_source.zip"));
+}
+
+Error ExportTemplateManager::install_android_template() {
+
+	DirAccessRef da = DirAccess::open("res://");
+	ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
+	//make android dir (if it does not exist)
+
+	da->make_dir("android");
+	{
+		//add an empty .gdignore file to avoid scan
+		FileAccessRef f = FileAccess::open("res://android/.gdignore", FileAccess::WRITE);
+		ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
+		f->store_line("");
+		f->close();
+	}
+	{
+		//add version, to ensure building wont work if template and Godot version are mismatch
+		FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::WRITE);
+		ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
+		f->store_line(VERSION_FULL_CONFIG);
+		f->close();
+	}
+
+	Error err = da->make_dir_recursive("android/build");
+	ERR_FAIL_COND_V(err != OK, err);
+
+	String source_zip = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG).plus_file("android_source.zip");
+	ERR_FAIL_COND_V(!FileAccess::exists(source_zip), ERR_CANT_OPEN);
+
+	FileAccess *src_f = NULL;
+	zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+
+	unzFile pkg = unzOpen2(source_zip.utf8().get_data(), &io);
+	ERR_EXPLAIN("Android sources not in zip format");
+	ERR_FAIL_COND_V(!pkg, ERR_CANT_OPEN);
+
+	int ret = unzGoToFirstFile(pkg);
+
+	int total_files = 0;
+	//count files
+	while (ret == UNZ_OK) {
+		total_files++;
+		ret = unzGoToNextFile(pkg);
+	}
+
+	ret = unzGoToFirstFile(pkg);
+	//decompress files
+	ProgressDialog::get_singleton()->add_task("uncompress", TTR("Uncompressing Android Build Sources"), total_files);
+
+	Set<String> dirs_tested;
+
+	int idx = 0;
+	while (ret == UNZ_OK) {
+
+		//get filename
+		unz_file_info info;
+		char fname[16384];
+		ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0);
+
+		String name = fname;
+
+		String base_dir = name.get_base_dir();
+
+		if (!name.ends_with("/")) {
+			Vector<uint8_t> data;
+			data.resize(info.uncompressed_size);
+
+			//read
+			unzOpenCurrentFile(pkg);
+			unzReadCurrentFile(pkg, data.ptrw(), data.size());
+			unzCloseCurrentFile(pkg);
+
+			if (!dirs_tested.has(base_dir)) {
+				da->make_dir_recursive(String("android/build").plus_file(base_dir));
+				dirs_tested.insert(base_dir);
+			}
+
+			String to_write = String("res://android/build").plus_file(name);
+			FileAccess *f = FileAccess::open(to_write, FileAccess::WRITE);
+			if (f) {
+				f->store_buffer(data.ptr(), data.size());
+				memdelete(f);
+#ifndef WINDOWS_ENABLED
+				FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF);
+#endif
+			} else {
+				ERR_PRINTS("Cant uncompress file: " + to_write);
+			}
+		}
+
+		ProgressDialog::get_singleton()->task_step("uncompress", name, idx);
+
+		idx++;
+		ret = unzGoToNextFile(pkg);
+	}
+
+	ProgressDialog::get_singleton()->end_task("uncompress");
+	unzClose(pkg);
+
+	return OK;
+}
+
 void ExportTemplateManager::_bind_methods() {
 
 	ClassDB::bind_method("_download_template", &ExportTemplateManager::_download_template);

+ 3 - 0
editor/export_template_manager.h

@@ -84,6 +84,9 @@ protected:
 	static void _bind_methods();
 
 public:
+	bool can_install_android_template();
+	Error install_android_template();
+
 	void popup_manager();
 
 	ExportTemplateManager();

+ 0 - 64
methods.py

@@ -211,70 +211,6 @@ def win32_spawn(sh, escape, cmd, args, spawnenv):
 	return exit_code
 """
 
-def android_add_flat_dir(self, dir):
-    if (dir not in self.android_flat_dirs):
-        self.android_flat_dirs.append(dir)
-
-def android_add_maven_repository(self, url):
-    if (url not in self.android_maven_repos):
-        self.android_maven_repos.append(url)
-
-def android_add_dependency(self, depline):
-    if (depline not in self.android_dependencies):
-        self.android_dependencies.append(depline)
-
-def android_add_java_dir(self, subpath):
-    base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + subpath
-    if (base_path not in self.android_java_dirs):
-        self.android_java_dirs.append(base_path)
-
-def android_add_res_dir(self, subpath):
-    base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + subpath
-    if (base_path not in self.android_res_dirs):
-        self.android_res_dirs.append(base_path)
-
-def android_add_asset_dir(self, subpath):
-    base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + subpath
-    if (base_path not in self.android_asset_dirs):
-        self.android_asset_dirs.append(base_path)
-
-def android_add_aidl_dir(self, subpath):
-    base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + subpath
-    if (base_path not in self.android_aidl_dirs):
-        self.android_aidl_dirs.append(base_path)
-
-def android_add_jni_dir(self, subpath):
-    base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + subpath
-    if (base_path not in self.android_jni_dirs):
-        self.android_jni_dirs.append(base_path)
-
-def android_add_gradle_plugin(self, plugin):
-    if (plugin not in self.android_gradle_plugins):
-        self.android_gradle_plugins.append(plugin)
-
-def android_add_gradle_classpath(self, classpath):
-    if (classpath not in self.android_gradle_classpath):
-        self.android_gradle_classpath.append(classpath)
-
-def android_add_default_config(self, config):
-    if (config not in self.android_default_config):
-        self.android_default_config.append(config)
-
-def android_add_to_manifest(self, file):
-    base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + file
-    with open(base_path, "r") as f:
-        self.android_manifest_chunk += f.read()
-
-def android_add_to_permissions(self, file):
-    base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + file
-    with open(base_path, "r") as f:
-        self.android_permission_chunk += f.read()
-
-def android_add_to_attributes(self, file):
-    base_path = self.Dir(".").abspath + "/modules/" + self.current_module + "/" + file
-    with open(base_path, "r") as f:
-        self.android_appattributes_chunk += f.read()
-
 def disable_module(self):
     self.disabled_modules.append(self.current_module)
 

+ 0 - 108
platform/android/SCsub

@@ -34,114 +34,6 @@ env_thirdparty = env_android.Clone()
 env_thirdparty.disable_warnings()
 android_objects.append(env_thirdparty.SharedObject('#thirdparty/misc/ifaddrs-android.cc'))
 
-abspath = env.Dir(".").abspath
-
-with open_utf8(abspath + "/build.gradle.template", "r") as gradle_basein:
-	gradle_text = gradle_basein.read()
-
-gradle_maven_flat_text = ""
-if len(env.android_flat_dirs) > 0:
-    gradle_maven_flat_text += "flatDir {\n"
-    gradle_maven_flat_text += "\tdirs "
-    for x in env.android_flat_dirs:
-        gradle_maven_flat_text += "'" + x + "',"
-
-    gradle_maven_flat_text = gradle_maven_flat_text[:-1]
-    gradle_maven_flat_text += "\n\t}\n"
-
-gradle_maven_repos_text = ""
-gradle_maven_repos_text += gradle_maven_flat_text
-
-if len(env.android_maven_repos) > 0:
-    gradle_maven_repos_text += ""
-    for x in env.android_maven_repos:
-        gradle_maven_repos_text += "\tmaven {\n"
-        gradle_maven_repos_text += "\t" + x + "\n"
-        gradle_maven_repos_text += "\t}\n"
-
-gradle_maven_dependencies_text = ""
-
-for x in env.android_dependencies:
-    gradle_maven_dependencies_text += x + "\n\t"
-
-gradle_java_dirs_text = ""
-
-for x in env.android_java_dirs:
-    gradle_java_dirs_text += ",'" + x.replace("\\", "/") + "'"
-
-gradle_plugins = ""
-for x in env.android_gradle_plugins:
-	gradle_plugins += "apply plugin: \"" + x + "\"\n"
-
-gradle_classpath = ""
-for x in env.android_gradle_classpath:
-	gradle_classpath += "\t\tclasspath \"" + x + "\"\n"
-
-gradle_res_dirs_text = ""
-
-for x in env.android_res_dirs:
-    gradle_res_dirs_text += ",'" + x.replace("\\", "/") + "'"
-
-gradle_aidl_dirs_text = ""
-
-for x in env.android_aidl_dirs:
-    gradle_aidl_dirs_text += ",'" + x.replace("\\", "/") + "'"
-
-gradle_jni_dirs_text = ""
-
-for x in env.android_jni_dirs:
-    gradle_jni_dirs_text += ",'" + x.replace("\\", "/") + "'"
-
-gradle_asset_dirs_text = ""
-
-for x in env.android_asset_dirs:
-    gradle_asset_dirs_text += ",'" + x.replace("\\", "/") + "'"
-
-gradle_default_config_text = ""
-
-minSdk = 18
-targetSdk = 28
-
-for x in env.android_default_config:
-    if x.startswith("minSdkVersion") and int(x.split(" ")[-1]) < minSdk:
-        x = "minSdkVersion " + str(minSdk)
-    if x.startswith("targetSdkVersion") and int(x.split(" ")[-1]) > targetSdk:
-        x = "targetSdkVersion " + str(targetSdk)
-
-    gradle_default_config_text += x + "\n\t\t"
-
-if "minSdkVersion" not in gradle_default_config_text:
-    gradle_default_config_text += ("minSdkVersion " + str(minSdk) + "\n\t\t")
-
-if "targetSdkVersion" not in gradle_default_config_text:
-    gradle_default_config_text += ("targetSdkVersion " + str(targetSdk) + "\n\t\t")
-
-gradle_text = gradle_text.replace("$$GRADLE_REPOSITORY_URLS$$", gradle_maven_repos_text)
-gradle_text = gradle_text.replace("$$GRADLE_DEPENDENCIES$$", gradle_maven_dependencies_text)
-gradle_text = gradle_text.replace("$$GRADLE_JAVA_DIRS$$", gradle_java_dirs_text)
-gradle_text = gradle_text.replace("$$GRADLE_RES_DIRS$$", gradle_res_dirs_text)
-gradle_text = gradle_text.replace("$$GRADLE_ASSET_DIRS$$", gradle_asset_dirs_text)
-gradle_text = gradle_text.replace("$$GRADLE_AIDL_DIRS$$", gradle_aidl_dirs_text)
-gradle_text = gradle_text.replace("$$GRADLE_JNI_DIRS$$", gradle_jni_dirs_text)
-gradle_text = gradle_text.replace("$$GRADLE_DEFAULT_CONFIG$$", gradle_default_config_text)
-gradle_text = gradle_text.replace("$$GRADLE_PLUGINS$$", gradle_plugins)
-gradle_text = gradle_text.replace("$$GRADLE_CLASSPATH$$", gradle_classpath)
-
-with open_utf8(abspath + "/java/build.gradle", "w") as gradle_baseout:
-	gradle_baseout.write(gradle_text)
-
-
-with open_utf8(abspath + "/AndroidManifest.xml.template", "r") as pp_basein:
-	manifest = pp_basein.read()
-
-manifest = manifest.replace("$$ADD_APPLICATION_CHUNKS$$", env.android_manifest_chunk)
-manifest = manifest.replace("$$ADD_PERMISSION_CHUNKS$$", env.android_permission_chunk)
-manifest = manifest.replace("$$ADD_APPATTRIBUTE_CHUNKS$$", env.android_appattributes_chunk)
-
-with open_utf8(abspath + "/java/AndroidManifest.xml", "w") as pp_baseout:
-	pp_baseout.write(manifest)
-
-
 lib = env_android.add_shared_library("#bin/libgodot", [android_objects], SHLIBSUFFIX=env["SHLIBSUFFIX"])
 
 lib_arch_dir = ''

+ 0 - 88
platform/android/build.gradle.template

@@ -1,88 +0,0 @@
-buildscript {
-	repositories {
-		google()
-		jcenter()
-		$$GRADLE_REPOSITORY_URLS$$
-	}
-	dependencies {
-		classpath 'com.android.tools.build:gradle:3.2.1'
-		$$GRADLE_CLASSPATH$$
-	}
-}
-
-apply plugin: 'com.android.application'
-
-allprojects {
-    repositories {
-	mavenCentral()
-	google()
-	jcenter()
-	$$GRADLE_REPOSITORY_URLS$$
-    }
-}
-
-dependencies {
-	implementation "com.android.support:support-core-utils:28.0.0"
-	$$GRADLE_DEPENDENCIES$$
-}
-
-android {
-
-	lintOptions {
-		abortOnError false
-		disable 'MissingTranslation','UnusedResources'
-	}
-
-	compileSdkVersion 28
-	buildToolsVersion "28.0.3"
-	useLibrary 'org.apache.http.legacy'
-
-	packagingOptions {
-		exclude 'META-INF/LICENSE'
-		exclude 'META-INF/NOTICE'
-	}
-	defaultConfig {
-		$$GRADLE_DEFAULT_CONFIG$$
-	}
-	// Both signing and zip-aligning will be done at export time
-	buildTypes.all { buildType ->
-		buildType.zipAlignEnabled false
-		buildType.signingConfig null
-	}
-	sourceSets {
-		main {
-			manifest.srcFile 'AndroidManifest.xml'
-			java.srcDirs = ['src'
-				$$GRADLE_JAVA_DIRS$$
-			]
-			res.srcDirs = [
-				'res'
-				$$GRADLE_RES_DIRS$$
-			]
-			aidl.srcDirs = [
-				'aidl'
-				$$GRADLE_AIDL_DIRS$$
-			]
-			assets.srcDirs = [
-				'assets'
-				$$GRADLE_ASSET_DIRS$$
-			]
-		}
-		debug.jniLibs.srcDirs = [
-			'libs/debug'
-			$$GRADLE_JNI_DIRS$$
-		]
-		release.jniLibs.srcDirs = [
-			'libs/release'
-			$$GRADLE_JNI_DIRS$$
-		]
-	}
-
-	applicationVariants.all { variant ->
-		variant.outputs.all { output ->
-			output.outputFileName = "../../../../../../../bin/android_${variant.name}.apk"
-		}
-	}
-}
-
-$$GRADLE_PLUGINS$$

+ 375 - 24
platform/android/export/export.cpp

@@ -417,6 +417,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
 			name = "noname";
 
 		pname = pname.replace("$genname", name);
+
 		return pname;
 	}
 
@@ -1143,11 +1144,12 @@ public:
 		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), true));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
+		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_package/use_custom_build"), false));
 		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::STRING, "version/name"), "1.0"));
-		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.$genname"), ""));
-		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
+		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname"));
+		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), ""));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true));
 		r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "screen/orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait"), 0));
@@ -1388,21 +1390,25 @@ public:
 	virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
 
 		String err;
-		r_missing_templates = find_export_template("android_debug.apk") == String() || find_export_template("android_release.apk") == String();
 
-		if (p_preset->get("custom_package/debug") != "") {
-			if (FileAccess::exists(p_preset->get("custom_package/debug"))) {
-				r_missing_templates = false;
-			} else {
-				err += TTR("Custom debug template not found.") + "\n";
+		if (!bool(p_preset->get("custom_package/use_custom_build"))) {
+
+			r_missing_templates = find_export_template("android_debug.apk") == String() || find_export_template("android_release.apk") == String();
+
+			if (p_preset->get("custom_package/debug") != "") {
+				if (FileAccess::exists(p_preset->get("custom_package/debug"))) {
+					r_missing_templates = false;
+				} else {
+					err += TTR("Custom debug template not found.") + "\n";
+				}
 			}
-		}
 
-		if (p_preset->get("custom_package/release") != "") {
-			if (FileAccess::exists(p_preset->get("custom_package/release"))) {
-				r_missing_templates = false;
-			} else {
-				err += TTR("Custom release template not found.") + "\n";
+			if (p_preset->get("custom_package/release") != "") {
+				if (FileAccess::exists(p_preset->get("custom_package/release"))) {
+					r_missing_templates = false;
+				} else {
+					err += TTR("Custom release template not found.") + "\n";
+				}
 			}
 		}
 
@@ -1435,6 +1441,30 @@ public:
 			}
 		}
 
+		if (bool(p_preset->get("custom_package/use_custom_build"))) {
+			String sdk_path = EditorSettings::get_singleton()->get("export/android/custom_build_sdk_path");
+			if (sdk_path == "") {
+				err += TTR("Custom build requires a valid Android SDK path in Editor Settings.") + "\n";
+				valid = false;
+			} else {
+				Error errn;
+				DirAccess *da = DirAccess::open(sdk_path.plus_file("tools"), &errn);
+				if (errn != OK) {
+					err += TTR("Invalid Android SDK path for custom build in Editor Settings.") + "\n";
+					valid = false;
+				}
+				if (da) {
+					memdelete(da);
+				}
+			}
+
+			if (!FileAccess::exists("res://android/build/build.gradle")) {
+
+				err += TTR("Android project is not installed for compiling. Install from Editor menu.") + "\n";
+				valid = false;
+			}
+		}
+
 		bool apk_expansion = p_preset->get("apk_expansion/enable");
 
 		if (apk_expansion) {
@@ -1473,6 +1503,260 @@ public:
 		return list;
 	}
 
+	void _update_custom_build_project() {
+
+		DirAccessRef da = DirAccess::open("res://android");
+
+		ERR_FAIL_COND(!da);
+		Map<String, List<String> > directory_paths;
+		Map<String, List<String> > manifest_sections;
+		Map<String, List<String> > gradle_sections;
+		da->list_dir_begin();
+		String d = da->get_next();
+		while (d != String()) {
+
+			if (!d.begins_with(".") && d != "build" && da->current_is_dir()) { //a dir and not the build dir
+				//add directories found
+				DirAccessRef ds = DirAccess::open(String("res://android").plus_file(d));
+				if (ds) {
+					ds->list_dir_begin();
+					String sd = ds->get_next();
+					while (sd != String()) {
+
+						if (!sd.begins_with(".") && ds->current_is_dir()) {
+							String key = sd.to_upper();
+							if (!directory_paths.has(key)) {
+								directory_paths[key] = List<String>();
+							}
+							String path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android").plus_file(d).plus_file(sd);
+							directory_paths[key].push_back(path);
+							print_line("Add: " + sd + ":" + path);
+						}
+
+						sd = ds->get_next();
+					}
+					ds->list_dir_end();
+				}
+				//parse manifest
+				{
+					FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("AndroidManifest.conf"), FileAccess::READ);
+					if (f) {
+
+						String section;
+						while (!f->eof_reached()) {
+							String l = f->get_line();
+							String k = l.strip_edges();
+							if (k.begins_with("[")) {
+								section = k.substr(1, k.length() - 2).strip_edges().to_upper();
+								print_line("Section: " + section);
+							} else if (k != String()) {
+								if (!manifest_sections.has(section)) {
+									manifest_sections[section] = List<String>();
+								}
+								manifest_sections[section].push_back(l);
+							}
+						}
+
+						f->close();
+					}
+				}
+				//parse gradle
+				{
+					FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("gradle.conf"), FileAccess::READ);
+					if (f) {
+
+						String section;
+						while (!f->eof_reached()) {
+							String l = f->get_line().strip_edges();
+							String k = l.strip_edges();
+							if (k.begins_with("[")) {
+								section = k.substr(1, k.length() - 2).strip_edges().to_upper();
+								print_line("Section: " + section);
+							} else if (k != String()) {
+								if (!gradle_sections.has(section)) {
+									gradle_sections[section] = List<String>();
+								}
+								gradle_sections[section].push_back(l);
+							}
+						}
+					}
+				}
+			}
+			d = da->get_next();
+		}
+		da->list_dir_end();
+
+		{ //fix gradle build
+
+			String new_file;
+			{
+				FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::READ);
+				if (f) {
+
+					while (!f->eof_reached()) {
+						String l = f->get_line();
+
+						if (l.begins_with("//CHUNK_")) {
+							String text = l.replace_first("//CHUNK_", "");
+							int begin_pos = text.find("_BEGIN");
+							if (begin_pos != -1) {
+								text = text.substr(0, begin_pos);
+								text = text.to_upper(); //just in case
+
+								String end_marker = "//CHUNK_" + text + "_END";
+								size_t pos = f->get_position();
+								bool found = false;
+								while (!f->eof_reached()) {
+									l = f->get_line();
+									if (l.begins_with(end_marker)) {
+										found = true;
+										break;
+									}
+								}
+
+								new_file += "//CHUNK_" + text + "_BEGIN\n";
+
+								if (!found) {
+									ERR_PRINTS("No end marker found in build.gradle for chunk: " + text);
+									f->seek(pos);
+								} else {
+
+									//add chunk lines
+									if (gradle_sections.has(text)) {
+										for (List<String>::Element *E = gradle_sections[text].front(); E; E = E->next()) {
+											new_file += E->get() + "\n";
+										}
+									}
+									new_file += end_marker + "\n";
+								}
+							} else {
+								new_file += l + "\n"; //pass line by
+							}
+						} else if (l.begins_with("//DIR_")) {
+							String text = l.replace_first("//DIR_", "");
+							int begin_pos = text.find("_BEGIN");
+							if (begin_pos != -1) {
+								text = text.substr(0, begin_pos);
+								text = text.to_upper(); //just in case
+
+								String end_marker = "//DIR_" + text + "_END";
+								size_t pos = f->get_position();
+								bool found = false;
+								while (!f->eof_reached()) {
+									l = f->get_line();
+									if (l.begins_with(end_marker)) {
+										found = true;
+										break;
+									}
+								}
+
+								new_file += "//DIR_" + text + "_BEGIN\n";
+
+								if (!found) {
+									ERR_PRINTS("No end marker found in build.gradle for dir: " + text);
+									f->seek(pos);
+								} else {
+									//add chunk lines
+									if (directory_paths.has(text)) {
+										for (List<String>::Element *E = directory_paths[text].front(); E; E = E->next()) {
+											new_file += ",'" + E->get().replace("'", "\'") + "'";
+											new_file += "\n";
+										}
+									}
+									new_file += end_marker + "\n";
+								}
+							} else {
+								new_file += l + "\n"; //pass line by
+							}
+
+						} else {
+							new_file += l + "\n";
+						}
+					}
+				}
+			}
+
+			FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::WRITE);
+			f->store_string(new_file);
+			f->close();
+		}
+
+		{ //fix manifest
+
+			String new_file;
+			{
+				FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::READ);
+				if (f) {
+
+					while (!f->eof_reached()) {
+						String l = f->get_line();
+
+						if (l.begins_with("<!--CHUNK_")) {
+							String text = l.replace_first("<!--CHUNK_", "");
+							int begin_pos = text.find("_BEGIN-->");
+							if (begin_pos != -1) {
+								text = text.substr(0, begin_pos);
+								text = text.to_upper(); //just in case
+
+								String end_marker = "<!--CHUNK_" + text + "_END-->";
+								size_t pos = f->get_position();
+								bool found = false;
+								while (!f->eof_reached()) {
+									l = f->get_line();
+									if (l.begins_with(end_marker)) {
+										found = true;
+										break;
+									}
+								}
+
+								new_file += "<!--CHUNK_" + text + "_BEGIN-->\n";
+
+								if (!found) {
+									ERR_PRINTS("No end marker found in AndroidManifest.conf for chunk: " + text);
+									f->seek(pos);
+								} else {
+									//add chunk lines
+									if (manifest_sections.has(text)) {
+										for (List<String>::Element *E = manifest_sections[text].front(); E; E = E->next()) {
+											new_file += E->get() + "\n";
+										}
+									}
+									new_file += end_marker + "\n";
+								}
+							} else {
+								new_file += l + "\n"; //pass line by
+							}
+
+						} else if (l.strip_edges().begins_with("<application")) {
+							String last_tag = "android:icon=\"@drawable/icon\"";
+							int last_tag_pos = l.find(last_tag);
+							if (last_tag_pos == -1) {
+								WARN_PRINTS("No adding of application tags because could not find last tag for <application: " + last_tag);
+								new_file += l + "\n";
+							} else {
+								String base = l.substr(0, last_tag_pos + last_tag.length());
+								if (manifest_sections.has("application_tags")) {
+									for (List<String>::Element *E = manifest_sections["application_tags"].front(); E; E = E->next()) {
+										String to_add = E->get().strip_edges();
+										base += " " + to_add + " ";
+									}
+								}
+								base += ">\n";
+								new_file += base;
+							}
+						} else {
+							new_file += l + "\n";
+						}
+					}
+				}
+			}
+
+			FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::WRITE);
+			f->store_string(new_file);
+			f->close();
+		}
+	}
+
 	virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) {
 
 		ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
@@ -1481,21 +1765,86 @@ public:
 
 		EditorProgress ep("export", "Exporting for Android", 105);
 
-		if (p_debug)
-			src_apk = p_preset->get("custom_package/debug");
-		else
-			src_apk = p_preset->get("custom_package/release");
+		if (bool(p_preset->get("custom_package/use_custom_build"))) { //custom build
+			//re-generate build.gradle and AndroidManifest.xml
 
-		src_apk = src_apk.strip_edges();
-		if (src_apk == "") {
+			{ //test that installed build version is alright
+				FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
+				if (!f) {
+					EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
+					return ERR_UNCONFIGURED;
+				}
+				String version = f->get_line().strip_edges();
+				if (version != VERSION_FULL_CONFIG) {
+					EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n   Template installed: %s\n   Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
+					return ERR_UNCONFIGURED;
+				}
+			}
+			//build project if custom build is enabled
+			String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
+
+			ERR_FAIL_COND_V(sdk_path == "", ERR_UNCONFIGURED);
+
+			_update_custom_build_project();
+
+			OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required
+
+			String build_command;
+#ifdef WINDOWS_ENABLED
+			build_command = "gradlew.bat";
+#else
+			build_command = "gradlew";
+#endif
+
+			String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
+
+			build_command = build_path.plus_file(build_command);
+
+			List<String> cmdline;
+			cmdline.push_back("build");
+			cmdline.push_back("-p");
+			cmdline.push_back(build_path);
+			/*{ used for debug
+				int ec;
+				String pipe;
+				OS::get_singleton()->execute(build_command, cmdline, true, NULL, NULL, &ec);
+				print_line("exit code: " + itos(ec));
+			}
+			*/
+			int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline);
+			if (result != 0) {
+				EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation."));
+				return ERR_CANT_CREATE;
+			}
 			if (p_debug) {
-				src_apk = find_export_template("android_debug.apk");
+				src_apk = build_path.plus_file("build/outputs/apk/debug/build-debug-unsigned.apk");
 			} else {
-				src_apk = find_export_template("android_release.apk");
+				src_apk = build_path.plus_file("build/outputs/apk/release/build-release-unsigned.apk");
+			}
+
+			if (!FileAccess::exists(src_apk)) {
+				EditorNode::get_singleton()->show_warning(TTR("No build apk generated at: ") + "\n" + src_apk);
+				return ERR_CANT_CREATE;
 			}
+
+		} else {
+
+			if (p_debug)
+				src_apk = p_preset->get("custom_package/debug");
+			else
+				src_apk = p_preset->get("custom_package/release");
+
+			src_apk = src_apk.strip_edges();
 			if (src_apk == "") {
-				EditorNode::add_io_error("Package not found: " + src_apk);
-				return ERR_FILE_NOT_FOUND;
+				if (p_debug) {
+					src_apk = find_export_template("android_debug.apk");
+				} else {
+					src_apk = find_export_template("android_release.apk");
+				}
+				if (src_apk == "") {
+					EditorNode::add_io_error("Package not found: " + src_apk);
+					return ERR_FILE_NOT_FOUND;
+				}
 			}
 		}
 
@@ -1975,6 +2324,8 @@ void register_android_exporter() {
 	EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey");
 	EDITOR_DEF("export/android/debug_keystore_pass", "android");
 	EDITOR_DEF("export/android/force_system_user", false);
+	EDITOR_DEF("export/android/custom_build_sdk_path", "");
+	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/custom_build_sdk_path", PROPERTY_HINT_GLOBAL_DIR, "*.keystore"));
 
 	EDITOR_DEF("export/android/timestamping_authority_url", "");
 	EDITOR_DEF("export/android/shutdown_adb_on_exit", true);

+ 22 - 4
platform/android/AndroidManifest.xml.template → platform/android/java/AndroidManifest.xml

@@ -11,11 +11,20 @@
                       android:largeScreens="true"
                       android:xlargeScreens="true"/>
 
+<!--glEsVersion is modified by the exporter, changing this value here has no effect-->
     <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+<!--Adding custom text to manifest is fine, but do it outside the custom user and application BEGIN/ENDregions, as that gets rewritten-->
 
-$$ADD_PERMISSION_CHUNKS$$
+<!--Custom permissions XML added by add-ons. It's recommended to add them from the export preset, though-->
+<!--CHUNK_USER_PERMISSIONS_BEGIN-->
+<!--CHUNK_USER_PERMISSIONS_END-->
+
+<!--Anything in this line after the icon will be erased when doing custom build. If you want to add tags manually, do before it.-->
+    <application android:label="@string/godot_project_name_string" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" android:icon="@drawable/icon">
+
+<!--The following values are replaced when Godot exports, modifying them here has no effect. Do theses changes in the-->
+<!--export preset. Adding new ones is fine.-->
 
-    <application android:label="@string/godot_project_name_string" android:icon="@drawable/icon" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" $$ADD_APPATTRIBUTE_CHUNKS$$ >
         <activity android:name="org.godotengine.godot.Godot"
                   android:label="@string/godot_project_name_string"
                   android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
@@ -32,13 +41,22 @@ $$ADD_PERMISSION_CHUNKS$$
         </activity>
     <service android:name="org.godotengine.godot.GodotDownloaderService" />
 
-$$ADD_APPLICATION_CHUNKS$$
+<!--Custom application XML added by add-ons-->
+<!--CHUNK_APPLICATION_BEGIN-->
+<activity android:name="com.google.android.gms.ads.AdActivity"
+	android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"/>
+<meta-data android:name="com.google.android.gms.version"
+	android:value="@integer/google_play_services_version" />
+<meta-data
+        android:name="com.google.android.gms.ads.AD_MANAGER_APP"
+        android:value="true"/>
+<!--CHUNK_APPLICATION_END-->
 
     </application>
 
     <instrumentation android:icon="@drawable/icon"
                      android:label="@string/godot_project_name_string"
                      android:name="org.godotengine.godot.GodotInstrumentation"
-                     android:targetPackage="com.godot.game" />
+                     android:targetPackage="org.godotengine.game" />
 
 </manifest>

+ 115 - 0
platform/android/java/build.gradle

@@ -0,0 +1,115 @@
+//Gradle project for Godot Engine Android port.
+//Do not modify code between the BEGIN/END sections, as it's autogenerated by add-ons
+
+buildscript {
+	repositories {
+		google()
+		jcenter()
+//CHUNK_BUILDSCRIPT_REPOSITORIES_BEGIN
+//CHUNK_BUILDSCRIPT_REPOSITORIES_END
+	}
+	dependencies {
+		classpath 'com.android.tools.build:gradle:3.2.1'
+//CHUNK_BUILD_DEPENDENCIES_BEGIN
+//CHUNK_BUILD_DEPENDENCIES_END
+	}
+}
+
+apply plugin: 'com.android.application'
+
+allprojects {
+    repositories {
+	mavenCentral()
+	google()
+	jcenter()
+//CHUNK_ALLPROJECTS_REPOSITORIES_BEGIN
+//CHUNK_ALLPROJECTS_REPOSITORIES_END
+
+    }
+}
+
+dependencies {
+	implementation "com.android.support:support-core-utils:28.0.0"
+//CHUNK_DEPENDENCIES_BEGIN
+compile ('com.google.android.gms:play-services-ads:16.0.0') { exclude group: 'com.android.support' }
+//CHUNK_DEPENDENCIES_END
+}
+
+android {
+
+	lintOptions {
+		abortOnError false
+		disable 'MissingTranslation','UnusedResources'
+	}
+
+	compileSdkVersion 28
+	buildToolsVersion "28.0.3"
+	useLibrary 'org.apache.http.legacy'
+
+	packagingOptions {
+		exclude 'META-INF/LICENSE'
+		exclude 'META-INF/NOTICE'
+	}
+	defaultConfig {
+		minSdkVersion 18
+		targetSdkVersion 28
+//CHUNK_ANDROID_DEFAULTCONFIG_BEGIN
+//CHUNK_ANDROID_DEFAULTCONFIG_END
+	}
+	// Both signing and zip-aligning will be done at export time
+	buildTypes.all { buildType ->
+		buildType.zipAlignEnabled false
+		buildType.signingConfig null
+	}
+	sourceSets {
+		main {
+			manifest.srcFile 'AndroidManifest.xml'
+			java.srcDirs = ['src'
+//DIR_SRC_BEGIN
+,'/home/red/coding/godot-demos/2d/platformer/android/admob/src'
+//DIR_SRC_END
+			]
+			res.srcDirs = [
+				'res'
+//DIR_RES_BEGIN
+//DIR_RES_END
+			]
+			aidl.srcDirs = [
+				'aidl'
+//DIR_AIDL_BEGIN
+//DIR_AIDL_END
+			]
+			assets.srcDirs = [
+				'assets'
+//DIR_ASSETS_BEGIN
+//DIR_ASSETS_END
+
+			]
+		}
+		debug.jniLibs.srcDirs = [
+			'libs/debug'
+//DIR_JNI_DEBUG_BEGIN
+//DIR_JNI_DEBUG_END
+		]
+		release.jniLibs.srcDirs = [
+			'libs/release'
+//DIR_JNI_RELEASE_BEGIN
+//DIR_JNI_RELEASE_END
+		]
+	}
+// No longer used, as it's not useful for build source template
+//	applicationVariants.all { variant ->
+//		variant.outputs.all { output ->
+//			output.outputFileName = "../../../../../../../bin/android_${variant.name}.apk"
+//		}
+//	}
+
+}
+
+//CHUNK_GLOBAL_BEGIN
+//CHUNK_GLOBAL_END
+
+
+
+
+

+ 7 - 1
platform/windows/os_windows.cpp

@@ -2460,7 +2460,7 @@ void OS_Windows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent,
 	DeleteDC(hMainDC);
 }
 
-Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr) {
+Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
 
 	if (p_blocking && r_pipe) {
 
@@ -2479,7 +2479,13 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
 		char buf[65535];
 		while (fgets(buf, 65535, f)) {
 
+			if (p_pipe_mutex) {
+				p_pipe_mutex->lock();
+			}
 			(*r_pipe) += buf;
+			if (p_pipe_mutex) {
+				p_pipe_mutex->lock();
+			}
 		}
 
 		int rv = _pclose(f);

+ 1 - 1
platform/windows/os_windows.h

@@ -261,7 +261,7 @@ public:
 	virtual void delay_usec(uint32_t p_usec) const;
 	virtual uint64_t get_ticks_usec() const;
 
-	virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false);
+	virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL);
 	virtual Error kill(const ProcessID &p_pid);
 	virtual int get_process_id() const;