diff options
Diffstat (limited to 'src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java')
-rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java | 254 |
1 files changed, 126 insertions, 128 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java index 894da8801..8ad4b1e22 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java @@ -1,22 +1,28 @@ -// Copyright 2020 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later package org.yuzu.yuzu_emu.applets; import android.app.Activity; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; +import android.graphics.Rect; import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; import android.text.InputFilter; -import android.text.Spanned; +import android.text.InputType; import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.WindowInsets; +import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.FrameLayout; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.core.view.ViewCompat; import androidx.fragment.app.DialogFragment; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -25,72 +31,66 @@ import org.yuzu.yuzu_emu.YuzuApplication; import org.yuzu.yuzu_emu.NativeLibrary; import org.yuzu.yuzu_emu.R; import org.yuzu.yuzu_emu.activities.EmulationActivity; -import org.yuzu.yuzu_emu.utils.Log; import java.util.Objects; public final class SoftwareKeyboard { - /// Corresponds to Frontend::ButtonConfig - private interface ButtonConfig { - int Single = 0; /// Ok button - int Dual = 1; /// Cancel | Ok buttons - int Triple = 2; /// Cancel | I Forgot | Ok buttons - int None = 3; /// No button (returned by swkbdInputText in special cases) - } - - /// Corresponds to Frontend::ValidationError - public enum ValidationError { - None, - // Button Selection - ButtonOutOfRange, - // Configured Filters - MaxDigitsExceeded, - AtSignNotAllowed, - PercentNotAllowed, - BackslashNotAllowed, - ProfanityNotAllowed, - CallbackFailed, - // Allowed Input Type - FixedLengthRequired, - MaxLengthExceeded, - BlankInputNotAllowed, - EmptyInputNotAllowed, - } + /// Corresponds to Service::AM::Applets::SwkbdType + private interface SwkbdType { + int Normal = 0; + int NumberPad = 1; + int Qwerty = 2; + int Unknown3 = 3; + int Latin = 4; + int SimplifiedChinese = 5; + int TraditionalChinese = 6; + int Korean = 7; + }; + + /// Corresponds to Service::AM::Applets::SwkbdPasswordMode + private interface SwkbdPasswordMode { + int Disabled = 0; + int Enabled = 1; + }; + + /// Corresponds to Service::AM::Applets::SwkbdResult + private interface SwkbdResult { + int Ok = 0; + int Cancel = 1; + }; public static class KeyboardConfig implements java.io.Serializable { - public int button_config; + public String ok_text; + public String header_text; + public String sub_text; + public String guide_text; + public String initial_text; + public short left_optional_symbol_key; + public short right_optional_symbol_key; public int max_text_length; - public boolean multiline_mode; /// True if the keyboard accepts multiple lines of input - public String hint_text; /// Displayed in the field as a hint before - @Nullable - public String[] button_text; /// Contains the button text that the caller provides + public int min_text_length; + public int initial_cursor_position; + public int type; + public int password_mode; + public int text_draw_type; + public int key_disable_flags; + public boolean use_blur_background; + public boolean enable_backspace_button; + public boolean enable_return_button; + public boolean disable_cancel_button; } /// Corresponds to Frontend::KeyboardData public static class KeyboardData { - public int button; + public int result; public String text; - private KeyboardData(int button, String text) { - this.button = button; + private KeyboardData(int result, String text) { + this.result = result; this.text = text; } } - private static class Filter implements InputFilter { - @Override - public CharSequence filter(CharSequence source, int start, int end, Spanned dest, - int dstart, int dend) { - String text = new StringBuilder(dest) - .replace(dstart, dend, source.subSequence(start, end).toString()) - .toString(); - if (ValidateFilters(text) == ValidationError.None) { - return null; // Accept replacement - } - return dest.subSequence(dstart, dend); // Request the subsequence to be unchanged - } - } - public static class KeyboardDialogFragment extends DialogFragment { static KeyboardDialogFragment newInstance(KeyboardConfig config) { KeyboardDialogFragment frag = new KeyboardDialogFragment(); @@ -113,60 +113,65 @@ public final class SoftwareKeyboard { R.dimen.dialog_margin); KeyboardConfig config = Objects.requireNonNull( - (KeyboardConfig) Objects.requireNonNull(getArguments()).getSerializable("config")); + (KeyboardConfig) requireArguments().getSerializable("config")); // Set up the input EditText editText = new EditText(YuzuApplication.getAppContext()); - editText.setHint(config.hint_text); - editText.setSingleLine(!config.multiline_mode); + editText.setHint(config.initial_text); + editText.setSingleLine(!config.enable_return_button); editText.setLayoutParams(params); - editText.setFilters(new InputFilter[]{ - new Filter(), new InputFilter.LengthFilter(config.max_text_length)}); + editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(config.max_text_length)}); + + // Handle input type + int input_type = 0; + switch (config.type) + { + case SwkbdType.Normal: + case SwkbdType.Qwerty: + case SwkbdType.Unknown3: + case SwkbdType.Latin: + case SwkbdType.SimplifiedChinese: + case SwkbdType.TraditionalChinese: + case SwkbdType.Korean: + default: + input_type = InputType.TYPE_CLASS_TEXT; + if (config.password_mode == SwkbdPasswordMode.Enabled) + { + input_type |= InputType.TYPE_TEXT_VARIATION_PASSWORD; + } + break; + case SwkbdType.NumberPad: + input_type = InputType.TYPE_CLASS_NUMBER; + if (config.password_mode == SwkbdPasswordMode.Enabled) + { + input_type |= InputType.TYPE_NUMBER_VARIATION_PASSWORD; + } + break; + } + + // Apply input type + editText.setInputType(input_type); FrameLayout container = new FrameLayout(emulationActivity); container.addView(editText); + String headerText = config.header_text.isEmpty() ? emulationActivity.getString(R.string.software_keyboard) : config.header_text; + String okText = config.header_text.isEmpty() ? emulationActivity.getString(android.R.string.ok) : config.ok_text; + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity) - .setTitle(R.string.software_keyboard) + .setTitle(headerText) .setView(container); setCancelable(false); - switch (config.button_config) { - case ButtonConfig.Triple: { - final String text = config.button_text[1].isEmpty() - ? emulationActivity.getString(R.string.i_forgot) - : config.button_text[1]; - builder.setNeutralButton(text, null); - } - // fallthrough - case ButtonConfig.Dual: { - final String text = config.button_text[0].isEmpty() - ? emulationActivity.getString(android.R.string.cancel) - : config.button_text[0]; - builder.setNegativeButton(text, null); - } - // fallthrough - case ButtonConfig.Single: { - final String text = config.button_text[2].isEmpty() - ? emulationActivity.getString(android.R.string.ok) - : config.button_text[2]; - builder.setPositiveButton(text, null); - break; - } - } + builder.setPositiveButton(okText, null); + builder.setNegativeButton(emulationActivity.getString(android.R.string.cancel), null); final AlertDialog dialog = builder.create(); dialog.create(); if (dialog.getButton(DialogInterface.BUTTON_POSITIVE) != null) { dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener((view) -> { - data.button = config.button_config; + data.result = SwkbdResult.Ok; data.text = editText.getText().toString(); - final ValidationError error = ValidateInput(data.text); - if (error != ValidationError.None) { - HandleValidationError(config, error); - return; - } - dialog.dismiss(); synchronized (finishLock) { @@ -176,7 +181,7 @@ public final class SoftwareKeyboard { } if (dialog.getButton(DialogInterface.BUTTON_NEUTRAL) != null) { dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener((view) -> { - data.button = 1; + data.result = SwkbdResult.Ok; dialog.dismiss(); synchronized (finishLock) { finishLock.notifyAll(); @@ -185,7 +190,7 @@ public final class SoftwareKeyboard { } if (dialog.getButton(DialogInterface.BUTTON_NEGATIVE) != null) { dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener((view) -> { - data.button = 0; + data.result = SwkbdResult.Cancel; dialog.dismiss(); synchronized (finishLock) { finishLock.notifyAll(); @@ -200,49 +205,42 @@ public final class SoftwareKeyboard { private static KeyboardData data; private static final Object finishLock = new Object(); - private static void ExecuteImpl(KeyboardConfig config) { + private static void ExecuteNormalImpl(KeyboardConfig config) { final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get(); - data = new KeyboardData(0, ""); + data = new KeyboardData(SwkbdResult.Cancel, ""); KeyboardDialogFragment fragment = KeyboardDialogFragment.newInstance(config); fragment.show(emulationActivity.getSupportFragmentManager(), "keyboard"); } - private static void HandleValidationError(KeyboardConfig config, ValidationError error) { + private static void ExecuteInlineImpl(KeyboardConfig config) { final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get(); - String message = ""; - switch (error) { - case FixedLengthRequired: - message = - emulationActivity.getString(R.string.fixed_length_required, config.max_text_length); - break; - case MaxLengthExceeded: - message = - emulationActivity.getString(R.string.max_length_exceeded, config.max_text_length); - break; - case BlankInputNotAllowed: - message = emulationActivity.getString(R.string.blank_input_not_allowed); - break; - case EmptyInputNotAllowed: - message = emulationActivity.getString(R.string.empty_input_not_allowed); - break; - } - new MaterialAlertDialogBuilder(emulationActivity) - .setTitle(R.string.software_keyboard) - .setMessage(message) - .setPositiveButton(android.R.string.ok, null) - .show(); - } + var overlayView = emulationActivity.findViewById(R.id.surface_input_overlay); + InputMethodManager im = (InputMethodManager)overlayView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + im.showSoftInput(overlayView, InputMethodManager.SHOW_FORCED); + + // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. + final Handler handler = new Handler(); + final int delayMs = 500; + handler.postDelayed(new Runnable() { + public void run() { + var insets = ViewCompat.getRootWindowInsets(overlayView); + var isKeyboardVisible = insets.isVisible(WindowInsets.Type.ime()); + if (isKeyboardVisible) { + handler.postDelayed(this, delayMs); + return; + } - public static KeyboardData Execute(KeyboardConfig config) { - if (config.button_config == ButtonConfig.None) { - Log.error("Unexpected button config None"); - return new KeyboardData(0, ""); - } + // No longer visible, submit the result. + NativeLibrary.SubmitInlineKeyboardInput(android.view.KeyEvent.KEYCODE_ENTER); + } + }, delayMs); + } - NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteImpl(config)); + public static KeyboardData ExecuteNormal(KeyboardConfig config) { + NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteNormalImpl(config)); synchronized (finishLock) { try { @@ -254,13 +252,13 @@ public final class SoftwareKeyboard { return data; } + public static void ExecuteInline(KeyboardConfig config) { + NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteInlineImpl(config)); + } + public static void ShowError(String error) { NativeLibrary.displayAlertMsg( YuzuApplication.getAppContext().getResources().getString(R.string.software_keyboard), error, false); } - - private static native ValidationError ValidateFilters(String text); - - private static native ValidationError ValidateInput(String text); } |