Jelajahi Sumber

Merge pull request #101061 from syntaxerror247/first-pr-2025

Android: Implement support for `native dialog`
Rémi Verschelde 8 bulan lalu
induk
melakukan
4d1cfc1784

+ 1 - 1
doc/classes/DisplayServer.xml

@@ -123,7 +123,7 @@
 			<param index="3" name="callback" type="Callable" />
 			<description>
 				Shows a text dialog which uses the operating system's native look-and-feel. [param callback] should accept a single [int] parameter which corresponds to the index of the pressed button.
-				[b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include macOS and Windows.
+				[b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include macOS, Windows, and Android.
 			</description>
 		</method>
 		<method name="enable_for_stealing_focus">

+ 1 - 0
platform/android/SCsub

@@ -29,6 +29,7 @@ android_files = [
     "plugin/godot_plugin_jni.cpp",
     "rendering_context_driver_vulkan_android.cpp",
     "variant/callable_jni.cpp",
+    "dialog_utils_jni.cpp",
 ]
 
 env_android = env.Clone()

+ 52 - 0
platform/android/dialog_utils_jni.cpp

@@ -0,0 +1,52 @@
+/**************************************************************************/
+/*  dialog_utils_jni.cpp                                                  */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "dialog_utils_jni.h"
+
+#include "display_server_android.h"
+#include "jni_utils.h"
+
+extern "C" {
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_dialogCallback(JNIEnv *env, jclass clazz, jint p_button_index) {
+	DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
+	if (ds) {
+		ds->emit_dialog_callback(p_button_index);
+	}
+}
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text) {
+	DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
+	if (ds) {
+		String text = jstring_to_string(p_text, env);
+		ds->emit_input_dialog_callback(text);
+	}
+}
+}

+ 41 - 0
platform/android/dialog_utils_jni.h

@@ -0,0 +1,41 @@
+/**************************************************************************/
+/*  dialog_utils_jni.h                                                    */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef DIALOG_UTILS_JNI_H
+#define DIALOG_UTILS_JNI_H
+
+#include <jni.h>
+
+extern "C" {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_dialogCallback(JNIEnv *env, jclass clazz, jint p_button_index);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text);
+}
+
+#endif // DIALOG_UTILS_JNI_H

+ 14 - 1
platform/android/display_server_android.cpp

@@ -70,7 +70,7 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const {
 		//case FEATURE_IME:
 		case FEATURE_MOUSE:
 		//case FEATURE_MOUSE_WARP:
-		//case FEATURE_NATIVE_DIALOG:
+		case FEATURE_NATIVE_DIALOG:
 		case FEATURE_NATIVE_DIALOG_INPUT:
 		case FEATURE_NATIVE_DIALOG_FILE:
 		//case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
@@ -178,6 +178,19 @@ bool DisplayServerAndroid::clipboard_has() const {
 	}
 }
 
+Error DisplayServerAndroid::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) {
+	GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+	ERR_FAIL_NULL_V(godot_java, FAILED);
+	dialog_callback = p_callback;
+	return godot_java->show_dialog(p_title, p_description, p_buttons);
+}
+
+void DisplayServerAndroid::emit_dialog_callback(int p_button_index) {
+	if (dialog_callback.is_valid()) {
+		dialog_callback.call_deferred(p_button_index);
+	}
+}
+
 Error DisplayServerAndroid::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) {
 	GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
 	ERR_FAIL_NULL_V(godot_java, FAILED);

+ 5 - 0
platform/android/display_server_android.h

@@ -87,7 +87,9 @@ class DisplayServerAndroid : public DisplayServer {
 
 	Callable system_theme_changed;
 
+	Callable dialog_callback;
 	Callable input_dialog_callback;
+
 	Callable file_picker_callback;
 
 	void _window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred = false) const;
@@ -119,6 +121,9 @@ public:
 	virtual String clipboard_get() const override;
 	virtual bool clipboard_has() const override;
 
+	virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override;
+	void emit_dialog_callback(int p_button_index);
+
 	virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
 	void emit_input_dialog_callback(String p_text);
 

+ 4 - 2
platform/android/java/lib/res/values/dimens.xml

@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
 	<dimen name="text_edit_height">48dp</dimen>
-	<dimen name="input_dialog_padding_horizontal">10dp</dimen>
-	<dimen name="input_dialog_padding_vertical">5dp</dimen>
+	<dimen name="button_height">48dp</dimen>
+	<dimen name="button_padding">10dp</dimen>
+	<dimen name="dialog_padding_horizontal">16dp</dimen>
+	<dimen name="dialog_padding_vertical">8dp</dimen>
 </resources>

+ 19 - 19
platform/android/java/lib/src/org/godotengine/godot/Godot.kt

@@ -44,7 +44,6 @@ import android.os.*
 import android.util.Log
 import android.util.TypedValue
 import android.view.*
-import android.widget.EditText
 import android.widget.FrameLayout
 import androidx.annotation.Keep
 import androidx.annotation.StringRes
@@ -65,6 +64,7 @@ import org.godotengine.godot.plugin.GodotPlugin
 import org.godotengine.godot.plugin.GodotPluginRegistry
 import org.godotengine.godot.tts.GodotTTS
 import org.godotengine.godot.utils.CommandLineFileParser
+import org.godotengine.godot.utils.DialogUtils
 import org.godotengine.godot.utils.GodotNetUtils
 import org.godotengine.godot.utils.PermissionsUtil
 import org.godotengine.godot.utils.PermissionsUtil.requestPermission
@@ -903,27 +903,27 @@ class Godot(private val context: Context) {
 	}
 
 	/**
-	 * Popup a dialog to input text.
+	 * This method shows a dialog with multiple buttons.
+	 *
+	 * @param title The title of the dialog.
+	 * @param message The message displayed in the dialog.
+	 * @param buttons An array of button labels to display.
+	 */
+	@Keep
+	private fun showDialog(title: String, message: String, buttons: Array<String>) {
+		getActivity()?.let { DialogUtils.showDialog(it, title, message, buttons) }
+	}
+
+	/**
+	 * This method shows a dialog with a text input field, allowing the user to input text.
+	 *
+	 * @param title The title of the input dialog.
+	 * @param message The message displayed in the input dialog.
+	 * @param existingText The existing text that will be pre-filled in the input field.
 	 */
 	@Keep
 	private fun showInputDialog(title: String, message: String, existingText: String) {
-		val activity: Activity = getActivity() ?: return
-		val inputField = EditText(activity)
-		val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.input_dialog_padding_horizontal)
-		val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.input_dialog_padding_vertical)
-		inputField.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
-		inputField.setText(existingText)
-		runOnUiThread {
-			val builder = AlertDialog.Builder(activity)
-			builder.setMessage(message).setTitle(title).setView(inputField)
-			builder.setPositiveButton(R.string.dialog_ok) {
-				dialog: DialogInterface, id: Int ->
-				GodotLib.inputDialogCallback(inputField.text.toString())
-				dialog.dismiss()
-			}
-			val dialog = builder.create()
-			dialog.show()
-		}
+		getActivity()?.let { DialogUtils.showInputDialog(it, title, message, existingText) }
 	}
 
 	@Keep

+ 0 - 5
platform/android/java/lib/src/org/godotengine/godot/GodotLib.java

@@ -235,11 +235,6 @@ public class GodotLib {
 	 */
 	public static native void onNightModeChanged();
 
-	/**
-	 * Invoked on the input dialog submitted.
-	 */
-	public static native void inputDialogCallback(String p_text);
-
 	/**
 	 * Invoked on the file picker closed.
 	 */

+ 185 - 0
platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt

@@ -0,0 +1,185 @@
+/**************************************************************************/
+/*  DialogUtils.kt                                                        */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+package org.godotengine.godot.utils
+
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.DialogInterface
+import android.widget.Button
+import android.widget.EditText
+import android.widget.LinearLayout
+
+import org.godotengine.godot.R
+
+/**
+ * Utility class for managing dialogs.
+ */
+internal class DialogUtils {
+	companion object {
+		private val TAG = DialogUtils::class.java.simpleName
+
+		/**
+		 * Invoked on dialog button press.
+		 */
+		@JvmStatic
+		private external fun dialogCallback(buttonIndex: Int)
+
+		/**
+		 * Invoked on the input dialog submitted.
+		 */
+		@JvmStatic
+		private external fun inputDialogCallback(text: String)
+
+		/**
+		 * Displays a dialog with dynamically arranged buttons based on their text length.
+		 *
+		 * The buttons are laid out in rows, with a maximum of 2 buttons per row. If a button's text
+		 * is too long to fit within half the screen width, it occupies the entire row.
+		 *
+		 * @param activity The activity where the dialog will be displayed.
+		 * @param title The title of the dialog.
+		 * @param message The message displayed in the dialog.
+		 * @param buttons An array of button labels to display.
+		 */
+		fun showDialog(activity: Activity, title: String, message: String, buttons: Array<String>) {
+			var dismissDialog: () -> Unit = {} // Helper to dismiss the Dialog when a button is clicked.
+			activity.runOnUiThread {
+				val builder = AlertDialog.Builder(activity)
+				builder.setTitle(title)
+				builder.setMessage(message)
+
+				val buttonHeight = activity.resources.getDimensionPixelSize(R.dimen.button_height)
+				val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_horizontal)
+				val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_vertical)
+				val buttonPadding = activity.resources.getDimensionPixelSize(R.dimen.button_padding)
+
+				// Create a vertical parent layout to hold all rows of buttons.
+				val parentLayout = LinearLayout(activity)
+				parentLayout.orientation = LinearLayout.VERTICAL
+				parentLayout.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
+
+				// Horizontal row layout for arranging buttons.
+				var rowLayout = LinearLayout(activity)
+				rowLayout.orientation = LinearLayout.HORIZONTAL
+				rowLayout.layoutParams = LinearLayout.LayoutParams(
+					LinearLayout.LayoutParams.MATCH_PARENT,
+					LinearLayout.LayoutParams.WRAP_CONTENT
+				)
+
+				// Calculate the maximum width for a button to allow two buttons per row.
+				val screenWidth = activity.resources.displayMetrics.widthPixels
+				val availableWidth = screenWidth - (2 * paddingHorizontal)
+				val maxButtonWidth = availableWidth / 2
+
+				buttons.forEachIndexed { index, buttonLabel ->
+					val button = Button(activity)
+					button.text = buttonLabel
+					button.isSingleLine = true
+					button.setPadding(buttonPadding, buttonPadding, buttonPadding, buttonPadding)
+
+					// Measure the button to determine its width.
+					button.measure(0, 0)
+					val buttonWidth = button.measuredWidth
+
+					val params = LinearLayout.LayoutParams(
+						if (buttonWidth > maxButtonWidth) LinearLayout.LayoutParams.MATCH_PARENT else 0,
+						buttonHeight
+					)
+					params.weight = if (buttonWidth > maxButtonWidth) 0f else 1f
+					button.layoutParams = params
+
+					// Handle full-width buttons by finalizing the current row, if needed.
+					if (buttonWidth > maxButtonWidth) {
+						if (rowLayout.childCount > 0) {
+							parentLayout.addView(rowLayout)
+							rowLayout = LinearLayout(activity)
+							rowLayout.orientation = LinearLayout.HORIZONTAL
+						}
+						// Add the full-width button directly to the parent layout.
+						parentLayout.addView(button)
+					} else {
+						rowLayout.addView(button)
+
+						// Finalize the row if it reaches 2 buttons.
+						if (rowLayout.childCount == 2) {
+							parentLayout.addView(rowLayout)
+							rowLayout = LinearLayout(activity)
+							rowLayout.orientation = LinearLayout.HORIZONTAL
+						}
+
+						// Handle the last button with incomplete row.
+						if (index == buttons.size - 1 && rowLayout.childCount > 0) {
+							parentLayout.addView(rowLayout)
+						}
+					}
+
+					button.setOnClickListener {
+						dialogCallback(index)
+						dismissDialog()
+					}
+				}
+
+				// Attach the parent layout to the dialog.
+				builder.setView(parentLayout)
+				val dialog = builder.create()
+				dismissDialog = {dialog.dismiss()}
+				dialog.show()
+			}
+		}
+
+		/**
+		 * This method shows a dialog with a text input field, allowing the user to input text.
+		 *
+		 * @param activity The activity where the input dialog will be displayed.
+		 * @param title The title of the input dialog.
+		 * @param message The message displayed in the input dialog.
+		 * @param existingText The existing text that will be pre-filled in the input field.
+		 */
+		fun showInputDialog(activity: Activity, title: String, message: String, existingText: String) {
+			val inputField = EditText(activity)
+			val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_horizontal)
+			val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_vertical)
+			inputField.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
+			inputField.setText(existingText)
+			activity.runOnUiThread {
+				val builder = AlertDialog.Builder(activity)
+				builder.setMessage(message).setTitle(title).setView(inputField)
+				builder.setPositiveButton(R.string.dialog_ok) {
+						dialog: DialogInterface, id: Int ->
+					inputDialogCallback(inputField.text.toString())
+					dialog.dismiss()
+				}
+				val dialog = builder.create()
+				dialog.show()
+			}
+		}
+	}
+}

+ 0 - 8
platform/android/java_godot_lib_jni.cpp

@@ -494,14 +494,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JN
 	}
 }
 
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text) {
-	DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
-	if (ds) {
-		String text = jstring_to_string(p_text, env);
-		ds->emit_input_dialog_callback(text);
-	}
-}
-
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths) {
 	DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
 	if (ds) {

+ 0 - 1
platform/android/java_godot_lib_jni.h

@@ -65,7 +65,6 @@ JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(J
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height);
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result);
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text);
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths);
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz);
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz);

+ 23 - 0
platform/android/java_godot_wrapper.cpp

@@ -69,6 +69,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
 	_get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;");
 	_set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V");
 	_has_clipboard = p_env->GetMethodID(godot_class, "hasClipboard", "()Z");
+	_show_dialog = p_env->GetMethodID(godot_class, "showDialog", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
 	_show_input_dialog = p_env->GetMethodID(godot_class, "showInputDialog", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
 	_show_file_picker = p_env->GetMethodID(godot_class, "showFilePicker", "(Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)V");
 	_request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z");
@@ -303,6 +304,28 @@ bool GodotJavaWrapper::has_clipboard() {
 	}
 }
 
+Error GodotJavaWrapper::show_dialog(const String &p_title, const String &p_description, const Vector<String> &p_buttons) {
+	if (_show_input_dialog) {
+		JNIEnv *env = get_jni_env();
+		ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED);
+		jstring j_title = env->NewStringUTF(p_title.utf8().get_data());
+		jstring j_description = env->NewStringUTF(p_description.utf8().get_data());
+		jobjectArray j_buttons = env->NewObjectArray(p_buttons.size(), env->FindClass("java/lang/String"), nullptr);
+		for (int i = 0; i < p_buttons.size(); ++i) {
+			jstring j_button = env->NewStringUTF(p_buttons[i].utf8().get_data());
+			env->SetObjectArrayElement(j_buttons, i, j_button);
+			env->DeleteLocalRef(j_button);
+		}
+		env->CallVoidMethod(godot_instance, _show_dialog, j_title, j_description, j_buttons);
+		env->DeleteLocalRef(j_title);
+		env->DeleteLocalRef(j_description);
+		env->DeleteLocalRef(j_buttons);
+		return OK;
+	} else {
+		return ERR_UNCONFIGURED;
+	}
+}
+
 Error GodotJavaWrapper::show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text) {
 	if (_show_input_dialog) {
 		JNIEnv *env = get_jni_env();

+ 2 - 0
platform/android/java_godot_wrapper.h

@@ -60,6 +60,7 @@ private:
 	jmethodID _get_clipboard = nullptr;
 	jmethodID _set_clipboard = nullptr;
 	jmethodID _has_clipboard = nullptr;
+	jmethodID _show_dialog = nullptr;
 	jmethodID _show_input_dialog = nullptr;
 	jmethodID _show_file_picker = nullptr;
 	jmethodID _request_permission = nullptr;
@@ -109,6 +110,7 @@ public:
 	void set_clipboard(const String &p_text);
 	bool has_has_clipboard();
 	bool has_clipboard();
+	Error show_dialog(const String &p_title, const String &p_description, const Vector<String> &p_buttons);
 	Error show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text);
 	Error show_file_picker(const String &p_current_directory, const String &p_filename, int p_mode, const Vector<String> &p_filters);
 	bool request_permission(const String &p_name);