Просмотр исходного кода

android: Convert keyboard applet to kotlin and refactor

Charles Lombardo 3 лет назад
Родитель
Сommit
d30103b69f

+ 7 - 7
src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt

@@ -83,22 +83,22 @@ open class EmulationActivity : AppCompatActivity() {
     }
 
     override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
-        if (event.action == android.view.KeyEvent.ACTION_DOWN) {
-            if (keyCode == android.view.KeyEvent.KEYCODE_ENTER) {
+        if (event.action == KeyEvent.ACTION_DOWN) {
+            if (keyCode == KeyEvent.KEYCODE_ENTER) {
                 // Special case, we do not support multiline input, dismiss the keyboard.
                 val overlayView: View =
-                    this.findViewById<View>(R.id.surface_input_overlay)
+                    this.findViewById(R.id.surface_input_overlay)
                 val im =
                     overlayView.context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
-                im.hideSoftInputFromWindow(overlayView.windowToken, 0);
+                im.hideSoftInputFromWindow(overlayView.windowToken, 0)
             } else {
-                val textChar = event.getUnicodeChar();
+                val textChar = event.unicodeChar
                 if (textChar == 0) {
                     // No text, button input.
-                    NativeLibrary.SubmitInlineKeyboardInput(keyCode);
+                    NativeLibrary.SubmitInlineKeyboardInput(keyCode)
                 } else {
                     // Text submitted.
-                    NativeLibrary.SubmitInlineKeyboardText(textChar.toChar().toString());
+                    NativeLibrary.SubmitInlineKeyboardText(textChar.toChar().toString())
                 }
             }
         }

+ 0 - 264
src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java

@@ -1,264 +0,0 @@
-// 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.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.appcompat.app.AlertDialog;
-import androidx.core.view.ViewCompat;
-import androidx.fragment.app.DialogFragment;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-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 java.util.Objects;
-
-public final class SoftwareKeyboard {
-    /// 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 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 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 result;
-        public String text;
-
-        private KeyboardData(int result, String text) {
-            this.result = result;
-            this.text = text;
-        }
-    }
-
-    public static class KeyboardDialogFragment extends DialogFragment {
-        static KeyboardDialogFragment newInstance(KeyboardConfig config) {
-            KeyboardDialogFragment frag = new KeyboardDialogFragment();
-            Bundle args = new Bundle();
-            args.putSerializable("config", config);
-            frag.setArguments(args);
-            return frag;
-        }
-
-        @NonNull
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            final Activity emulationActivity = getActivity();
-            assert emulationActivity != null;
-
-            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-            params.leftMargin = params.rightMargin =
-                    YuzuApplication.getAppContext().getResources().getDimensionPixelSize(
-                            R.dimen.dialog_margin);
-
-            KeyboardConfig config = Objects.requireNonNull(
-                    (KeyboardConfig) requireArguments().getSerializable("config"));
-
-            // Set up the input
-            EditText editText = new EditText(YuzuApplication.getAppContext());
-            editText.setHint(config.initial_text);
-            editText.setSingleLine(!config.enable_return_button);
-            editText.setLayoutParams(params);
-            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(headerText)
-                    .setView(container);
-            setCancelable(false);
-
-            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.result = SwkbdResult.Ok;
-                    data.text = editText.getText().toString();
-                    dialog.dismiss();
-
-                    synchronized (finishLock) {
-                        finishLock.notifyAll();
-                    }
-                });
-            }
-            if (dialog.getButton(DialogInterface.BUTTON_NEUTRAL) != null) {
-                dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener((view) -> {
-                    data.result = SwkbdResult.Ok;
-                    dialog.dismiss();
-                    synchronized (finishLock) {
-                        finishLock.notifyAll();
-                    }
-                });
-            }
-            if (dialog.getButton(DialogInterface.BUTTON_NEGATIVE) != null) {
-                dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener((view) -> {
-                    data.result = SwkbdResult.Cancel;
-                    dialog.dismiss();
-                    synchronized (finishLock) {
-                        finishLock.notifyAll();
-                    }
-                });
-            }
-
-            return dialog;
-        }
-    }
-
-    private static KeyboardData data;
-    private static final Object finishLock = new Object();
-
-    private static void ExecuteNormalImpl(KeyboardConfig config) {
-        final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
-
-        data = new KeyboardData(SwkbdResult.Cancel, "");
-
-        KeyboardDialogFragment fragment = KeyboardDialogFragment.newInstance(config);
-        fragment.show(emulationActivity.getSupportFragmentManager(), "keyboard");
-    }
-
-    private static void ExecuteInlineImpl(KeyboardConfig config) {
-        final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
-
-        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;
-                }
-
-                // No longer visible, submit the result.
-                NativeLibrary.SubmitInlineKeyboardInput(android.view.KeyEvent.KEYCODE_ENTER);
-            }
-        }, delayMs);
-    }
-
-    public static KeyboardData ExecuteNormal(KeyboardConfig config) {
-        NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteNormalImpl(config));
-
-        synchronized (finishLock) {
-            try {
-                finishLock.wait();
-            } catch (Exception ignored) {
-            }
-        }
-
-        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);
-    }
-}

+ 117 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt

@@ -0,0 +1,117 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.applets.keyboard
+
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.view.KeyEvent
+import android.view.View
+import android.view.WindowInsets
+import android.view.inputmethod.InputMethodManager
+import androidx.core.view.ViewCompat
+import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment
+import java.io.Serializable
+
+object SoftwareKeyboard {
+    lateinit var data: KeyboardData
+    val dataLock = Object()
+
+    private fun executeNormalImpl(config: KeyboardConfig) {
+        val emulationActivity = NativeLibrary.sEmulationActivity.get()
+        data = KeyboardData(SwkbdResult.Cancel.ordinal, "")
+        val fragment = KeyboardDialogFragment.newInstance(config)
+        fragment.show(emulationActivity!!.supportFragmentManager, KeyboardDialogFragment.TAG)
+    }
+
+    private fun executeInlineImpl(config: KeyboardConfig) {
+        val emulationActivity = NativeLibrary.sEmulationActivity.get()
+
+        val overlayView = emulationActivity!!.findViewById<View>(R.id.surface_input_overlay)
+        val im =
+            overlayView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+        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.
+        val handler = Handler(Looper.myLooper()!!)
+        val delayMs = 500
+        handler.postDelayed(object : Runnable {
+            override fun run() {
+                val insets = ViewCompat.getRootWindowInsets(overlayView)
+                val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
+                if (isKeyboardVisible) {
+                    handler.postDelayed(this, delayMs.toLong())
+                    return
+                }
+
+                // No longer visible, submit the result.
+                NativeLibrary.SubmitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
+            }
+        }, delayMs.toLong())
+    }
+
+    @JvmStatic
+    fun executeNormal(config: KeyboardConfig): KeyboardData {
+        NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeNormalImpl(config) }
+        synchronized(dataLock) {
+            dataLock.wait()
+        }
+        return data
+    }
+
+    @JvmStatic
+    fun executeInline(config: KeyboardConfig) {
+        NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeInlineImpl(config) }
+    }
+
+    // Corresponds to Service::AM::Applets::SwkbdType
+    enum class SwkbdType {
+        Normal,
+        NumberPad,
+        Qwerty,
+        Unknown3,
+        Latin,
+        SimplifiedChinese,
+        TraditionalChinese,
+        Korean
+    }
+
+    // Corresponds to Service::AM::Applets::SwkbdPasswordMode
+    enum class SwkbdPasswordMode {
+        Disabled,
+        Enabled
+    }
+
+    // Corresponds to Service::AM::Applets::SwkbdResult
+    enum class SwkbdResult {
+        Ok,
+        Cancel
+    }
+
+    data class KeyboardConfig(
+        var ok_text: String? = null,
+        var header_text: String? = null,
+        var sub_text: String? = null,
+        var guide_text: String? = null,
+        var initial_text: String? = null,
+        var left_optional_symbol_key: Short = 0,
+        var right_optional_symbol_key: Short = 0,
+        var max_text_length: Int = 0,
+        var min_text_length: Int = 0,
+        var initial_cursor_position: Int = 0,
+        var type: Int = 0,
+        var password_mode: Int = 0,
+        var text_draw_type: Int = 0,
+        var key_disable_flags: Int = 0,
+        var use_blur_background: Boolean = false,
+        var enable_backspace_button: Boolean = false,
+        var enable_return_button: Boolean = false,
+        var disable_cancel_button: Boolean = false
+    ) : Serializable
+
+    // Corresponds to Frontend::KeyboardData
+    data class KeyboardData(var result: Int, var text: String)
+}

+ 100 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/ui/KeyboardDialogFragment.kt

@@ -0,0 +1,100 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.applets.keyboard.ui
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.text.InputFilter
+import android.text.InputType
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.applets.keyboard.SoftwareKeyboard
+import org.yuzu.yuzu_emu.applets.keyboard.SoftwareKeyboard.KeyboardConfig
+import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
+import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
+
+class KeyboardDialogFragment : DialogFragment() {
+    private lateinit var binding: DialogEditTextBinding
+    private lateinit var config: KeyboardConfig
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        binding = DialogEditTextBinding.inflate(layoutInflater)
+        config = requireArguments().serializable(CONFIG)!!
+
+        // Set up the input
+        binding.editText.hint = config.initial_text
+        binding.editText.isSingleLine = !config.enable_return_button
+        binding.editText.filters =
+            arrayOf<InputFilter>(InputFilter.LengthFilter(config.max_text_length))
+
+        // Handle input type
+        var inputType: Int
+        when (config.type) {
+            SoftwareKeyboard.SwkbdType.Normal.ordinal,
+            SoftwareKeyboard.SwkbdType.Qwerty.ordinal,
+            SoftwareKeyboard.SwkbdType.Unknown3.ordinal,
+            SoftwareKeyboard.SwkbdType.Latin.ordinal,
+            SoftwareKeyboard.SwkbdType.SimplifiedChinese.ordinal,
+            SoftwareKeyboard.SwkbdType.TraditionalChinese.ordinal,
+            SoftwareKeyboard.SwkbdType.Korean.ordinal -> {
+                inputType = InputType.TYPE_CLASS_TEXT
+                if (config.password_mode == SoftwareKeyboard.SwkbdPasswordMode.Enabled.ordinal) {
+                    inputType = inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
+                }
+            }
+            SoftwareKeyboard.SwkbdType.NumberPad.ordinal -> {
+                inputType = InputType.TYPE_CLASS_NUMBER
+                if (config.password_mode == SoftwareKeyboard.SwkbdPasswordMode.Enabled.ordinal) {
+                    inputType = inputType or InputType.TYPE_NUMBER_VARIATION_PASSWORD
+                }
+            }
+            else -> {
+                inputType = InputType.TYPE_CLASS_TEXT
+                if (config.password_mode == SoftwareKeyboard.SwkbdPasswordMode.Enabled.ordinal) {
+                    inputType = inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
+                }
+            }
+        }
+        binding.editText.inputType = inputType
+
+        val headerText =
+            config.header_text!!.ifEmpty { resources.getString(R.string.software_keyboard) }
+        val okText =
+            if (config.header_text!!.isEmpty()) resources.getString(android.R.string.ok) else config.ok_text!!
+
+        return MaterialAlertDialogBuilder(requireContext())
+            .setTitle(headerText)
+            .setView(binding.root)
+            .setPositiveButton(okText) { _, _ ->
+                SoftwareKeyboard.data.result = SoftwareKeyboard.SwkbdResult.Ok.ordinal
+                SoftwareKeyboard.data.text = binding.editText.text.toString()
+            }
+            .setNegativeButton(resources.getString(android.R.string.cancel)) { _, _ ->
+                SoftwareKeyboard.data.result = SoftwareKeyboard.SwkbdResult.Cancel.ordinal
+            }
+            .create()
+    }
+
+    override fun onDismiss(dialog: DialogInterface) {
+        super.onDismiss(dialog)
+        synchronized(SoftwareKeyboard.dataLock) {
+            SoftwareKeyboard.dataLock.notifyAll()
+        }
+    }
+
+    companion object {
+        const val TAG = "KeyboardDialogFragment"
+        const val CONFIG = "keyboard_config"
+
+        fun newInstance(config: KeyboardConfig?): KeyboardDialogFragment {
+            val frag = KeyboardDialogFragment()
+            val args = Bundle()
+            args.putSerializable(CONFIG, config)
+            frag.arguments = args
+            return frag
+        }
+    }
+}

+ 8 - 8
src/android/app/src/main/jni/applets/software_keyboard.cpp

@@ -253,19 +253,19 @@ void AndroidKeyboard::SubmitNormalText(const ResultData& data) const {
 
 void InitJNI(JNIEnv* env) {
     s_software_keyboard_class = reinterpret_cast<jclass>(
-        env->NewGlobalRef(env->FindClass("org/yuzu/yuzu_emu/applets/SoftwareKeyboard")));
+        env->NewGlobalRef(env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard")));
     s_keyboard_config_class = reinterpret_cast<jclass>(env->NewGlobalRef(
-        env->FindClass("org/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardConfig")));
+        env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig")));
     s_keyboard_data_class = reinterpret_cast<jclass>(env->NewGlobalRef(
-        env->FindClass("org/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardData")));
+        env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardData")));
 
     s_swkbd_execute_normal = env->GetStaticMethodID(
-        s_software_keyboard_class, "ExecuteNormal",
-        "(Lorg/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/"
-        "applets/SoftwareKeyboard$KeyboardData;");
+        s_software_keyboard_class, "executeNormal",
+        "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/"
+        "applets/keyboard/SoftwareKeyboard$KeyboardData;");
     s_swkbd_execute_inline =
-        env->GetStaticMethodID(s_software_keyboard_class, "ExecuteInline",
-                               "(Lorg/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardConfig;)V");
+        env->GetStaticMethodID(s_software_keyboard_class, "executeInline",
+                               "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)V");
 }
 
 void CleanupJNI(JNIEnv* env) {

+ 23 - 0
src/android/app/src/main/res/layout/dialog_edit_text.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/edit_text_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="24dp"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:id="@+id/edit_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="none" />
+
+    </com.google.android.material.textfield.TextInputLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>