diff --git a/src/android/jar/CMakeLists.txt b/src/android/jar/CMakeLists.txt index 5d99756f968..b24c8d91001 100644 --- a/src/android/jar/CMakeLists.txt +++ b/src/android/jar/CMakeLists.txt @@ -45,6 +45,7 @@ set(java_sources src/org/qtproject/qt/android/QtAccessibilityInterface.java src/org/qtproject/qt/android/QtMenuInterface.java src/org/qtproject/qt/android/QtLayoutInterface.java + src/org/qtproject/qt/android/QtInputInterface.java ) qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java index 59794ff2778..10e40c98df8 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java @@ -65,6 +65,8 @@ class QtActivityDelegate extends QtActivityDelegateBase (QtMenuInterface)QtActivityDelegate.this); BackendRegister.registerBackend(QtLayoutInterface.class, (QtLayoutInterface)QtActivityDelegate.this); + BackendRegister.registerBackend(QtInputInterface.class, + (QtInputInterface)m_inputDelegate); } } @@ -76,6 +78,7 @@ class QtActivityDelegate extends QtActivityDelegateBase BackendRegister.unregisterBackend(QtAccessibilityInterface.class); BackendRegister.unregisterBackend(QtMenuInterface.class); BackendRegister.unregisterBackend(QtLayoutInterface.class); + BackendRegister.unregisterBackend(QtInputInterface.class); } } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java index f7f41e7d498..9127bc8e4b9 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java @@ -64,7 +64,6 @@ abstract class QtActivityDelegateBase return m_displayManager; } - @UsedFromNativeCode QtInputDelegate getInputDelegate() { return m_inputDelegate; } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java b/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java index 1bfe05e7ace..b95f817d33e 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java @@ -77,6 +77,10 @@ class QtInputConnection extends BaseInputConnection Log.w(QtTAG, "HideKeyboardRunnable: The activity reference is null"); return; } + if (m_qtInputConnectionListener == null) { + Log.w(QtTAG, "HideKeyboardRunnable: QtInputConnectionListener is null"); + return; + } Rect r = new Rect(); activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r); @@ -109,7 +113,7 @@ class QtInputConnection extends BaseInputConnection { if (closing) m_view.postDelayed(new HideKeyboardRunnable(), 100); - else + else if (m_qtInputConnectionListener != null) m_qtInputConnectionListener.onSetClosing(false); } @@ -297,7 +301,8 @@ class QtInputConnection extends BaseInputConnection restartImmInput(); break; default: - m_qtInputConnectionListener.onSendKeyEventDefaultCase(); + if (m_qtInputConnectionListener != null) + m_qtInputConnectionListener.onSendKeyEventDefaultCase(); break; } } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java index cfa273e410d..bf5578285a4 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java @@ -21,7 +21,8 @@ import android.view.inputmethod.InputMethodManager; import org.qtproject.qt.android.QtInputConnection.QtInputConnectionListener; /** @noinspection FieldCanBeLocal*/ -class QtInputDelegate implements QtInputConnection.QtInputConnectionListener { +class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, QtInputInterface +{ // keyboard methods public static native void keyDown(int key, int unicode, int modifier, boolean autoRepeat); @@ -90,90 +91,20 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener { m_imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); } - // QtInputConnectionListener methods + // QtInputInterface implementation begin @Override - public void onSetClosing(boolean closing) { - if (!closing) - setKeyboardVisibility(true, System.nanoTime()); + public void updateSelection(final int selStart, final int selEnd, + final int candidatesStart, final int candidatesEnd) + { + QtNative.runAction(() -> { + if (m_imm == null) + return; + + m_imm.updateSelection(m_currentEditText, selStart, selEnd, candidatesStart, candidatesEnd); + }); } @Override - public void onHideKeyboardRunnableDone(boolean visibility, long hideTimeStamp) { - setKeyboardVisibility(visibility, hideTimeStamp); - } - - @Override - public void onSendKeyEventDefaultCase() { - hideSoftwareKeyboard(); - } - // QtInputConnectionListener methods - - public boolean isKeyboardVisible() - { - return m_keyboardIsVisible; - } - - // Is the keyboard fully visible i.e. visible and no ongoing animation - @UsedFromNativeCode - public boolean isSoftwareKeyboardVisible() - { - return isKeyboardVisible() && !m_isKeyboardHidingAnimationOngoing; - } - - void setSoftInputMode(int inputMode) - { - m_softInputMode = inputMode; - } - - QtEditText getCurrentQtEditText() - { - return m_currentEditText; - } - - void setEditPopupMenu(EditPopupMenu editPopupMenu) - { - m_editPopupMenu = editPopupMenu; - } - - private void keyboardVisibilityUpdated(boolean visibility) - { - m_isKeyboardHidingAnimationOngoing = false; - QtInputDelegate.keyboardVisibilityChanged(visibility); - } - - public void setKeyboardVisibility(boolean visibility, long timeStamp) - { - if (m_showHideTimeStamp > timeStamp) - return; - m_showHideTimeStamp = timeStamp; - - if (m_keyboardIsVisible == visibility) - return; - m_keyboardIsVisible = visibility; - keyboardVisibilityUpdated(m_keyboardIsVisible); - - // Hiding the keyboard clears the immersive mode, so we need to set it again. - if (!visibility) - m_keyboardVisibilityListener.onKeyboardVisibilityChange(); - - } - - @UsedFromNativeCode - public void resetSoftwareKeyboard() - { - if (m_imm == null || m_currentEditText == null) - return; - m_currentEditText.postDelayed(() -> { - m_imm.restartInput(m_currentEditText); - m_currentEditText.m_optionsChanged = false; - }, 5); - } - - void setFocusedView(QtEditText currentEditText) - { - m_currentEditText = currentEditText; - } - public void showSoftwareKeyboard(Activity activity, QtLayout layout, final int x, final int y, final int width, final int height, final int inputHints, final int enterKeyType) @@ -217,6 +148,149 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener { }); } + @Override + public int getSelectHandleWidth() + { + int width = 0; + if (m_leftSelectionHandle != null && m_rightSelectionHandle != null) { + width = Math.max(m_leftSelectionHandle.width(), m_rightSelectionHandle.width()); + } else if (m_cursorHandle != null) { + width = m_cursorHandle.width(); + } + return width; + } + + /* called from the C++ code when the position of the cursor or selection handles needs to + be adjusted. + mode is one of QAndroidInputContext::CursorHandleShowMode + */ + @Override + public void updateHandles(Activity activity, QtLayout layout, int mode, + int editX, int editY, int editButtons, + int x1, int y1, int x2, int y2, boolean rtl) + { + QtNative.runAction(() -> updateHandleImpl(activity, layout, mode, editX, editY, editButtons, + x1, y1, x2, y2, rtl)); + } + + @Override + public QtInputConnection.QtInputConnectionListener getInputConnectionListener() + { + return this; + } + + @Override + public void resetSoftwareKeyboard() + { + if (m_imm == null || m_currentEditText == null) + return; + m_currentEditText.postDelayed(() -> { + m_imm.restartInput(m_currentEditText); + m_currentEditText.m_optionsChanged = false; + }, 5); + } + + @Override + public void hideSoftwareKeyboard() + { + m_isKeyboardHidingAnimationOngoing = true; + QtNative.runAction(() -> { + if (m_imm == null || m_currentEditText == null) + return; + + m_imm.hideSoftInputFromWindow(m_currentEditText.getWindowToken(), 0, + new ResultReceiver(new Handler()) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + switch (resultCode) { + case InputMethodManager.RESULT_SHOWN: + case InputMethodManager.RESULT_UNCHANGED_SHOWN: + setKeyboardVisibility(true, System.nanoTime()); + break; + case InputMethodManager.RESULT_HIDDEN: + case InputMethodManager.RESULT_UNCHANGED_HIDDEN: + setKeyboardVisibility(false, System.nanoTime()); + break; + } + } + }); + }); + } + + // Is the keyboard fully visible i.e. visible and no ongoing animation + @Override + public boolean isSoftwareKeyboardVisible() + { + return isKeyboardVisible() && !m_isKeyboardHidingAnimationOngoing; + } + // QtInputInterface implementation end + + // QtInputConnectionListener methods + @Override + public void onSetClosing(boolean closing) { + if (!closing) + setKeyboardVisibility(true, System.nanoTime()); + } + + @Override + public void onHideKeyboardRunnableDone(boolean visibility, long hideTimeStamp) { + setKeyboardVisibility(visibility, hideTimeStamp); + } + + @Override + public void onSendKeyEventDefaultCase() { + hideSoftwareKeyboard(); + } + // QtInputConnectionListener methods + + public boolean isKeyboardVisible() + { + return m_keyboardIsVisible; + } + + void setSoftInputMode(int inputMode) + { + m_softInputMode = inputMode; + } + + QtEditText getCurrentQtEditText() + { + return m_currentEditText; + } + + void setEditPopupMenu(EditPopupMenu editPopupMenu) + { + m_editPopupMenu = editPopupMenu; + } + + private void keyboardVisibilityUpdated(boolean visibility) + { + m_isKeyboardHidingAnimationOngoing = false; + QtInputDelegate.keyboardVisibilityChanged(visibility); + } + + public void setKeyboardVisibility(boolean visibility, long timeStamp) + { + if (m_showHideTimeStamp > timeStamp) + return; + m_showHideTimeStamp = timeStamp; + + if (m_keyboardIsVisible == visibility) + return; + m_keyboardIsVisible = visibility; + keyboardVisibilityUpdated(m_keyboardIsVisible); + + // Hiding the keyboard clears the immersive mode, so we need to set it again. + if (!visibility) + m_keyboardVisibilityListener.onKeyboardVisibilityChange(); + + } + + void setFocusedView(QtEditText currentEditText) + { + m_currentEditText = currentEditText; + } + private boolean updateSoftInputMode(Activity activity, int height) { DisplayMetrics metrics = new DisplayMetrics(); @@ -284,69 +358,6 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener { }, m_probeKeyboardHeightDelayMs); } - public void hideSoftwareKeyboard() - { - m_isKeyboardHidingAnimationOngoing = true; - QtNative.runAction(() -> { - if (m_imm == null || m_currentEditText == null) - return; - - m_imm.hideSoftInputFromWindow(m_currentEditText.getWindowToken(), 0, - new ResultReceiver(new Handler()) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - switch (resultCode) { - case InputMethodManager.RESULT_SHOWN: - case InputMethodManager.RESULT_UNCHANGED_SHOWN: - setKeyboardVisibility(true, System.nanoTime()); - break; - case InputMethodManager.RESULT_HIDDEN: - case InputMethodManager.RESULT_UNCHANGED_HIDDEN: - setKeyboardVisibility(false, System.nanoTime()); - break; - } - } - }); - }); - } - - @UsedFromNativeCode - public void updateSelection(final int selStart, final int selEnd, - final int candidatesStart, final int candidatesEnd) - { - QtNative.runAction(() -> { - if (m_imm == null) - return; - - m_imm.updateSelection(m_currentEditText, selStart, selEnd, candidatesStart, candidatesEnd); - }); - } - - @UsedFromNativeCode - public int getSelectHandleWidth() - { - int width = 0; - if (m_leftSelectionHandle != null && m_rightSelectionHandle != null) { - width = Math.max(m_leftSelectionHandle.width(), m_rightSelectionHandle.width()); - } else if (m_cursorHandle != null) { - width = m_cursorHandle.width(); - } - return width; - } - - /* called from the C++ code when the position of the cursor or selection handles needs to - be adjusted. - mode is one of QAndroidInputContext::CursorHandleShowMode - */ - @UsedFromNativeCode - public void updateHandles(Activity activity, QtLayout layout, int mode, - int editX, int editY, int editButtons, - int x1, int y1, int x2, int y2, boolean rtl) - { - QtNative.runAction(() -> updateHandleImpl(activity, layout, mode, editX, editY, editButtons, - x1, y1, x2, y2, rtl)); - } - private void updateHandleImpl(Activity activity, QtLayout layout, int mode, int editX, int editY, int editButtons, int x1, int y1, int x2, int y2, boolean rtl) diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtInputInterface.java new file mode 100644 index 00000000000..1dc4d5fd7f8 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputInterface.java @@ -0,0 +1,21 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +package org.qtproject.qt.android; + +import android.app.Activity; + +@UsedFromNativeCode +interface QtInputInterface { + void updateSelection(final int selStart, final int selEnd, final int candidatesStart, + final int candidatesEnd); + void showSoftwareKeyboard(Activity activity, QtLayout layout, final int x, final int y, + final int width, final int height, final int inputHints, + final int enterKeyType); + void resetSoftwareKeyboard(); + void hideSoftwareKeyboard(); + boolean isSoftwareKeyboardVisible(); + int getSelectHandleWidth(); + void updateHandles(Activity activity, QtLayout layout, int mode, int editX, int editY, + int editButtons, int x1, int y1, int x2, int y2, boolean rtl); + QtInputConnection.QtInputConnectionListener getInputConnectionListener(); +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java index 22152f7e69e..6ee2d53791b 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java @@ -29,13 +29,7 @@ class QtServiceEmbeddedDelegate implements QtEmbeddedViewInterface, QtNative.App QtNative.registerAppStateListener(this); QtNative.setService(service); // QTBUG-122920 TODO Implement accessibility for service UIs - } - - @UsedFromNativeCode - QtInputDelegate getInputDelegate() - { - // TODO Implement text input (QTBUG-122552) - return null; + // QTBUG-122552 TODO Implement text input } @Override diff --git a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java index d72e69d32a7..e88309e47e1 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java @@ -26,11 +26,12 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { private static native void setSurface(int windowId, Surface surface); static native void windowFocusChanged(boolean hasFocus, int id); - public QtWindow(Context context, QtWindow parentWindow, QtInputDelegate delegate) + public QtWindow(Context context, QtWindow parentWindow, + QtInputConnection.QtInputConnectionListener listener) { super(context); setId(View.generateViewId()); - m_editText = new QtEditText(context, delegate); + m_editText = new QtEditText(context, listener); setParent(parentWindow); setFocusableInTouchMode(true); addView(m_editText, new QtLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index c03da365586..266d027b3c1 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -25,6 +25,7 @@ using namespace QtAndroid; Q_DECLARE_JNI_CLASS(QtLayout, "org/qtproject/qt/android/QtLayout") Q_DECLARE_JNI_CLASS(QtLayoutInterface, "org/qtproject/qt/android/QtLayoutInterface") +Q_DECLARE_JNI_CLASS(QtInputInterface, "org/qtproject/qt/android/QtInputInterface") namespace QtAndroidInput { @@ -107,42 +108,40 @@ namespace QtAndroidInput void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd) { qCDebug(lcQpaInputMethods) << ">>> UPDATESELECTION" << selStart << selEnd << candidatesStart << candidatesEnd; - qtInputDelegate().callMethod("updateSelection", - selStart, - selEnd, - candidatesStart, - candidatesEnd); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface("updateSelection", selStart, selEnd, + candidatesStart, candidatesEnd); } void showSoftwareKeyboard(int left, int top, int width, int height, int inputHints, int enterKeyType) { - qtInputDelegate().callMethod("showSoftwareKeyboard", - QtAndroidPrivate::activity(), - qtLayout().object(), - left, - top, - width, - height, - inputHints, - enterKeyType); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface( + "showSoftwareKeyboard", QtAndroidPrivate::activity(), + qtLayout().object(), left, top, width, height, inputHints, + enterKeyType); qCDebug(lcQpaInputMethods) << "@@@ SHOWSOFTWAREKEYBOARD" << left << top << width << height << inputHints << enterKeyType; } void resetSoftwareKeyboard() { - qtInputDelegate().callMethod("resetSoftwareKeyboard"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface("resetSoftwareKeyboard"); qCDebug(lcQpaInputMethods) << "@@@ RESETSOFTWAREKEYBOARD"; } void hideSoftwareKeyboard() { - qtInputDelegate().callMethod("hideSoftwareKeyboard"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface("hideSoftwareKeyboard"); qCDebug(lcQpaInputMethods) << "@@@ HIDESOFTWAREKEYBOARD"; } bool isSoftwareKeyboardVisible() { - return qtInputDelegate().callMethod("isSoftwareKeyboardVisible"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + return reg->callInterface( + "isSoftwareKeyboardVisible"); } QRect softwareKeyboardRect() @@ -152,17 +151,17 @@ namespace QtAndroidInput int getSelectHandleWidth() { - return qtInputDelegate().callMethod("getSelectHandleWidth"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + return reg->callInterface("getSelectHandleWidth"); } void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl) { - qtInputDelegate().callMethod("updateHandles", - QtAndroidPrivate::activity(), - qtLayout().object(), - mode, editMenuPos.x(), editMenuPos.y(), editButtons, - cursor.x(), cursor.y(), - anchor.x(), anchor.y(), rtl); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface( + "updateHandles", QtAndroidPrivate::activity(), + qtLayout().object(), mode, editMenuPos.x(), editMenuPos.y(), + editButtons, cursor.x(), cursor.y(), anchor.x(), anchor.y(), rtl); } // from https://developer.android.com/reference/android/view/MotionEvent#getButtonState() diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index 35dfeb6a42d..1b2eb69b2e8 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -55,7 +55,6 @@ static jclass m_qtActivityClass = nullptr; static jclass m_qtServiceClass = nullptr; static QtJniTypes::QtActivityDelegateBase m_activityDelegate = nullptr; -static QtJniTypes::QtInputDelegate m_inputDelegate = nullptr; static int m_pendingApplicationState = -1; static QBasicMutex m_platformMutex; @@ -210,16 +209,6 @@ namespace QtAndroid return m_activityDelegate; } - QtJniTypes::QtInputDelegate qtInputDelegate() - { - if (!m_inputDelegate.isValid()) { - m_inputDelegate = qtActivityDelegate().callMethod( - "getInputDelegate"); - } - - return m_inputDelegate; - } - bool isQtApplication() { // Returns true if the app is a Qt app, i.e. Qt controls the whole app and diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h index 40db9e86827..b916446ea3c 100644 --- a/src/plugins/platforms/android/androidjnimain.h +++ b/src/plugins/platforms/android/androidjnimain.h @@ -51,7 +51,6 @@ namespace QtAndroid jclass applicationClass(); QtJniTypes::QtActivityDelegateBase qtActivityDelegate(); - QtJniTypes::QtInputDelegate qtInputDelegate(); // Keep synchronized with flags in ActivityDelegate.java enum SystemUiVisibility { diff --git a/src/plugins/platforms/android/qandroidplatformwindow.cpp b/src/plugins/platforms/android/qandroidplatformwindow.cpp index 979f0fb98ab..2482160573e 100644 --- a/src/plugins/platforms/android/qandroidplatformwindow.cpp +++ b/src/plugins/platforms/android/qandroidplatformwindow.cpp @@ -16,6 +16,10 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window") +Q_DECLARE_JNI_CLASS(QtInputInterface, "org/qtproject/qt/android/QtInputInterface") +Q_DECLARE_JNI_CLASS(QtInputConnectionListener, + "org/qtproject/qt/android/QtInputConnection$QtInputConnectionListener") + QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window) : QPlatformWindow(window), m_nativeQtWindow(nullptr), m_surfaceContainerType(SurfaceContainer::TextureView), m_nativeParentQtWindow(nullptr), @@ -55,10 +59,13 @@ QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window) m_nativeParentQtWindow = androidParent->nativeWindow(); } + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + QtJniTypes::QtInputConnectionListener listener = + reg->callInterface( + "getInputConnectionListener"); + m_nativeQtWindow = QJniObject::construct( - QNativeInterface::QAndroidApplication::context(), - m_nativeParentQtWindow, - QtAndroid::qtInputDelegate()); + QNativeInterface::QAndroidApplication::context(), m_nativeParentQtWindow, listener); m_nativeViewId = m_nativeQtWindow.callMethod("getId"); if (window->isTopLevel())