diff --git a/src/android/jar/CMakeLists.txt b/src/android/jar/CMakeLists.txt index dd6a1dacbaa..0f8d08fffe1 100644 --- a/src/android/jar/CMakeLists.txt +++ b/src/android/jar/CMakeLists.txt @@ -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 diff --git a/src/android/jar/src/org/qtproject/qt/android/CursorHandle.java b/src/android/jar/src/org/qtproject/qt/android/CursorHandle.java index 87257adb3da..d1bc72f9ff7 100644 --- a/src/android/jar/src/org/qtproject/qt/android/CursorHandle.java +++ b/src/android/jar/src/org/qtproject/qt/android/CursorHandle.java @@ -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; } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java index fe2eaf0b9d6..9c1d38982a7 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java @@ -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) { diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java index b180685af5b..e7acb06ca69 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java @@ -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 m_surfaces = null; private HashMap 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(); m_nativeViews = new HashMap(); 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 diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEditText.java b/src/android/jar/src/org/qtproject/qt/android/QtEditText.java index 66d9ccff8b7..b2df8959cce 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtEditText.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtEditText.java @@ -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 diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java b/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java index abcc76da17e..bce476a94c6 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java @@ -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; } } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java new file mode 100644 index 00000000000..4c2188d23ab --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java @@ -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; + } +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtNative.java b/src/android/jar/src/org/qtproject/qt/android/QtNative.java index 6d6dfc4dda9..dff4a27a36d 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtNative.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtNative.java @@ -1,5 +1,5 @@ // Copyright (C) 2016 BogDan Vatra -// 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 m_lostActions = new ArrayList(); // 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 res = new ArrayList(); @@ -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 diff --git a/src/android/jar/src/org/qtproject/qt/android/QtSurface.java b/src/android/jar/src/org/qtproject/qt/android/QtSurface.java index 42830783e76..13ccbd93d49 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtSurface.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtSurface.java @@ -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()); } } diff --git a/src/corelib/kernel/qjnihelpers.cpp b/src/corelib/kernel/qjnihelpers.cpp index 78d05261e5b..47c4e0ed179 100644 --- a/src/corelib/kernel/qjnihelpers.cpp +++ b/src/corelib/kernel/qjnihelpers.cpp @@ -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 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 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(dispatchGenericMotionEvent)}, - {"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast(dispatchKeyEvent)}, {"updateNativeActivity", "()Z", reinterpret_cast(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(); diff --git a/src/corelib/kernel/qjnihelpers_p.h b/src/corelib/kernel/qjnihelpers_p.h index 45c10010991..6ea3f670a17 100644 --- a/src/corelib/kernel/qjnihelpers_p.h +++ b/src/corelib/kernel/qjnihelpers_p.h @@ -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); diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index a46ccbe59d4..afaf5f1d2a3 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -1,3 +1,4 @@ +// Copyright (C) 2023 The Qt Company Ltd. // Copyright (C) 2012 BogDan Vatra // Copyright (C) 2016 Olivier Goffart // 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 m_mouseGrabber; + GenericMotionEventListener::~GenericMotionEventListener() {} + namespace { + struct GenericMotionEventListeners { + QMutex mutex; + QList 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 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( + "getActivityDelegate"); + } + + QJniObject qtInputDelegate() + { + return qtActivityDelegate().callMethod("getInputDelegate"); + } + + QJniObject qtLayout() + { + return qtActivityDelegate().callMethod("getQtLayout"); + } + void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd) { qCDebug(lcQpaInputMethods) << ">>> UPDATESELECTION" << selStart << selEnd << candidatesStart << candidatesEnd; - QJniObject::callStaticMethod(applicationClass(), - "updateSelection", - "(IIII)V", + qtInputDelegate().callMethod("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(applicationClass(), - "showSoftwareKeyboard", - "(IIIIII)V", + qtInputDelegate().callMethod("showSoftwareKeyboard", + QtAndroidPrivate::activity(), + qtLayout().object(), left, top, width, @@ -59,19 +139,19 @@ namespace QtAndroidInput void resetSoftwareKeyboard() { - QJniObject::callStaticMethod(applicationClass(), "resetSoftwareKeyboard"); + qtInputDelegate().callMethod("resetSoftwareKeyboard"); qCDebug(lcQpaInputMethods) << "@@@ RESETSOFTWAREKEYBOARD"; } void hideSoftwareKeyboard() { - QJniObject::callStaticMethod(applicationClass(), "hideSoftwareKeyboard"); + qtInputDelegate().callMethod("hideSoftwareKeyboard"); qCDebug(lcQpaInputMethods) << "@@@ HIDESOFTWAREKEYBOARD"; } bool isSoftwareKeyboardVisible() { - return QJniObject::callStaticMethod(applicationClass(), "isSoftwareKeyboardVisible"); + return qtInputDelegate().callMethod("isSoftwareKeyboardVisible"); } QRect softwareKeyboardRect() @@ -81,12 +161,14 @@ namespace QtAndroidInput int getSelectHandleWidth() { - return QJniObject::callStaticMethod(applicationClass(), "getSelectHandleWidth"); + return qtInputDelegate().callMethod("getSelectHandleWidth"); } void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl) { - QJniObject::callStaticMethod(applicationClass(), "updateHandles", "(IIIIIIIIZ)V", + qtInputDelegate().callMethod("updateHandles", + QtAndroidPrivate::activity(), + qtLayout().object(), 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(dispatchGenericMotionEvent)}, + {"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast(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::className(), + methods, sizeof(methods) / sizeof(methods[0]))) { __android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed"); return false; } diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h index 06d13cc6800..982065eff03 100644 --- a/src/plugins/platforms/android/androidjniinput.h +++ b/src/plugins/platforms/android/androidjniinput.h @@ -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 diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index 307ad62f191..0a0c7b7a9fa 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -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)) {