From 5dce4bc7166c1e0295665f6acda9f74736d72097 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 4 Nov 2023 23:22:55 -0700
Subject: [PATCH] Makes SDLInputConnection and DummyEdit public classes (thanks
Cole!)
I've added an additional patch that expands on the same basic idea as the first one; it makes SDLInputConnection and DummyEdit into public classes so that they can be overridden from the Xamarin end if their functionality needs to be extended. (In my case, I need to change the type of software keyboard that's displayed.)
Fixes https://github.com/libsdl-org/SDL/issues/2785
---
.../main/java/org/libsdl/app/SDLActivity.java | 190 +-----------------
.../java/org/libsdl/app/SDLDummyEdit.java | 62 ++++++
.../org/libsdl/app/SDLInputConnection.java | 136 +++++++++++++
3 files changed, 200 insertions(+), 188 deletions(-)
create mode 100644 android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java
create mode 100644 android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
index 41497a71e68e..82ba742a429c 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -23,9 +23,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.Selection;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
@@ -39,12 +36,9 @@
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
-import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -210,7 +204,7 @@ public enum NativeState {
// Main components
protected static SDLActivity mSingleton;
protected static SDLSurface mSurface;
- protected static DummyEdit mTextEdit;
+ protected static SDLDummyEdit mTextEdit;
protected static boolean mScreenKeyboardShown;
protected static ViewGroup mLayout;
protected static SDLClipboardHandler mClipboardHandler;
@@ -1353,7 +1347,7 @@ public void run() {
params.topMargin = y;
if (mTextEdit == null) {
- mTextEdit = new DummyEdit(SDL.getContext());
+ mTextEdit = new SDLDummyEdit(SDL.getContext());
mLayout.addView(mTextEdit, params);
} else {
@@ -1975,186 +1969,6 @@ public void run() {
}
}
-/* This is a fake invisible editor view that receives the input and defines the
- * pan&scan region
- */
-class DummyEdit extends View implements View.OnKeyListener {
- InputConnection ic;
-
- public DummyEdit(Context context) {
- super(context);
- setFocusableInTouchMode(true);
- setFocusable(true);
- setOnKeyListener(this);
- }
-
- @Override
- public boolean onCheckIsTextEditor() {
- return true;
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- return SDLActivity.handleKeyEvent(v, keyCode, event, ic);
- }
-
- //
- @Override
- public boolean onKeyPreIme (int keyCode, KeyEvent event) {
- // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
- // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
- // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
- // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
- // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
- // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
- if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
- if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
- SDLActivity.onNativeKeyboardFocusLost();
- }
- }
- return super.onKeyPreIme(keyCode, event);
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- ic = new SDLInputConnection(this, true);
-
- outAttrs.inputType = InputType.TYPE_CLASS_TEXT |
- InputType.TYPE_TEXT_FLAG_MULTI_LINE;
- outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |
- EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
-
- return ic;
- }
-}
-
-class SDLInputConnection extends BaseInputConnection {
-
- protected EditText mEditText;
- protected String mCommittedText = "";
-
- public SDLInputConnection(View targetView, boolean fullEditor) {
- super(targetView, fullEditor);
- mEditText = new EditText(SDL.getContext());
- }
-
- @Override
- public Editable getEditable() {
- return mEditText.getEditableText();
- }
-
- @Override
- public boolean sendKeyEvent(KeyEvent event) {
- /*
- * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
- * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses
- * and so we need to generate them ourselves in commitText. To avoid duplicates on the handful of keys
- * that still do, we empty this out.
- */
-
- /*
- * Return DOES still generate a key event, however. So rather than using it as the 'click a button' key
- * as we do with physical keyboards, let's just use it to hide the keyboard.
- */
-
- if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
- if (SDLActivity.onNativeSoftReturnKey()) {
- return true;
- }
- }
-
- return super.sendKeyEvent(event);
- }
-
- @Override
- public boolean commitText(CharSequence text, int newCursorPosition) {
- if (!super.commitText(text, newCursorPosition)) {
- return false;
- }
- updateText();
- return true;
- }
-
- @Override
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
- if (!super.setComposingText(text, newCursorPosition)) {
- return false;
- }
- updateText();
- return true;
- }
-
- @Override
- public boolean deleteSurroundingText(int beforeLength, int afterLength) {
- if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) {
- // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
- // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
- if (beforeLength > 0 && afterLength == 0) {
- // backspace(s)
- while (beforeLength-- > 0) {
- nativeGenerateScancodeForUnichar('\b');
- }
- return true;
- }
- }
-
- if (!super.deleteSurroundingText(beforeLength, afterLength)) {
- return false;
- }
- updateText();
- return true;
- }
-
- protected void updateText() {
- final Editable content = getEditable();
- if (content == null) {
- return;
- }
-
- String text = content.toString();
- int compareLength = Math.min(text.length(), mCommittedText.length());
- int matchLength, offset;
-
- /* Backspace over characters that are no longer in the string */
- for (matchLength = 0; matchLength < compareLength; ) {
- int codePoint = mCommittedText.codePointAt(matchLength);
- if (codePoint != text.codePointAt(matchLength)) {
- break;
- }
- matchLength += Character.charCount(codePoint);
- }
- /* FIXME: This doesn't handle graphemes, like '🌬️' */
- for (offset = matchLength; offset < mCommittedText.length(); ) {
- int codePoint = mCommittedText.codePointAt(offset);
- nativeGenerateScancodeForUnichar('\b');
- offset += Character.charCount(codePoint);
- }
-
- if (matchLength < text.length()) {
- String pendingText = text.subSequence(matchLength, text.length()).toString();
- for (offset = 0; offset < pendingText.length(); ) {
- int codePoint = pendingText.codePointAt(offset);
- if (codePoint == '\n') {
- if (SDLActivity.onNativeSoftReturnKey()) {
- return;
- }
- }
- /* Higher code points don't generate simulated scancodes */
- if (codePoint < 128) {
- nativeGenerateScancodeForUnichar((char)codePoint);
- }
- offset += Character.charCount(codePoint);
- }
- SDLInputConnection.nativeCommitText(pendingText, 0);
- }
- mCommittedText = text;
- }
-
- public static native void nativeCommitText(String text, int newCursorPosition);
-
- public static native void nativeGenerateScancodeForUnichar(char c);
-}
-
class SDLClipboardHandler implements
ClipboardManager.OnPrimaryClipChangedListener {
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java b/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java
new file mode 100644
index 000000000000..dca28145ec19
--- /dev/null
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java
@@ -0,0 +1,62 @@
+package org.libsdl.app;
+
+import android.content.*;
+import android.text.InputType;
+import android.view.*;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+/* This is a fake invisible editor view that receives the input and defines the
+ * pan&scan region
+ */
+public class SDLDummyEdit extends View implements View.OnKeyListener
+{
+ InputConnection ic;
+
+ public SDLDummyEdit(Context context) {
+ super(context);
+ setFocusableInTouchMode(true);
+ setFocusable(true);
+ setOnKeyListener(this);
+ }
+
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return true;
+ }
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ return SDLActivity.handleKeyEvent(v, keyCode, event, ic);
+ }
+
+ //
+ @Override
+ public boolean onKeyPreIme (int keyCode, KeyEvent event) {
+ // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
+ // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
+ // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
+ // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
+ // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
+ // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
+ if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
+ if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
+ SDLActivity.onNativeKeyboardFocusLost();
+ }
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ ic = new SDLInputConnection(this, true);
+
+ outAttrs.inputType = InputType.TYPE_CLASS_TEXT |
+ InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+ outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |
+ EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
+
+ return ic;
+ }
+}
+
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java b/android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java
new file mode 100644
index 000000000000..e1d29a8890e2
--- /dev/null
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java
@@ -0,0 +1,136 @@
+package org.libsdl.app;
+
+import android.content.*;
+import android.os.Build;
+import android.text.Editable;
+import android.view.*;
+import android.view.inputmethod.BaseInputConnection;
+import android.widget.EditText;
+
+public class SDLInputConnection extends BaseInputConnection
+{
+ protected EditText mEditText;
+ protected String mCommittedText = "";
+
+ public SDLInputConnection(View targetView, boolean fullEditor) {
+ super(targetView, fullEditor);
+ mEditText = new EditText(SDL.getContext());
+ }
+
+ @Override
+ public Editable getEditable() {
+ return mEditText.getEditableText();
+ }
+
+ @Override
+ public boolean sendKeyEvent(KeyEvent event) {
+ /*
+ * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
+ * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses
+ * and so we need to generate them ourselves in commitText. To avoid duplicates on the handful of keys
+ * that still do, we empty this out.
+ */
+
+ /*
+ * Return DOES still generate a key event, however. So rather than using it as the 'click a button' key
+ * as we do with physical keyboards, let's just use it to hide the keyboard.
+ */
+
+ if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
+ if (SDLActivity.onNativeSoftReturnKey()) {
+ return true;
+ }
+ }
+
+ return super.sendKeyEvent(event);
+ }
+
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ if (!super.commitText(text, newCursorPosition)) {
+ return false;
+ }
+ updateText();
+ return true;
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ if (!super.setComposingText(text, newCursorPosition)) {
+ return false;
+ }
+ updateText();
+ return true;
+ }
+
+ @Override
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) {
+ // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
+ // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
+ if (beforeLength > 0 && afterLength == 0) {
+ // backspace(s)
+ while (beforeLength-- > 0) {
+ nativeGenerateScancodeForUnichar('\b');
+ }
+ return true;
+ }
+ }
+
+ if (!super.deleteSurroundingText(beforeLength, afterLength)) {
+ return false;
+ }
+ updateText();
+ return true;
+ }
+
+ protected void updateText() {
+ final Editable content = getEditable();
+ if (content == null) {
+ return;
+ }
+
+ String text = content.toString();
+ int compareLength = Math.min(text.length(), mCommittedText.length());
+ int matchLength, offset;
+
+ /* Backspace over characters that are no longer in the string */
+ for (matchLength = 0; matchLength < compareLength; ) {
+ int codePoint = mCommittedText.codePointAt(matchLength);
+ if (codePoint != text.codePointAt(matchLength)) {
+ break;
+ }
+ matchLength += Character.charCount(codePoint);
+ }
+ /* FIXME: This doesn't handle graphemes, like '🌬️' */
+ for (offset = matchLength; offset < mCommittedText.length(); ) {
+ int codePoint = mCommittedText.codePointAt(offset);
+ nativeGenerateScancodeForUnichar('\b');
+ offset += Character.charCount(codePoint);
+ }
+
+ if (matchLength < text.length()) {
+ String pendingText = text.subSequence(matchLength, text.length()).toString();
+ for (offset = 0; offset < pendingText.length(); ) {
+ int codePoint = pendingText.codePointAt(offset);
+ if (codePoint == '\n') {
+ if (SDLActivity.onNativeSoftReturnKey()) {
+ return;
+ }
+ }
+ /* Higher code points don't generate simulated scancodes */
+ if (codePoint < 128) {
+ nativeGenerateScancodeForUnichar((char)codePoint);
+ }
+ offset += Character.charCount(codePoint);
+ }
+ SDLInputConnection.nativeCommitText(pendingText, 0);
+ }
+ mCommittedText = text;
+ }
+
+ public static native void nativeCommitText(String text, int newCursorPosition);
+
+ public static native void nativeGenerateScancodeForUnichar(char c);
+}
+