Android: move input handling from QtActivityDelegate to separate class

To further simplify the code and logic of the delegate, move keyboard
input code to separate class. Make an input delegate available under the
QtActivityDelegate to allow classes like QtNative and the Activity to
access that. For now, it's okay to leave access from QtNative to that,
but for future even that should be simplified and the Activity should be
accessing that directly.

For the case where the QtInputDelegate needs access to
QtActivityDelegate, for now namely updateFullScreen(), a new Listener
is implemented to be implemented under QtActivityDelegate.

Along the way use newer JNI APIs under C++ QtAndroidInput.

Don't make them static methods, so that it can be possible later to
do various keyboard operations to specific activity and not a global
one.

Task-number: QTBUG-114593
Task-number: QTBUG-118077
Change-Id: I110b897f6f16d0ae5f5a645551b4a82e8ad3f2fb
Reviewed-by: Tinja Paavoseppä <tinja.paavoseppa@qt.io>
This commit is contained in:
Assam Boudjelthia 2023-09-16 03:31:33 +03:00
parent ed2fbed479
commit ac7f22ed0a
14 changed files with 943 additions and 867 deletions

View File

@ -13,6 +13,7 @@ set(java_sources
src/org/qtproject/qt/android/QtServiceBase.java
src/org/qtproject/qt/android/QtActivityDelegate.java
src/org/qtproject/qt/android/QtServiceDelegate.java
src/org/qtproject/qt/android/QtInputDelegate.java
src/org/qtproject/qt/android/QtLoader.java
src/org/qtproject/qt/android/QtActivityLoader.java
src/org/qtproject/qt/android/QtServiceLoader.java

View File

@ -134,9 +134,9 @@ public class CursorHandle implements ViewTreeObserver.OnPreDrawListener
int x2 = x + layoutLocation[0] - activityLocation[0];
int y2 = y + layoutLocation[1] + m_yShift + (activityLocationInWindow[1] - activityLocation[1]);
if (m_id == QtNative.IdCursorHandle) {
if (m_id == QtInputDelegate.IdCursorHandle) {
x2 -= m_popup.getWidth() / 2 ;
} else if ((m_id == QtNative.IdLeftHandle && !m_rtl) || (m_id == QtNative.IdRightHandle && m_rtl)) {
} else if ((m_id == QtInputDelegate.IdLeftHandle && !m_rtl) || (m_id == QtInputDelegate.IdRightHandle && m_rtl)) {
x2 -= m_popup.getWidth() * 3 / 4;
} else {
x2 -= m_popup.getWidth() / 4;
@ -176,7 +176,7 @@ public class CursorHandle implements ViewTreeObserver.OnPreDrawListener
public void updatePosition(int x, int y) {
y -= m_yShift;
if (Math.abs(m_lastX - x) > tolerance || Math.abs(m_lastY - y) > tolerance) {
QtNative.handleLocationChanged(m_id, x + m_posX, y + m_posY);
QtInputDelegate.handleLocationChanged(m_id, x + m_posX, y + m_posY);
m_lastX = x;
m_lastY = y;
}

View File

@ -22,9 +22,6 @@ import android.view.View;
public class QtActivityBase extends Activity
{
private long m_metaState;
public boolean m_backKeyPressedSent = false;
private boolean m_optionsMenuIsVisible = false;
// use this variable to pass any parameters to your application,
@ -144,24 +141,6 @@ public class QtActivityBase extends Activity
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
if (m_delegate.isStarted()
&& event.getAction() == KeyEvent.ACTION_MULTIPLE
&& event.getCharacters() != null
&& event.getCharacters().length() == 1
&& event.getKeyCode() == 0) {
QtNative.keyDown(0, event.getCharacters().charAt(0), event.getMetaState(), event.getRepeatCount() > 0);
QtNative.keyUp(0, event.getCharacters().charAt(0), event.getMetaState(), event.getRepeatCount() > 0);
}
if (QtNative.dispatchKeyEvent(event))
return true;
return super.dispatchKeyEvent(event);
}
@Override
public void onConfigurationChanged(Configuration newConfig)
{
@ -193,7 +172,24 @@ public class QtActivityBase extends Activity
m_delegate.setContextMenuVisible(true);
}
private int m_lastChar = 0;
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
if (m_delegate.isStarted() && m_delegate.getInputDelegate().handleDispatchKeyEvent(event))
return true;
return super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event)
{
boolean handled = m_delegate.getInputDelegate().handleDispatchGenericMotionEvent(event);
if (m_delegate.isStarted() && handled)
return true;
return super.dispatchGenericMotionEvent(event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
@ -201,32 +197,7 @@ public class QtActivityBase extends Activity
if (!m_delegate.isStarted() || !m_delegate.isPluginRunning())
return false;
m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode, event);
int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(m_metaState) | event.getMetaState());
int lc = c;
m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState);
if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
c = KeyEvent.getDeadChar(m_lastChar, c);
}
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
|| keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_MUTE)
&& System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
return false;
}
m_lastChar = lc;
if (keyCode == KeyEvent.KEYCODE_BACK) {
m_backKeyPressedSent = !m_delegate.isKeyboardVisible();
if (!m_backKeyPressedSent)
return true;
}
QtNative.keyDown(keyCode, c, event.getMetaState(), event.getRepeatCount() > 0);
return true;
return m_delegate.getInputDelegate().onKeyDown(keyCode, event);
}
@Override
@ -235,22 +206,7 @@ public class QtActivityBase extends Activity
if (!m_delegate.isStarted() || !m_delegate.isPluginRunning())
return false;
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
|| keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_MUTE)
&& System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
return false;
}
if (keyCode == KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
m_delegate.hideSoftwareKeyboard();
m_delegate.setKeyboardVisibility(false, System.nanoTime());
return true;
}
m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode, event);
QtNative.keyUp(keyCode, event.getUnicodeChar(), event.getMetaState(), event.getRepeatCount() > 0);
return true;
return m_delegate.getInputDelegate().onKeyUp(keyCode, event);
}
@Override
@ -315,15 +271,6 @@ public class QtActivityBase extends Activity
m_delegate.updateFullScreen();
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent ev)
{
if (m_delegate.isStarted() && QtNative.dispatchGenericMotionEvent(ev))
return true;
return super.dispatchGenericMotionEvent(ev);
}
@Override
protected void onNewIntent(Intent intent)
{

View File

@ -7,24 +7,15 @@ package org.qtproject.qt.android;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.Rect;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.text.method.MetaKeyKeyListener;
import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
@ -32,14 +23,9 @@ import android.util.TypedValue;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewConfiguration;
@ -53,13 +39,6 @@ import android.widget.ImageView;
import android.widget.PopupMenu;
import android.hardware.display.DisplayManager;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
@ -76,6 +55,7 @@ public class QtActivityDelegate
public static final int SYSTEM_UI_VISIBILITY_NORMAL = 0;
public static final int SYSTEM_UI_VISIBILITY_FULLSCREEN = 1;
public static final int SYSTEM_UI_VISIBILITY_TRANSLUCENT = 2;
private int m_systemUiVisibility = SYSTEM_UI_VISIBILITY_NORMAL;
private static String m_applicationParameters = null;
@ -83,34 +63,42 @@ public class QtActivityDelegate
private int m_nativeOrientation = Configuration.ORIENTATION_UNDEFINED;
private String m_mainLib;
private int m_softInputMode = 0;
private int m_systemUiVisibility = SYSTEM_UI_VISIBILITY_NORMAL;
private boolean m_started = false;
private boolean m_quitApp = true;
private boolean m_isPluginRunning = false;
private HashMap<Integer, QtSurface> m_surfaces = null;
private HashMap<Integer, View> m_nativeViews = null;
private QtLayout m_layout = null;
private ImageView m_splashScreen = null;
private boolean m_splashScreenSticky = false;
private QtEditText m_editText = null;
private InputMethodManager m_imm = null;
private boolean m_quitApp = true;
private View m_dummyView = null;
private boolean m_keyboardIsVisible = false;
private long m_showHideTimeStamp = System.nanoTime();
private int m_portraitKeyboardHeight = 0;
private int m_landscapeKeyboardHeight = 0;
private int m_probeKeyboardHeightDelay = 50; // ms
private CursorHandle m_cursorHandle;
private CursorHandle m_leftSelectionHandle;
private CursorHandle m_rightSelectionHandle;
private EditPopupMenu m_editPopupMenu;
private boolean m_isPluginRunning = false;
private QtAccessibilityDelegate m_accessibilityDelegate = null;
private QtInputDelegate.KeyboardVisibilityListener m_keyboardVisibilityListener =
new QtInputDelegate.KeyboardVisibilityListener() {
@Override
public void onKeyboardVisibilityChange() {
updateFullScreen();
}
};
private final QtInputDelegate m_inputDelegate = new QtInputDelegate(m_keyboardVisibilityListener);
QtActivityDelegate() { }
QtInputDelegate getInputDelegate()
{
return m_inputDelegate;
}
QtLayout getQtLayout()
{
return m_layout;
}
public void setSystemUiVisibility(int systemUiVisibility)
{
@ -166,272 +154,6 @@ public class QtActivityDelegate
}
}
public boolean isKeyboardVisible()
{
return m_keyboardIsVisible;
}
// input method hints - must be kept in sync with QTDIR/src/corelib/global/qnamespace.h
private final int ImhHiddenText = 0x1;
private final int ImhSensitiveData = 0x2;
private final int ImhNoAutoUppercase = 0x4;
private final int ImhPreferNumbers = 0x8;
private final int ImhPreferUppercase = 0x10;
private final int ImhPreferLowercase = 0x20;
private final int ImhNoPredictiveText = 0x40;
private final int ImhDate = 0x80;
private final int ImhTime = 0x100;
private final int ImhPreferLatin = 0x200;
private final int ImhMultiLine = 0x400;
private final int ImhDigitsOnly = 0x10000;
private final int ImhFormattedNumbersOnly = 0x20000;
private final int ImhUppercaseOnly = 0x40000;
private final int ImhLowercaseOnly = 0x80000;
private final int ImhDialableCharactersOnly = 0x100000;
private final int ImhEmailCharactersOnly = 0x200000;
private final int ImhUrlCharactersOnly = 0x400000;
private final int ImhLatinOnly = 0x800000;
// enter key type - must be kept in sync with QTDIR/src/corelib/global/qnamespace.h
private final int EnterKeyDefault = 0;
private final int EnterKeyReturn = 1;
private final int EnterKeyDone = 2;
private final int EnterKeyGo = 3;
private final int EnterKeySend = 4;
private final int EnterKeySearch = 5;
private final int EnterKeyNext = 6;
private final int EnterKeyPrevious = 7;
public boolean setKeyboardVisibility(boolean visibility, long timeStamp)
{
if (m_showHideTimeStamp > timeStamp)
return false;
m_showHideTimeStamp = timeStamp;
if (m_keyboardIsVisible == visibility)
return false;
m_keyboardIsVisible = visibility;
QtNative.keyboardVisibilityUpdated(m_keyboardIsVisible);
if (visibility == false)
updateFullScreen(); // Hiding the keyboard clears the immersive mode, so we need to set it again.
return true;
}
public void resetSoftwareKeyboard()
{
if (m_imm == null)
return;
m_editText.postDelayed(new Runnable() {
@Override
public void run() {
m_imm.restartInput(m_editText);
m_editText.m_optionsChanged = false;
}
}, 5);
}
public void showSoftwareKeyboard(final int x, final int y, final int width, final int height, final int inputHints, final int enterKeyType)
{
if (m_imm == null)
return;
DisplayMetrics metrics = new DisplayMetrics();
m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
// If the screen is in portrait mode than we estimate that keyboard height will not be higher than 2/5 of the screen.
// else than we estimate that keyboard height will not be higher than 2/3 of the screen
final int visibleHeight;
if (metrics.widthPixels < metrics.heightPixels)
visibleHeight = m_portraitKeyboardHeight != 0 ? m_portraitKeyboardHeight : metrics.heightPixels * 3 / 5;
else
visibleHeight = m_landscapeKeyboardHeight != 0 ? m_landscapeKeyboardHeight : metrics.heightPixels / 3;
if (m_softInputMode != 0) {
m_activity.getWindow().setSoftInputMode(m_softInputMode);
final boolean softInputIsHidden = (m_softInputMode & WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) != 0;
if (softInputIsHidden)
return;
} else {
if (height > visibleHeight)
m_activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
else
m_activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
}
int initialCapsMode = 0;
int imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
switch (enterKeyType) {
case EnterKeyReturn:
imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION;
break;
case EnterKeyGo:
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO;
break;
case EnterKeySend:
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_SEND;
break;
case EnterKeySearch:
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH;
break;
case EnterKeyNext:
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
break;
case EnterKeyPrevious:
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS;
break;
}
int inputType = android.text.InputType.TYPE_CLASS_TEXT;
if ((inputHints & (ImhPreferNumbers | ImhDigitsOnly | ImhFormattedNumbersOnly)) != 0) {
inputType = android.text.InputType.TYPE_CLASS_NUMBER;
if ((inputHints & ImhFormattedNumbersOnly) != 0) {
inputType |= (android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL
| android.text.InputType.TYPE_NUMBER_FLAG_SIGNED);
}
if ((inputHints & ImhHiddenText) != 0)
inputType |= android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD;
} else if ((inputHints & ImhDialableCharactersOnly) != 0) {
inputType = android.text.InputType.TYPE_CLASS_PHONE;
} else if ((inputHints & (ImhDate | ImhTime)) != 0) {
inputType = android.text.InputType.TYPE_CLASS_DATETIME;
if ((inputHints & (ImhDate | ImhTime)) != (ImhDate | ImhTime)) {
if ((inputHints & ImhDate) != 0)
inputType |= android.text.InputType.TYPE_DATETIME_VARIATION_DATE;
else
inputType |= android.text.InputType.TYPE_DATETIME_VARIATION_TIME;
} // else { TYPE_DATETIME_VARIATION_NORMAL(0) }
} else { // CLASS_TEXT
if ((inputHints & ImhHiddenText) != 0) {
inputType |= android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
} else if ((inputHints & ImhSensitiveData) != 0 ||
((inputHints & ImhNoPredictiveText) != 0 &&
System.getenv("QT_ANDROID_ENABLE_WORKAROUND_TO_DISABLE_PREDICTIVE_TEXT") != null)) {
inputType |= android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
} else if ((inputHints & ImhUrlCharactersOnly) != 0) {
inputType |= android.text.InputType.TYPE_TEXT_VARIATION_URI;
if (enterKeyType == 0) // not explicitly overridden
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO;
} else if ((inputHints & ImhEmailCharactersOnly) != 0) {
inputType |= android.text.InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
}
if ((inputHints & ImhMultiLine) != 0) {
inputType |= android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE;
// Clear imeOptions for Multi-Line Type
// User should be able to insert new line in such case
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
}
if ((inputHints & (ImhNoPredictiveText | ImhSensitiveData | ImhHiddenText)) != 0)
inputType |= android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
if ((inputHints & ImhUppercaseOnly) != 0) {
initialCapsMode |= android.text.TextUtils.CAP_MODE_CHARACTERS;
inputType |= android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
} else if ((inputHints & ImhLowercaseOnly) == 0 && (inputHints & ImhNoAutoUppercase) == 0) {
initialCapsMode |= android.text.TextUtils.CAP_MODE_SENTENCES;
inputType |= android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
}
}
if (enterKeyType == 0 && (inputHints & ImhMultiLine) != 0)
imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION;
m_editText.setInitialCapsMode(initialCapsMode);
m_editText.setImeOptions(imeOptions);
m_editText.setInputType(inputType);
m_layout.setLayoutParams(m_editText, new QtLayout.LayoutParams(width, height, x, y), false);
m_editText.requestFocus();
m_editText.postDelayed(new Runnable() {
@Override
public void run() {
m_imm.showSoftInput(m_editText, 0, new ResultReceiver(new Handler()) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case InputMethodManager.RESULT_SHOWN:
QtNativeInputConnection.updateCursorPosition();
//FALLTHROUGH
case InputMethodManager.RESULT_UNCHANGED_SHOWN:
setKeyboardVisibility(true, System.nanoTime());
if (m_softInputMode == 0) {
// probe for real keyboard height
m_layout.postDelayed(new Runnable() {
@Override
public void run() {
if (!m_keyboardIsVisible)
return;
DisplayMetrics metrics = new DisplayMetrics();
m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
Rect r = new Rect();
m_activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
if (metrics.heightPixels != r.bottom) {
if (metrics.widthPixels > metrics.heightPixels) { // landscape
if (m_landscapeKeyboardHeight != r.bottom) {
m_landscapeKeyboardHeight = r.bottom;
showSoftwareKeyboard(x, y, width, height, inputHints, enterKeyType);
}
} else {
if (m_portraitKeyboardHeight != r.bottom) {
m_portraitKeyboardHeight = r.bottom;
showSoftwareKeyboard(x, y, width, height, inputHints, enterKeyType);
}
}
} else {
// no luck ?
// maybe the delay was too short, so let's make it longer
if (m_probeKeyboardHeightDelay < 1000)
m_probeKeyboardHeightDelay *= 2;
}
}
}, m_probeKeyboardHeightDelay);
}
break;
case InputMethodManager.RESULT_HIDDEN:
case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
setKeyboardVisibility(false, System.nanoTime());
break;
}
}
});
if (m_editText.m_optionsChanged) {
m_imm.restartInput(m_editText);
m_editText.m_optionsChanged = false;
}
}
}, 15);
}
public void hideSoftwareKeyboard()
{
if (m_imm == null)
return;
m_imm.hideSoftInputFromWindow(m_editText.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;
}
}
});
}
void setStarted(boolean started)
{
m_started = started;
@ -489,101 +211,6 @@ public class QtActivityDelegate
return size;
}
public void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd)
{
if (m_imm == null)
return;
m_imm.updateSelection(m_editText, selStart, selEnd, candidatesStart, candidatesEnd);
}
// Values coming from QAndroidInputContext::CursorHandleShowMode
private static final int CursorHandleNotShown = 0;
private static final int CursorHandleShowNormal = 1;
private static final int CursorHandleShowSelection = 2;
private static final int CursorHandleShowEdit = 0x100;
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
*/
public void updateHandles(int mode, int editX, int editY, int editButtons, int x1, int y1, int x2, int y2, boolean rtl)
{
switch (mode & 0xff)
{
case CursorHandleNotShown:
if (m_cursorHandle != null) {
m_cursorHandle.hide();
m_cursorHandle = null;
}
if (m_rightSelectionHandle != null) {
m_rightSelectionHandle.hide();
m_leftSelectionHandle.hide();
m_rightSelectionHandle = null;
m_leftSelectionHandle = null;
}
if (m_editPopupMenu != null)
m_editPopupMenu.hide();
break;
case CursorHandleShowNormal:
if (m_cursorHandle == null) {
m_cursorHandle = new CursorHandle(m_activity, m_layout, QtNative.IdCursorHandle,
android.R.attr.textSelectHandle, false);
}
m_cursorHandle.setPosition(x1, y1);
if (m_rightSelectionHandle != null) {
m_rightSelectionHandle.hide();
m_leftSelectionHandle.hide();
m_rightSelectionHandle = null;
m_leftSelectionHandle = null;
}
break;
case CursorHandleShowSelection:
if (m_rightSelectionHandle == null) {
m_leftSelectionHandle = new CursorHandle(m_activity, m_layout, QtNative.IdLeftHandle,
!rtl ? android.R.attr.textSelectHandleLeft :
android.R.attr.textSelectHandleRight,
rtl);
m_rightSelectionHandle = new CursorHandle(m_activity, m_layout, QtNative.IdRightHandle,
!rtl ? android.R.attr.textSelectHandleRight :
android.R.attr.textSelectHandleLeft,
rtl);
}
m_leftSelectionHandle.setPosition(x1,y1);
m_rightSelectionHandle.setPosition(x2,y2);
if (m_cursorHandle != null) {
m_cursorHandle.hide();
m_cursorHandle = null;
}
mode |= CursorHandleShowEdit;
break;
}
if (!QtNative.hasClipboardText())
editButtons &= ~EditContextView.PASTE_BUTTON;
if ((mode & CursorHandleShowEdit) == CursorHandleShowEdit && editButtons != 0) {
m_editPopupMenu.setPosition(editX, editY, editButtons, m_cursorHandle, m_leftSelectionHandle,
m_rightSelectionHandle);
} else {
if (m_editPopupMenu != null)
m_editPopupMenu.hide();
}
}
private final DisplayManager.DisplayListener displayListener = new DisplayManager.DisplayListener()
{
@Override
@ -655,7 +282,7 @@ public class QtActivityDelegate
QtNative.setActivity(m_activity, this);
setActionBarVisibility(false);
m_softInputMode = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), 0).softInputMode;
m_inputDelegate.setSoftInputMode(m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), 0).softInputMode);
DisplayManager displayManager = (DisplayManager)m_activity.getSystemService(Context.DISPLAY_SERVICE);
displayManager.registerDisplayListener(displayListener, null);
@ -834,8 +461,8 @@ public class QtActivityDelegate
e.printStackTrace();
}
m_editText = new QtEditText(m_activity, this);
m_imm = (InputMethodManager)m_activity.getSystemService(Context.INPUT_METHOD_SERVICE);
m_inputDelegate.setEditText(new QtEditText(m_activity));
m_inputDelegate.setInputMethodManager((InputMethodManager)m_activity.getSystemService(Context.INPUT_METHOD_SERVICE));
m_surfaces = new HashMap<Integer, QtSurface>();
m_nativeViews = new HashMap<Integer, View>();
m_activity.registerForContextMenu(m_layout);
@ -865,7 +492,7 @@ public class QtActivityDelegate
m_layout.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (!m_keyboardIsVisible)
if (!m_inputDelegate.isKeyboardVisible())
return true;
Rect r = new Rect();
@ -874,17 +501,17 @@ public class QtActivityDelegate
m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
final int kbHeight = metrics.heightPixels - r.bottom;
if (kbHeight < 0) {
setKeyboardVisibility(false, System.nanoTime());
m_inputDelegate.setKeyboardVisibility(false, System.nanoTime());
return true;
}
final int[] location = new int[2];
m_layout.getLocationOnScreen(location);
QtNative.keyboardGeometryChanged(location[0], r.bottom - location[1],
QtInputDelegate.keyboardGeometryChanged(location[0], r.bottom - location[1],
r.width(), kbHeight);
return true;
}
});
m_editPopupMenu = new EditPopupMenu(m_activity, m_layout);
m_inputDelegate.setEditPopupMenu(new EditPopupMenu(m_activity, m_layout));
}
public void hideSplashScreen()
@ -1016,8 +643,8 @@ public class QtActivityDelegate
m_layout.postDelayed(new Runnable() {
@Override
public void run() {
m_layout.setLayoutParams(m_editText, new QtLayout.LayoutParams(w, h, x, y), false);
PopupMenu popup = new PopupMenu(m_activity, m_editText);
m_layout.setLayoutParams(m_inputDelegate.getQtEditText(), new QtLayout.LayoutParams(w, h, x, y), false);
PopupMenu popup = new PopupMenu(m_activity, m_inputDelegate.getQtEditText());
QtActivityDelegate.this.onCreatePopupMenu(popup.getMenu());
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override

View File

@ -16,7 +16,6 @@ public class QtEditText extends View
int m_imeOptions = 0;
int m_inputType = InputType.TYPE_CLASS_TEXT;
boolean m_optionsChanged = false;
QtActivityDelegate m_activityDelegate;
public void setImeOptions(int m_imeOptions)
{
@ -43,16 +42,11 @@ public class QtEditText extends View
m_optionsChanged = true;
}
public QtEditText(Context context, QtActivityDelegate activityDelegate)
public QtEditText(Context context)
{
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
m_activityDelegate = activityDelegate;
}
public QtActivityDelegate getActivityDelegate()
{
return m_activityDelegate;
}
@Override

View File

@ -72,7 +72,7 @@ class HideKeyboardRunnable implements Runnable {
}
final int kbHeight = screenHeight - r.bottom;
if (kbHeight < 100)
QtNative.activityDelegate().setKeyboardVisibility(false, m_hideTimeStamp);
QtNative.activityDelegate().getInputDelegate().setKeyboardVisibility(false, m_hideTimeStamp);
}
}
@ -93,7 +93,7 @@ public class QtInputConnection extends BaseInputConnection
if (closing) {
m_view.postDelayed(new HideKeyboardRunnable(), 100);
} else {
QtNative.activityDelegate().setKeyboardVisibility(true, System.nanoTime());
QtNative.activityDelegate().getInputDelegate().setKeyboardVisibility(true, System.nanoTime());
}
}
@ -256,7 +256,7 @@ public class QtInputConnection extends BaseInputConnection
break;
default:
QtNative.activityDelegate().hideSoftwareKeyboard();
QtNative.activityDelegate().getInputDelegate().hideSoftwareKeyboard();
break;
}
}

View File

@ -0,0 +1,746 @@
// Copyright (C) 2023 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;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.text.method.MetaKeyKeyListener;
import android.util.DisplayMetrics;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
public class QtInputDelegate {
// keyboard methods
public static native void keyDown(int key, int unicode, int modifier, boolean autoRepeat);
public static native void keyUp(int key, int unicode, int modifier, boolean autoRepeat);
public static native void keyboardVisibilityChanged(boolean visibility);
public static native void keyboardGeometryChanged(int x, int y, int width, int height);
// keyboard methods
// dispatch events methods
public static native boolean dispatchGenericMotionEvent(MotionEvent event);
public static native boolean dispatchKeyEvent(KeyEvent event);
// dispatch events methods
// handle methods
public static native void handleLocationChanged(int id, int x, int y);
// handle methods
private QtEditText m_editText = null;
private InputMethodManager m_imm = null;
private boolean m_keyboardIsVisible = false;
private long m_showHideTimeStamp = System.nanoTime();
private int m_portraitKeyboardHeight = 0;
private int m_landscapeKeyboardHeight = 0;
private int m_probeKeyboardHeightDelayMs = 50;
private CursorHandle m_cursorHandle;
private CursorHandle m_leftSelectionHandle;
private CursorHandle m_rightSelectionHandle;
private EditPopupMenu m_editPopupMenu;
// input method hints - must be kept in sync with QTDIR/src/corelib/global/qnamespace.h
private final int ImhHiddenText = 0x1;
private final int ImhSensitiveData = 0x2;
private final int ImhNoAutoUppercase = 0x4;
private final int ImhPreferNumbers = 0x8;
private final int ImhPreferUppercase = 0x10;
private final int ImhPreferLowercase = 0x20;
private final int ImhNoPredictiveText = 0x40;
private final int ImhDate = 0x80;
private final int ImhTime = 0x100;
private final int ImhPreferLatin = 0x200;
private final int ImhMultiLine = 0x400;
private final int ImhDigitsOnly = 0x10000;
private final int ImhFormattedNumbersOnly = 0x20000;
private final int ImhUppercaseOnly = 0x40000;
private final int ImhLowercaseOnly = 0x80000;
private final int ImhDialableCharactersOnly = 0x100000;
private final int ImhEmailCharactersOnly = 0x200000;
private final int ImhUrlCharactersOnly = 0x400000;
private final int ImhLatinOnly = 0x800000;
// enter key type - must be kept in sync with QTDIR/src/corelib/global/qnamespace.h
private final int EnterKeyDefault = 0;
private final int EnterKeyReturn = 1;
private final int EnterKeyDone = 2;
private final int EnterKeyGo = 3;
private final int EnterKeySend = 4;
private final int EnterKeySearch = 5;
private final int EnterKeyNext = 6;
private final int EnterKeyPrevious = 7;
private int m_softInputMode = 0;
private boolean m_isKeyboardHiding = false;
// Values coming from QAndroidInputContext::CursorHandleShowMode
private static final int CursorHandleNotShown = 0;
private static final int CursorHandleShowNormal = 1;
private static final int CursorHandleShowSelection = 2;
private static final int CursorHandleShowEdit = 0x100;
// Handle IDs
public static final int IdCursorHandle = 1;
public static final int IdLeftHandle = 2;
public static final int IdRightHandle = 3;
private static Boolean m_tabletEventSupported = null;
private static int m_oldX, m_oldY;
private long m_metaState;
private int m_lastChar = 0;
private boolean m_backKeyPressedSent = false;
// Note: because of the circular call to updateFullScreen() from QtActivityDelegate, we need
// a listener to be able to do that call from the delegate, because that's where that logic lives
public interface KeyboardVisibilityListener {
void onKeyboardVisibilityChange();
}
private final KeyboardVisibilityListener m_keyboardVisibilityListener;
QtInputDelegate(KeyboardVisibilityListener listener)
{
this.m_keyboardVisibilityListener = listener;
}
public boolean isKeyboardVisible()
{
return m_keyboardIsVisible;
}
public boolean isSoftwareKeyboardVisible()
{
return isKeyboardVisible() && !m_isKeyboardHiding;
}
void setSoftInputMode(int inputMode)
{
m_softInputMode = inputMode;
}
QtEditText getQtEditText()
{
return m_editText;
}
void setEditText(QtEditText editText)
{
m_editText = editText;
}
void setInputMethodManager(InputMethodManager inputMethodManager)
{
m_imm = inputMethodManager;
}
void setEditPopupMenu(EditPopupMenu editPopupMenu)
{
m_editPopupMenu = editPopupMenu;
}
private void keyboardVisibilityUpdated(boolean visibility)
{
m_isKeyboardHiding = 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();
}
public void resetSoftwareKeyboard()
{
if (m_imm == null)
return;
m_editText.postDelayed(new Runnable() {
@Override
public void run() {
m_imm.restartInput(m_editText);
m_editText.m_optionsChanged = false;
}
}, 5);
}
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)
{
QtNative.runAction(new Runnable() {
@Override
public void run() {
if (m_imm == null)
return;
DisplayMetrics metrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
// If the screen is in portrait mode than we estimate that keyboard height will not be higher than 2/5 of the screen.
// else than we estimate that keyboard height will not be higher than 2/3 of the screen
final int visibleHeight;
if (metrics.widthPixels < metrics.heightPixels)
visibleHeight = m_portraitKeyboardHeight != 0 ? m_portraitKeyboardHeight : metrics.heightPixels * 3 / 5;
else
visibleHeight = m_landscapeKeyboardHeight != 0 ? m_landscapeKeyboardHeight : metrics.heightPixels / 3;
if (m_softInputMode != 0) {
activity.getWindow().setSoftInputMode(m_softInputMode);
final boolean softInputIsHidden = (m_softInputMode & WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) != 0;
if (softInputIsHidden)
return;
} else {
if (height > visibleHeight)
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
else
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
}
int initialCapsMode = 0;
int imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
switch (enterKeyType) {
case EnterKeyReturn:
imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION;
break;
case EnterKeyGo:
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO;
break;
case EnterKeySend:
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_SEND;
break;
case EnterKeySearch:
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH;
break;
case EnterKeyNext:
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
break;
case EnterKeyPrevious:
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS;
break;
}
int inputType = android.text.InputType.TYPE_CLASS_TEXT;
if ((inputHints & (ImhPreferNumbers | ImhDigitsOnly | ImhFormattedNumbersOnly)) != 0) {
inputType = android.text.InputType.TYPE_CLASS_NUMBER;
if ((inputHints & ImhFormattedNumbersOnly) != 0) {
inputType |= (android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL
| android.text.InputType.TYPE_NUMBER_FLAG_SIGNED);
}
if ((inputHints & ImhHiddenText) != 0)
inputType |= android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD;
} else if ((inputHints & ImhDialableCharactersOnly) != 0) {
inputType = android.text.InputType.TYPE_CLASS_PHONE;
} else if ((inputHints & (ImhDate | ImhTime)) != 0) {
inputType = android.text.InputType.TYPE_CLASS_DATETIME;
if ((inputHints & (ImhDate | ImhTime)) != (ImhDate | ImhTime)) {
if ((inputHints & ImhDate) != 0)
inputType |= android.text.InputType.TYPE_DATETIME_VARIATION_DATE;
else
inputType |= android.text.InputType.TYPE_DATETIME_VARIATION_TIME;
} // else { TYPE_DATETIME_VARIATION_NORMAL(0) }
} else { // CLASS_TEXT
if ((inputHints & ImhHiddenText) != 0) {
inputType |= android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
} else if ((inputHints & ImhSensitiveData) != 0 ||
((inputHints & ImhNoPredictiveText) != 0 &&
System.getenv("QT_ANDROID_ENABLE_WORKAROUND_TO_DISABLE_PREDICTIVE_TEXT") != null)) {
inputType |= android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
} else if ((inputHints & ImhUrlCharactersOnly) != 0) {
inputType |= android.text.InputType.TYPE_TEXT_VARIATION_URI;
if (enterKeyType == 0) // not explicitly overridden
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO;
} else if ((inputHints & ImhEmailCharactersOnly) != 0) {
inputType |= android.text.InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
}
if ((inputHints & ImhMultiLine) != 0) {
inputType |= android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE;
// Clear imeOptions for Multi-Line Type
// User should be able to insert new line in such case
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
}
if ((inputHints & (ImhNoPredictiveText | ImhSensitiveData | ImhHiddenText)) != 0)
inputType |= android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
if ((inputHints & ImhUppercaseOnly) != 0) {
initialCapsMode |= android.text.TextUtils.CAP_MODE_CHARACTERS;
inputType |= android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
} else if ((inputHints & ImhLowercaseOnly) == 0 && (inputHints & ImhNoAutoUppercase) == 0) {
initialCapsMode |= android.text.TextUtils.CAP_MODE_SENTENCES;
inputType |= android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
}
}
if (enterKeyType == 0 && (inputHints & ImhMultiLine) != 0)
imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION;
m_editText.setInitialCapsMode(initialCapsMode);
m_editText.setImeOptions(imeOptions);
m_editText.setInputType(inputType);
layout.setLayoutParams(m_editText, new QtLayout.LayoutParams(width, height, x, y), false);
m_editText.requestFocus();
m_editText.postDelayed(new Runnable() {
@Override
public void run() {
m_imm.showSoftInput(m_editText, 0, new ResultReceiver(new Handler()) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case InputMethodManager.RESULT_SHOWN:
QtNativeInputConnection.updateCursorPosition();
//FALLTHROUGH
case InputMethodManager.RESULT_UNCHANGED_SHOWN:
setKeyboardVisibility(true, System.nanoTime());
if (m_softInputMode == 0) {
// probe for real keyboard height
layout.postDelayed(new Runnable() {
@Override
public void run() {
if (!m_keyboardIsVisible)
return;
DisplayMetrics metrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
Rect r = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
if (metrics.heightPixels != r.bottom) {
if (metrics.widthPixels > metrics.heightPixels) { // landscape
if (m_landscapeKeyboardHeight != r.bottom) {
m_landscapeKeyboardHeight = r.bottom;
showSoftwareKeyboard(activity, layout, x, y, width, height, inputHints, enterKeyType);
}
} else {
if (m_portraitKeyboardHeight != r.bottom) {
m_portraitKeyboardHeight = r.bottom;
showSoftwareKeyboard(activity, layout, x, y, width, height, inputHints, enterKeyType);
}
}
} else {
// no luck ?
// maybe the delay was too short, so let's make it longer
if (m_probeKeyboardHeightDelayMs < 1000)
m_probeKeyboardHeightDelayMs *= 2;
}
}
}, m_probeKeyboardHeightDelayMs);
}
break;
case InputMethodManager.RESULT_HIDDEN:
case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
setKeyboardVisibility(false, System.nanoTime());
break;
}
}
});
if (m_editText.m_optionsChanged) {
m_imm.restartInput(m_editText);
m_editText.m_optionsChanged = false;
}
}
}, 15);
}
});
}
public void hideSoftwareKeyboard()
{
m_isKeyboardHiding = true;
QtNative.runAction(new Runnable() {
@Override
public void run() {
if (m_imm == null)
return;
m_imm.hideSoftInputFromWindow(m_editText.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;
}
}
});
}
});
}
public void updateSelection(final int selStart, final int selEnd,
final int candidatesStart, final int candidatesEnd)
{
QtNative.runAction(new Runnable() {
@Override
public void run() {
if (m_imm == null)
return;
m_imm.updateSelection(m_editText, selStart, selEnd, candidatesStart, candidatesEnd);
}
});
}
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
*/
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(new Runnable() {
@Override
public void run() {
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)
{
switch (mode & 0xff)
{
case CursorHandleNotShown:
if (m_cursorHandle != null) {
m_cursorHandle.hide();
m_cursorHandle = null;
}
if (m_rightSelectionHandle != null) {
m_rightSelectionHandle.hide();
m_leftSelectionHandle.hide();
m_rightSelectionHandle = null;
m_leftSelectionHandle = null;
}
if (m_editPopupMenu != null)
m_editPopupMenu.hide();
break;
case CursorHandleShowNormal:
if (m_cursorHandle == null) {
m_cursorHandle = new CursorHandle(activity, layout, IdCursorHandle,
android.R.attr.textSelectHandle, false);
}
m_cursorHandle.setPosition(x1, y1);
if (m_rightSelectionHandle != null) {
m_rightSelectionHandle.hide();
m_leftSelectionHandle.hide();
m_rightSelectionHandle = null;
m_leftSelectionHandle = null;
}
break;
case CursorHandleShowSelection:
if (m_rightSelectionHandle == null) {
m_leftSelectionHandle = new CursorHandle(activity, layout, IdLeftHandle,
!rtl ? android.R.attr.textSelectHandleLeft :
android.R.attr.textSelectHandleRight,
rtl);
m_rightSelectionHandle = new CursorHandle(activity, layout, IdRightHandle,
!rtl ? android.R.attr.textSelectHandleRight :
android.R.attr.textSelectHandleLeft,
rtl);
}
m_leftSelectionHandle.setPosition(x1,y1);
m_rightSelectionHandle.setPosition(x2,y2);
if (m_cursorHandle != null) {
m_cursorHandle.hide();
m_cursorHandle = null;
}
mode |= CursorHandleShowEdit;
break;
}
if (!QtNative.hasClipboardText())
editButtons &= ~EditContextView.PASTE_BUTTON;
if ((mode & CursorHandleShowEdit) == CursorHandleShowEdit && editButtons != 0) {
m_editPopupMenu.setPosition(editX, editY, editButtons, m_cursorHandle, m_leftSelectionHandle,
m_rightSelectionHandle);
} else {
if (m_editPopupMenu != null)
m_editPopupMenu.hide();
}
}
public boolean onKeyDown(int keyCode, KeyEvent event)
{
m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode, event);
int metaState = MetaKeyKeyListener.getMetaState(m_metaState) | event.getMetaState();
int c = event.getUnicodeChar(metaState);
int lc = c;
m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState);
if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
c = KeyEvent.getDeadChar(m_lastChar, c);
}
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
|| keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_MUTE)
&& System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
return false;
}
m_lastChar = lc;
if (keyCode == KeyEvent.KEYCODE_BACK) {
m_backKeyPressedSent = !isKeyboardVisible();
if (!m_backKeyPressedSent)
return true;
}
QtInputDelegate.keyDown(keyCode, c, event.getMetaState(), event.getRepeatCount() > 0);
return true;
}
public boolean onKeyUp(int keyCode, KeyEvent event)
{
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
|| keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_MUTE)
&& System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
return false;
}
if (keyCode == KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
hideSoftwareKeyboard();
setKeyboardVisibility(false, System.nanoTime());
return true;
}
m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode, event);
boolean autoRepeat = event.getRepeatCount() > 0;
QtInputDelegate.keyUp(keyCode, event.getUnicodeChar(), event.getMetaState(), autoRepeat);
return true;
}
public boolean handleDispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_MULTIPLE
&& event.getCharacters() != null
&& event.getCharacters().length() == 1
&& event.getKeyCode() == 0) {
keyDown(0, event.getCharacters().charAt(0), event.getMetaState(),
event.getRepeatCount() > 0);
keyUp(0, event.getCharacters().charAt(0), event.getMetaState(),
event.getRepeatCount() > 0);
}
return dispatchKeyEvent(event);
}
public boolean handleDispatchGenericMotionEvent(MotionEvent event)
{
return dispatchGenericMotionEvent(event);
}
//////////////////////////////
// Mouse and Touch Input //
//////////////////////////////
// tablet methods
public static native boolean isTabletEventSupported();
public static native void tabletEvent(int winId, int deviceId, long time, int action,
int pointerType, int buttonState, float x, float y,
float pressure);
// tablet methods
// pointer methods
public static native void mouseDown(int winId, int x, int y);
public static native void mouseUp(int winId, int x, int y);
public static native void mouseMove(int winId, int x, int y);
public static native void mouseWheel(int winId, int x, int y, float hdelta, float vdelta);
public static native void touchBegin(int winId);
public static native void touchAdd(int winId, int pointerId, int action, boolean primary,
int x, int y, float major, float minor, float rotation,
float pressure);
public static native void touchEnd(int winId, int action);
public static native void touchCancel(int winId);
public static native void longPress(int winId, int x, int y);
// pointer methods
static private int getAction(int index, MotionEvent event)
{
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {
int hsz = event.getHistorySize();
if (hsz > 0) {
float x = event.getX(index);
float y = event.getY(index);
for (int h = 0; h < hsz; ++h) {
if ( event.getHistoricalX(index, h) != x ||
event.getHistoricalY(index, h) != y )
return 1;
}
return 2;
}
return 1;
}
if (action == MotionEvent.ACTION_DOWN
|| action == MotionEvent.ACTION_POINTER_DOWN && index == event.getActionIndex()) {
return 0;
} else if (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_POINTER_UP && index == event.getActionIndex()) {
return 3;
}
return 2;
}
static public void sendTouchEvent(MotionEvent event, int id)
{
int pointerType = 0;
if (m_tabletEventSupported == null)
m_tabletEventSupported = isTabletEventSupported();
switch (event.getToolType(0)) {
case MotionEvent.TOOL_TYPE_STYLUS:
pointerType = 1; // QTabletEvent::Pen
break;
case MotionEvent.TOOL_TYPE_ERASER:
pointerType = 3; // QTabletEvent::Eraser
break;
}
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
sendMouseEvent(event, id);
} else if (m_tabletEventSupported && pointerType != 0) {
tabletEvent(id, event.getDeviceId(), event.getEventTime(), event.getActionMasked(),
pointerType, event.getButtonState(),
event.getX(), event.getY(), event.getPressure());
} else {
touchBegin(id);
for (int i = 0; i < event.getPointerCount(); ++i) {
touchAdd(id,
event.getPointerId(i),
getAction(i, event),
i == 0,
(int)event.getX(i),
(int)event.getY(i),
event.getTouchMajor(i),
event.getTouchMinor(i),
event.getOrientation(i),
event.getPressure(i));
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchEnd(id, 0);
break;
case MotionEvent.ACTION_UP:
touchEnd(id, 2);
break;
case MotionEvent.ACTION_CANCEL:
touchCancel(id);
break;
default:
touchEnd(id, 1);
}
}
}
static public void sendTrackballEvent(MotionEvent event, int id)
{
sendMouseEvent(event,id);
}
static public boolean sendGenericMotionEvent(MotionEvent event, int id)
{
if (((event.getAction() & (MotionEvent.ACTION_SCROLL | MotionEvent.ACTION_HOVER_MOVE)) == 0)
|| (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != InputDevice.SOURCE_CLASS_POINTER) {
return false;
}
return sendMouseEvent(event, id);
}
static public boolean sendMouseEvent(MotionEvent event, int id)
{
switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP:
mouseUp(id, (int) event.getX(), (int) event.getY());
break;
case MotionEvent.ACTION_DOWN:
mouseDown(id, (int) event.getX(), (int) event.getY());
m_oldX = (int) event.getX();
m_oldY = (int) event.getY();
break;
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
mouseMove(id, (int) event.getX(), (int) event.getY());
} else {
int dx = (int) (event.getX() - m_oldX);
int dy = (int) (event.getY() - m_oldY);
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
mouseMove(id, (int) event.getX(), (int) event.getY());
m_oldX = (int) event.getX();
m_oldY = (int) event.getY();
}
}
break;
case MotionEvent.ACTION_SCROLL:
mouseWheel(id, (int) event.getX(), (int) event.getY(),
event.getAxisValue(MotionEvent.AXIS_HSCROLL),
event.getAxisValue(MotionEvent.AXIS_VSCROLL));
break;
default:
return false;
}
return true;
}
}

View File

@ -1,5 +1,5 @@
// Copyright (C) 2016 BogDan Vatra <bogdan@kde.org>
// Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2023 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;
@ -32,9 +32,7 @@ import android.content.ClipDescription;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.InputDevice;
import android.view.Display;
@ -69,7 +67,6 @@ public class QtNative
public static final String QtTAG = "Qt JAVA"; // string used for Log.x
private static ArrayList<Runnable> m_lostActions = new ArrayList<Runnable>(); // a list containing all actions which could not be performed (e.g. the main activity is destroyed, etc.)
private static boolean m_started = false;
private static boolean m_isKeyboardHiding = false;
private static int m_displayMetricsScreenWidthPixels = 0;
private static int m_displayMetricsScreenHeightPixels = 0;
private static int m_displayMetricsAvailableLeftPixels = 0;
@ -81,14 +78,12 @@ public class QtNative
private static double m_displayMetricsYDpi = .0;
private static double m_displayMetricsScaledDensity = 1.0;
private static double m_displayMetricsDensity = 1.0;
private static int m_oldx, m_oldy;
private static final int m_moveThreshold = 0;
private static ClipboardManager m_clipboardManager = null;
private static Method m_checkSelfPermissionMethod = null;
private static Boolean m_tabletEventSupported = null;
private static boolean m_usePrimaryClip = false;
public static QtThread m_qtThread = new QtThread();
private static final int KEYBOARD_HEIGHT_THRESHOLD = 100;
private static final String INVALID_OR_NULL_URI_ERROR_MESSAGE = "Received invalid/null Uri";
@ -353,7 +348,7 @@ public class QtNative
updateApplicationState(state);
}
private static void runAction(Runnable action)
static void runAction(Runnable action)
{
synchronized (m_mainActivityMutex) {
final Looper mainLooper = Looper.getMainLooper();
@ -505,8 +500,6 @@ public class QtNative
}
}
// application methods
public static native boolean startQtAndroidPlugin(String params);
public static native void startQtApplication();
@ -533,141 +526,6 @@ public class QtNative
});
}
//@ANDROID-9
static private int getAction(int index, MotionEvent event)
{
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {
int hsz = event.getHistorySize();
if (hsz > 0) {
float x = event.getX(index);
float y = event.getY(index);
for (int h = 0; h < hsz; ++h) {
if ( event.getHistoricalX(index, h) != x ||
event.getHistoricalY(index, h) != y )
return 1;
}
return 2;
}
return 1;
}
if (action == MotionEvent.ACTION_DOWN
|| action == MotionEvent.ACTION_POINTER_DOWN && index == event.getActionIndex()) {
return 0;
} else if (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_POINTER_UP && index == event.getActionIndex()) {
return 3;
}
return 2;
}
//@ANDROID-9
static public void sendTouchEvent(MotionEvent event, int id)
{
int pointerType = 0;
if (m_tabletEventSupported == null)
m_tabletEventSupported = isTabletEventSupported();
switch (event.getToolType(0)) {
case MotionEvent.TOOL_TYPE_STYLUS:
pointerType = 1; // QTabletEvent::Pen
break;
case MotionEvent.TOOL_TYPE_ERASER:
pointerType = 3; // QTabletEvent::Eraser
break;
}
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
sendMouseEvent(event, id);
} else if (m_tabletEventSupported && pointerType != 0) {
tabletEvent(id, event.getDeviceId(), event.getEventTime(), event.getActionMasked(), pointerType,
event.getButtonState(), event.getX(), event.getY(), event.getPressure());
} else {
touchBegin(id);
for (int i = 0; i < event.getPointerCount(); ++i) {
touchAdd(id,
event.getPointerId(i),
getAction(i, event),
i == 0,
(int)event.getX(i),
(int)event.getY(i),
event.getTouchMajor(i),
event.getTouchMinor(i),
event.getOrientation(i),
event.getPressure(i));
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchEnd(id, 0);
break;
case MotionEvent.ACTION_UP:
touchEnd(id, 2);
break;
case MotionEvent.ACTION_CANCEL:
touchCancel(id);
break;
default:
touchEnd(id, 1);
}
}
}
static public void sendTrackballEvent(MotionEvent event, int id)
{
sendMouseEvent(event,id);
}
static public boolean sendGenericMotionEvent(MotionEvent event, int id)
{
if (((event.getAction() & (MotionEvent.ACTION_SCROLL | MotionEvent.ACTION_HOVER_MOVE)) == 0)
|| (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != InputDevice.SOURCE_CLASS_POINTER) {
return false;
}
return sendMouseEvent(event, id);
}
static public boolean sendMouseEvent(MotionEvent event, int id)
{
switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP:
mouseUp(id, (int) event.getX(), (int) event.getY());
break;
case MotionEvent.ACTION_DOWN:
mouseDown(id, (int) event.getX(), (int) event.getY());
m_oldx = (int) event.getX();
m_oldy = (int) event.getY();
break;
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
mouseMove(id, (int) event.getX(), (int) event.getY());
} else {
int dx = (int) (event.getX() - m_oldx);
int dy = (int) (event.getY() - m_oldy);
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
mouseMove(id, (int) event.getX(), (int) event.getY());
m_oldx = (int) event.getX();
m_oldy = (int) event.getY();
}
}
break;
case MotionEvent.ACTION_SCROLL:
mouseWheel(id, (int) event.getX(), (int) event.getY(),
event.getAxisValue(MotionEvent.AXIS_HSCROLL), event.getAxisValue(MotionEvent.AXIS_VSCROLL));
break;
default:
return false;
}
return true;
}
public static Context getContext() {
if (m_activity != null)
return m_activity;
@ -686,82 +544,7 @@ public class QtNative
return perm;
}
private static void updateSelection(final int selStart,
final int selEnd,
final int candidatesStart,
final int candidatesEnd)
{
runAction(new Runnable() {
@Override
public void run() {
if (m_activityDelegate != null)
m_activityDelegate.updateSelection(selStart, selEnd, candidatesStart, candidatesEnd);
}
});
}
private static int getSelectHandleWidth()
{
return m_activityDelegate.getSelectHandleWidth();
}
private static void updateHandles(final int mode,
final int editX,
final int editY,
final int editButtons,
final int x1,
final int y1,
final int x2,
final int y2,
final boolean rtl)
{
runAction(new Runnable() {
@Override
public void run() {
m_activityDelegate.updateHandles(mode, editX, editY, editButtons, x1, y1, x2, y2, rtl);
}
});
}
private static void showSoftwareKeyboard(final int x,
final int y,
final int width,
final int height,
final int inputHints,
final int enterKeyType)
{
runAction(new Runnable() {
@Override
public void run() {
if (m_activityDelegate != null)
m_activityDelegate.showSoftwareKeyboard(x, y, width, height, inputHints, enterKeyType);
}
});
}
private static void resetSoftwareKeyboard()
{
runAction(new Runnable() {
@Override
public void run() {
if (m_activityDelegate != null)
m_activityDelegate.resetSoftwareKeyboard();
}
});
}
private static void hideSoftwareKeyboard()
{
m_isKeyboardHiding = true;
runAction(new Runnable() {
@Override
public void run() {
if (m_activityDelegate != null)
m_activityDelegate.hideSoftwareKeyboard();
}
});
}
// TODO get rid of the delegation from QtNative, call directly the Activity in c++
private static void setSystemUiVisibility(final int systemUiVisibility)
{
runAction(new Runnable() {
@ -775,11 +558,6 @@ public class QtNative
});
}
public static boolean isSoftwareKeyboardVisible()
{
return m_activityDelegate.isKeyboardVisible() && !m_isKeyboardHiding;
}
private static void notifyAccessibilityLocationChange(final int viewId)
{
runAction(new Runnable() {
@ -842,7 +620,8 @@ public class QtNative
public static void notifyQtAndroidPluginRunning(final boolean running)
{
m_activityDelegate.notifyQtAndroidPluginRunning(running);
if (m_activityDelegate != null)
m_activityDelegate.notifyQtAndroidPluginRunning(running);
}
private static void registerClipboardManager()
@ -1153,7 +932,8 @@ public class QtNative
runAction(new Runnable() {
@Override
public void run() {
m_activityDelegate.initializeAccessibility();
if (m_activityDelegate != null)
m_activityDelegate.initializeAccessibility();
}
});
}
@ -1169,12 +949,6 @@ public class QtNative
});
}
public static void keyboardVisibilityUpdated(boolean visibility)
{
m_isKeyboardHiding = false;
keyboardVisibilityChanged(visibility);
}
private static String[] listAssetContent(android.content.res.AssetManager asset, String path) {
String [] list;
ArrayList<String> res = new ArrayList<String>();
@ -1246,42 +1020,6 @@ public class QtNative
// screen methods
public static native void handleUiDarkModeChanged(int newUiMode);
// pointer methods
public static native void mouseDown(int winId, int x, int y);
public static native void mouseUp(int winId, int x, int y);
public static native void mouseMove(int winId, int x, int y);
public static native void mouseWheel(int winId, int x, int y, float hdelta, float vdelta);
public static native void touchBegin(int winId);
public static native void touchAdd(int winId, int pointerId, int action, boolean primary, int x, int y, float major, float minor, float rotation, float pressure);
public static native void touchEnd(int winId, int action);
public static native void touchCancel(int winId);
public static native void longPress(int winId, int x, int y);
// pointer methods
// tablet methods
public static native boolean isTabletEventSupported();
public static native void tabletEvent(int winId, int deviceId, long time, int action, int pointerType, int buttonState, float x, float y, float pressure);
// tablet methods
// keyboard methods
public static native void keyDown(int key, int unicode, int modifier, boolean autoRepeat);
public static native void keyUp(int key, int unicode, int modifier, boolean autoRepeat);
public static native void keyboardVisibilityChanged(boolean visibility);
public static native void keyboardGeometryChanged(int x, int y, int width, int height);
// keyboard methods
// handle methods
public static final int IdCursorHandle = 1;
public static final int IdLeftHandle = 2;
public static final int IdRightHandle = 3;
public static native void handleLocationChanged(int id, int x, int y);
// handle methods
// dispatch events methods
public static native boolean dispatchGenericMotionEvent(MotionEvent ev);
public static native boolean dispatchKeyEvent(KeyEvent event);
// dispatch events methods
// surface methods
public static native void setSurface(int id, Object surface, int w, int h);
// surface methods

View File

@ -36,7 +36,7 @@ public class QtSurface extends SurfaceView implements SurfaceHolder.Callback
m_gestureDetector =
new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
public void onLongPress(MotionEvent event) {
QtNative.longPress(getId(), (int) event.getX(), (int) event.getY());
QtInputDelegate.longPress(getId(), (int) event.getX(), (int) event.getY());
}
});
m_gestureDetector.setIsLongpressEnabled(true);
@ -70,7 +70,7 @@ public class QtSurface extends SurfaceView implements SurfaceHolder.Callback
// In case when Surface is moved, we should also add this move to event position
event.setLocation(event.getX() + getX(), event.getY() + getY());
QtNative.sendTouchEvent(event, getId());
QtInputDelegate.sendTouchEvent(event, getId());
m_gestureDetector.onTouchEvent(event);
return true;
}
@ -78,13 +78,13 @@ public class QtSurface extends SurfaceView implements SurfaceHolder.Callback
@Override
public boolean onTrackballEvent(MotionEvent event)
{
QtNative.sendTrackballEvent(event, getId());
QtInputDelegate.sendTrackballEvent(event, getId());
return true;
}
@Override
public boolean onGenericMotionEvent(MotionEvent event)
{
return QtNative.sendGenericMotionEvent(event, getId());
return QtInputDelegate.sendGenericMotionEvent(event, getId());
}
}

View File

@ -26,8 +26,6 @@ namespace QtAndroidPrivate {
ResumePauseListener::~ResumePauseListener() {}
void ResumePauseListener::handlePause() {}
void ResumePauseListener::handleResume() {}
GenericMotionEventListener::~GenericMotionEventListener() {}
KeyEventListener::~KeyEventListener() {}
}
static JavaVM *g_javaVM = nullptr;
@ -42,40 +40,6 @@ Q_CONSTINIT static QBasicAtomicInt g_serviceSetupLockers = Q_BASIC_ATOMIC_INITIA
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex);
namespace {
struct GenericMotionEventListeners {
QMutex mutex;
QList<QtAndroidPrivate::GenericMotionEventListener *> listeners;
};
}
Q_GLOBAL_STATIC(GenericMotionEventListeners, g_genericMotionEventListeners)
static jboolean dispatchGenericMotionEvent(JNIEnv *, jclass, jobject event)
{
jboolean ret = JNI_FALSE;
QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
for (auto *listener : std::as_const(g_genericMotionEventListeners()->listeners))
ret |= listener->handleGenericMotionEvent(event);
return ret;
}
namespace {
struct KeyEventListeners {
QMutex mutex;
QList<QtAndroidPrivate::KeyEventListener *> listeners;
};
}
Q_GLOBAL_STATIC(KeyEventListeners, g_keyEventListeners)
static jboolean dispatchKeyEvent(JNIEnv *, jclass, jobject event)
{
jboolean ret = JNI_FALSE;
QMutexLocker locker(&g_keyEventListeners()->mutex);
for (auto *listener : std::as_const(g_keyEventListeners()->listeners))
ret |= listener->handleKeyEvent(event);
return ret;
}
static jboolean updateNativeActivity(JNIEnv *env, jclass = nullptr)
{
@ -272,8 +236,6 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
}
static const JNINativeMethod methods[] = {
{"dispatchGenericMotionEvent", "(Landroid/view/MotionEvent;)Z", reinterpret_cast<void *>(dispatchGenericMotionEvent)},
{"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast<void *>(dispatchKeyEvent)},
{"updateNativeActivity", "()Z", reinterpret_cast<void *>(updateNativeActivity) },
};
@ -331,30 +293,6 @@ jint QtAndroidPrivate::androidSdkVersion()
return sdkVersion;
}
void QtAndroidPrivate::registerGenericMotionEventListener(QtAndroidPrivate::GenericMotionEventListener *listener)
{
QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
g_genericMotionEventListeners()->listeners.push_back(listener);
}
void QtAndroidPrivate::unregisterGenericMotionEventListener(QtAndroidPrivate::GenericMotionEventListener *listener)
{
QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
g_genericMotionEventListeners()->listeners.removeOne(listener);
}
void QtAndroidPrivate::registerKeyEventListener(QtAndroidPrivate::KeyEventListener *listener)
{
QMutexLocker locker(&g_keyEventListeners()->mutex);
g_keyEventListeners()->listeners.push_back(listener);
}
void QtAndroidPrivate::unregisterKeyEventListener(QtAndroidPrivate::KeyEventListener *listener)
{
QMutexLocker locker(&g_keyEventListeners()->mutex);
g_keyEventListeners()->listeners.removeOne(listener);
}
void QtAndroidPrivate::waitForServiceSetup()
{
g_waitForServiceSetupSemaphore->acquire();

View File

@ -49,20 +49,6 @@ namespace QtAndroidPrivate
virtual void handleResume();
};
class Q_CORE_EXPORT GenericMotionEventListener
{
public:
virtual ~GenericMotionEventListener();
virtual bool handleGenericMotionEvent(jobject event) = 0;
};
class Q_CORE_EXPORT KeyEventListener
{
public:
virtual ~KeyEventListener();
virtual bool handleKeyEvent(jobject event) = 0;
};
class Q_CORE_EXPORT OnBindListener
{
public:
@ -95,12 +81,6 @@ namespace QtAndroidPrivate
Q_CORE_EXPORT void registerResumePauseListener(ResumePauseListener *listener);
Q_CORE_EXPORT void unregisterResumePauseListener(ResumePauseListener *listener);
Q_CORE_EXPORT void registerGenericMotionEventListener(GenericMotionEventListener *listener);
Q_CORE_EXPORT void unregisterGenericMotionEventListener(GenericMotionEventListener *listener);
Q_CORE_EXPORT void registerKeyEventListener(KeyEventListener *listener);
Q_CORE_EXPORT void unregisterKeyEventListener(KeyEventListener *listener);
Q_CORE_EXPORT void waitForServiceSetup();
Q_CORE_EXPORT int acuqireServiceSetup(int flags);
Q_CORE_EXPORT void setOnBindListener(OnBindListener *listener);

View File

@ -1,3 +1,4 @@
// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
@ -22,6 +23,10 @@ Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods");
using namespace QtAndroid;
Q_DECLARE_JNI_CLASS(QtInputDelegate, "org/qtproject/qt/android/QtInputDelegate")
Q_DECLARE_JNI_CLASS(QtActivityDelegate, "org/qtproject/qt/android/QtActivityDelegate")
Q_DECLARE_JNI_CLASS(QtLayout, "org/qtproject/qt/android/QtLayout")
namespace QtAndroidInput
{
static bool m_ignoreMouseEvents = false;
@ -31,12 +36,87 @@ namespace QtAndroidInput
static QPointer<QWindow> m_mouseGrabber;
GenericMotionEventListener::~GenericMotionEventListener() {}
namespace {
struct GenericMotionEventListeners {
QMutex mutex;
QList<QtAndroidInput::GenericMotionEventListener *> listeners;
};
}
Q_GLOBAL_STATIC(GenericMotionEventListeners, g_genericMotionEventListeners)
static jboolean dispatchGenericMotionEvent(JNIEnv *, jclass, jobject event)
{
jboolean ret = JNI_FALSE;
QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
for (auto *listener : std::as_const(g_genericMotionEventListeners()->listeners))
ret |= listener->handleGenericMotionEvent(event);
return ret;
}
KeyEventListener::~KeyEventListener() {}
namespace {
struct KeyEventListeners {
QMutex mutex;
QList<QtAndroidInput::KeyEventListener *> listeners;
};
}
Q_GLOBAL_STATIC(KeyEventListeners, g_keyEventListeners)
static jboolean dispatchKeyEvent(JNIEnv *, jclass, jobject event)
{
jboolean ret = JNI_FALSE;
QMutexLocker locker(&g_keyEventListeners()->mutex);
for (auto *listener : std::as_const(g_keyEventListeners()->listeners))
ret |= listener->handleKeyEvent(event);
return ret;
}
void registerGenericMotionEventListener(QtAndroidInput::GenericMotionEventListener *listener)
{
QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
g_genericMotionEventListeners()->listeners.push_back(listener);
}
void unregisterGenericMotionEventListener(QtAndroidInput::GenericMotionEventListener *listener)
{
QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
g_genericMotionEventListeners()->listeners.removeOne(listener);
}
void registerKeyEventListener(QtAndroidInput::KeyEventListener *listener)
{
QMutexLocker locker(&g_keyEventListeners()->mutex);
g_keyEventListeners()->listeners.push_back(listener);
}
void unregisterKeyEventListener(QtAndroidInput::KeyEventListener *listener)
{
QMutexLocker locker(&g_keyEventListeners()->mutex);
g_keyEventListeners()->listeners.removeOne(listener);
}
// FIXME: avoid direct access to QtActivityDelegate
QJniObject qtActivityDelegate()
{
return QtAndroidPrivate::activity().callMethod<QtJniTypes::QtActivityDelegate>(
"getActivityDelegate");
}
QJniObject qtInputDelegate()
{
return qtActivityDelegate().callMethod<QtJniTypes::QtInputDelegate>("getInputDelegate");
}
QJniObject qtLayout()
{
return qtActivityDelegate().callMethod<QtJniTypes::QtLayout>("getQtLayout");
}
void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd)
{
qCDebug(lcQpaInputMethods) << ">>> UPDATESELECTION" << selStart << selEnd << candidatesStart << candidatesEnd;
QJniObject::callStaticMethod<void>(applicationClass(),
"updateSelection",
"(IIII)V",
qtInputDelegate().callMethod<void>("updateSelection",
selStart,
selEnd,
candidatesStart,
@ -45,9 +125,9 @@ namespace QtAndroidInput
void showSoftwareKeyboard(int left, int top, int width, int height, int inputHints, int enterKeyType)
{
QJniObject::callStaticMethod<void>(applicationClass(),
"showSoftwareKeyboard",
"(IIIIII)V",
qtInputDelegate().callMethod<void>("showSoftwareKeyboard",
QtAndroidPrivate::activity(),
qtLayout().object<QtJniTypes::QtLayout>(),
left,
top,
width,
@ -59,19 +139,19 @@ namespace QtAndroidInput
void resetSoftwareKeyboard()
{
QJniObject::callStaticMethod<void>(applicationClass(), "resetSoftwareKeyboard");
qtInputDelegate().callMethod<void>("resetSoftwareKeyboard");
qCDebug(lcQpaInputMethods) << "@@@ RESETSOFTWAREKEYBOARD";
}
void hideSoftwareKeyboard()
{
QJniObject::callStaticMethod<void>(applicationClass(), "hideSoftwareKeyboard");
qtInputDelegate().callMethod<void>("hideSoftwareKeyboard");
qCDebug(lcQpaInputMethods) << "@@@ HIDESOFTWAREKEYBOARD";
}
bool isSoftwareKeyboardVisible()
{
return QJniObject::callStaticMethod<jboolean>(applicationClass(), "isSoftwareKeyboardVisible");
return qtInputDelegate().callMethod<jboolean>("isSoftwareKeyboardVisible");
}
QRect softwareKeyboardRect()
@ -81,12 +161,14 @@ namespace QtAndroidInput
int getSelectHandleWidth()
{
return QJniObject::callStaticMethod<jint>(applicationClass(), "getSelectHandleWidth");
return qtInputDelegate().callMethod<jint>("getSelectHandleWidth");
}
void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl)
{
QJniObject::callStaticMethod<void>(applicationClass(), "updateHandles", "(IIIIIIIIZ)V",
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);
@ -817,7 +899,8 @@ namespace QtAndroidInput
}
static JNINativeMethod methods[] = {
static const JNINativeMethod methods[] = {
{"touchBegin","(I)V",(void*)touchBegin},
{"touchAdd","(IIIZIIFFFF)V",(void*)touchAdd},
{"touchEnd","(II)V",(void*)touchEnd},
@ -833,14 +916,16 @@ namespace QtAndroidInput
{"keyUp", "(IIIZ)V", (void *)keyUp},
{"keyboardVisibilityChanged", "(Z)V", (void *)keyboardVisibilityChanged},
{"keyboardGeometryChanged", "(IIII)V", (void *)keyboardGeometryChanged},
{"handleLocationChanged", "(III)V", (void *)handleLocationChanged}
{"handleLocationChanged", "(III)V", (void *)handleLocationChanged},
{"dispatchGenericMotionEvent", "(Landroid/view/MotionEvent;)Z", reinterpret_cast<void *>(dispatchGenericMotionEvent)},
{"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast<void *>(dispatchKeyEvent)},
};
bool registerNatives(JNIEnv *env)
bool registerNatives()
{
jclass appClass = QtAndroid::applicationClass();
if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
QJniEnvironment qenv;
if (!qenv.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtInputDelegate>::className(),
methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
return false;
}

View File

@ -29,7 +29,27 @@ namespace QtAndroidInput
QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false);
int getSelectHandleWidth();
bool registerNatives(JNIEnv *env);
class GenericMotionEventListener
{
public:
virtual ~GenericMotionEventListener();
virtual bool handleGenericMotionEvent(jobject event) = 0;
};
class KeyEventListener
{
public:
virtual ~KeyEventListener();
virtual bool handleKeyEvent(jobject event) = 0;
};
void registerGenericMotionEventListener(GenericMotionEventListener *listener);
void unregisterGenericMotionEventListener(GenericMotionEventListener *listener);
void registerKeyEventListener(KeyEventListener *listener);
void unregisterKeyEventListener(KeyEventListener *listener);
bool registerNatives();
}
QT_END_NAMESPACE

View File

@ -938,7 +938,7 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
JNIEnv *env = uenv.nativeEnvironment;
if (!registerNatives(env)
|| !QtAndroidInput::registerNatives(env)
|| !QtAndroidInput::registerNatives()
|| !QtAndroidMenu::registerNatives(env)
|| !QtAndroidAccessibility::registerNatives(env)
|| !QtAndroidDialogHelpers::registerNatives(env)) {