Android: Implement input backend interface

Interface for input-related actions, based off of QtInputDelegate.

Implements all the QtInputDelegate functions called from native code
plus an extra function that allows the native side to get a
QtInputConnectionListener object for QtWindow creation.

Removed some unused functions and unmarked @UsedFromNativeCode in some
that are no longer called from native, but still used from java.

Added QtInputConnectionListener null checks in QtInputConnection, due
to the possibility of a non-existent InputInterface returning a null
QtInputConnectionListener for the QtInputConnection constructor.

Task-number: QTBUG-118874
Change-Id: I8d4cde3e0c735471d0fa30d16db20eb13542cdaa
Reviewed-by: Tinja Paavoseppä <tinja.paavoseppa@qt.io>
This commit is contained in:
Petri Virkkunen 2024-04-09 12:39:30 +03:00
parent ad649b583d
commit 6bee03629f
12 changed files with 224 additions and 195 deletions

View File

@ -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

View File

@ -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);
}
}

View File

@ -64,7 +64,6 @@ abstract class QtActivityDelegateBase
return m_displayManager;
}
@UsedFromNativeCode
QtInputDelegate getInputDelegate() {
return m_inputDelegate;
}

View File

@ -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;
}
}

View File

@ -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)

View File

@ -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();
}

View File

@ -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

View File

@ -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,

View File

@ -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<void>("updateSelection",
selStart,
selEnd,
candidatesStart,
candidatesEnd);
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface, void>("updateSelection", selStart, selEnd,
candidatesStart, candidatesEnd);
}
void showSoftwareKeyboard(int left, int top, int width, int height, int inputHints, int enterKeyType)
{
qtInputDelegate().callMethod<void>("showSoftwareKeyboard",
QtAndroidPrivate::activity(),
qtLayout().object<QtJniTypes::QtLayout>(),
left,
top,
width,
height,
inputHints,
enterKeyType);
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface, void>(
"showSoftwareKeyboard", QtAndroidPrivate::activity(),
qtLayout().object<QtJniTypes::QtLayout>(), left, top, width, height, inputHints,
enterKeyType);
qCDebug(lcQpaInputMethods) << "@@@ SHOWSOFTWAREKEYBOARD" << left << top << width << height << inputHints << enterKeyType;
}
void resetSoftwareKeyboard()
{
qtInputDelegate().callMethod<void>("resetSoftwareKeyboard");
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface, void>("resetSoftwareKeyboard");
qCDebug(lcQpaInputMethods) << "@@@ RESETSOFTWAREKEYBOARD";
}
void hideSoftwareKeyboard()
{
qtInputDelegate().callMethod<void>("hideSoftwareKeyboard");
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface, void>("hideSoftwareKeyboard");
qCDebug(lcQpaInputMethods) << "@@@ HIDESOFTWAREKEYBOARD";
}
bool isSoftwareKeyboardVisible()
{
return qtInputDelegate().callMethod<jboolean>("isSoftwareKeyboardVisible");
AndroidBackendRegister *reg = QtAndroid::backendRegister();
return reg->callInterface<QtJniTypes::QtInputInterface, jboolean>(
"isSoftwareKeyboardVisible");
}
QRect softwareKeyboardRect()
@ -152,17 +151,17 @@ namespace QtAndroidInput
int getSelectHandleWidth()
{
return qtInputDelegate().callMethod<jint>("getSelectHandleWidth");
AndroidBackendRegister *reg = QtAndroid::backendRegister();
return reg->callInterface<QtJniTypes::QtInputInterface, jint>("getSelectHandleWidth");
}
void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl)
{
qtInputDelegate().callMethod<void>("updateHandles",
QtAndroidPrivate::activity(),
qtLayout().object<QtJniTypes::QtLayout>(),
mode, editMenuPos.x(), editMenuPos.y(), editButtons,
cursor.x(), cursor.y(),
anchor.x(), anchor.y(), rtl);
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface, void>(
"updateHandles", QtAndroidPrivate::activity(),
qtLayout().object<QtJniTypes::QtLayout>(), 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()

View File

@ -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<QtJniTypes::QtInputDelegate>(
"getInputDelegate");
}
return m_inputDelegate;
}
bool isQtApplication()
{
// Returns true if the app is a Qt app, i.e. Qt controls the whole app and

View File

@ -51,7 +51,6 @@ namespace QtAndroid
jclass applicationClass();
QtJniTypes::QtActivityDelegateBase qtActivityDelegate();
QtJniTypes::QtInputDelegate qtInputDelegate();
// Keep synchronized with flags in ActivityDelegate.java
enum SystemUiVisibility {

View File

@ -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<QtJniTypes::QtInputInterface, QtJniTypes::QtInputConnectionListener>(
"getInputConnectionListener");
m_nativeQtWindow = QJniObject::construct<QtJniTypes::QtWindow>(
QNativeInterface::QAndroidApplication::context(),
m_nativeParentQtWindow,
QtAndroid::qtInputDelegate());
QNativeInterface::QAndroidApplication::context(), m_nativeParentQtWindow, listener);
m_nativeViewId = m_nativeQtWindow.callMethod<jint>("getId");
if (window->isTopLevel())