4package org.qtproject.qt.android;
7import android.app.Activity;
8import android.content.Context;
9import android.graphics.Rect;
10import android.os.Build;
11import android.os.Bundle;
12import android.os.Handler;
13import android.os.Looper;
14import android.os.ResultReceiver;
15import android.text.method.MetaKeyKeyListener;
16import android.util.DisplayMetrics;
17import android.util.Log;
18import android.view.InputDevice;
19import android.view.KeyCharacterMap;
20import android.view.KeyEvent;
21import android.view.MotionEvent;
22import android.view.WindowInsets;
23import android.view.WindowInsets.Type;
24import android.view.Window;
25import android.view.WindowInsetsAnimation;
26import android.view.WindowInsetsAnimation.Callback;
27import android.view.WindowManager;
28import android.view.View;
29import android.view.ViewTreeObserver;
30import android.view.inputmethod.InputMethodManager;
31import android.window.OnBackInvokedCallback;
32import android.window.OnBackInvokedDispatcher;
37 private static final String TAG =
"QtInputDelegate";
39 static native
void keyDown(
int key,
int unicode,
int modifier,
boolean autoRepeat);
40 static native
void keyUp(
int key,
int unicode,
int modifier,
boolean autoRepeat);
41 static native
void keyboardVisibilityChanged(
boolean visibility);
42 static native
void keyboardGeometryChanged(
int x,
int y,
int width,
int height);
46 static native
boolean dispatchGenericMotionEvent(MotionEvent
event);
47 static native
boolean dispatchKeyEvent(KeyEvent
event);
51 static native
void handleLocationChanged(
int id,
int x,
int y);
54 private QtEditText m_currentEditText =
null;
55 private InputMethodManager m_imm;
59 private static final float KEYBOARD_TO_SCREEN_RATIO = 0.15f;
61 private boolean m_keyboardTransitionInProgress =
false;
62 private boolean m_keyboardIsVisible =
false;
63 private boolean m_isKeyboardHidingAnimationOngoing =
false;
64 private long m_showHideTimeStamp =
System.nanoTime();
65 private int m_portraitKeyboardHeight = 0;
66 private int m_landscapeKeyboardHeight = 0;
67 private int m_probeKeyboardHeightDelayMs = 50;
69 private int m_softInputMode = 0;
71 private static Boolean m_tabletEventSupported =
null;
73 private static int m_oldX, m_oldY;
76 private long m_metaState;
77 private int m_lastChar = 0;
78 private boolean m_backKeyPressedSent =
false;
80 private final OnBackInvokedCallback m_backInvokeCallback;
93 m_keyboardVisibilityListener = listener;
94 m_backInvokeCallback = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
104 void initInputMethodManager(Activity activity)
106 m_imm = (InputMethodManager) activity.getSystemService(
Context.INPUT_METHOD_SERVICE);
108 Log.w(
TAG,
"getSystemService() returned a null InputMethodManager instance");
110 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
111 View rootView = activity.getWindow().getDecorView();
112 ViewTreeObserver observer = rootView.getViewTreeObserver();
113 observer.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
114 private boolean m_lastImeVisibility = false;
117 public void onGlobalLayout() {
118 WindowInsets windowInsets = rootView.getRootWindowInsets();
119 if (windowInsets == null)
122 boolean imeVisible = windowInsets.isVisible(WindowInsets.Type.ime());
123 if (m_lastImeVisibility != imeVisible) {
124 m_lastImeVisibility = imeVisible;
125 setKeyboardVisibility_internal(imeVisible, System.nanoTime());
128 if (!isKeyboardHidden())
129 setKeyboardTransitionInProgress(false);
135 private void setKeyboardTransitionInProgress(
boolean state)
137 if (m_currentEditText ==
null || m_keyboardTransitionInProgress ==
state)
140 m_keyboardTransitionInProgress =
state;
146 final int candidatesStart,
final int candidatesEnd)
151 m_imm.updateSelection(m_currentEditText, selStart, selEnd,
152 candidatesStart, candidatesEnd);
158 private void showKeyboard(Activity activity,
160 final int inputHints,
final int enterKeyType)
162 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
164 View decorView =
window.getDecorView();
165 decorView.setWindowInsetsAnimationCallback(
166 new WindowInsetsAnimation.Callback(
167 WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
169 public WindowInsets onProgress(
170 WindowInsets insets, List<WindowInsetsAnimation> animationList) {
174 public void onEnd(WindowInsetsAnimation animation) {
175 decorView.setWindowInsetsAnimationCallback(
null);
176 if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
177 QtNativeInputConnection.updateCursorPosition();
178 if (m_softInputMode == 0) {
180 inputHints, enterKeyType);
185 window.getInsetsController().show(
Type.ime());
189 m_imm.showSoftInput(m_currentEditText, 0,
new ResultReceiver(
new Handler(Looper.getMainLooper())) {
191 @SuppressWarnings(
"fallthrough")
192 protected void onReceiveResult(int resultCode, Bundle resultData) {
193 switch (resultCode) {
194 case InputMethodManager.RESULT_SHOWN:
195 QtNativeInputConnection.updateCursorPosition();
197 case InputMethodManager.RESULT_UNCHANGED_SHOWN:
198 setKeyboardVisibility(true, System.nanoTime());
199 if (m_softInputMode == 0) {
200 probeForKeyboardHeight(activity,
201 x, y, width, height, inputHints, enterKeyType);
204 case InputMethodManager.RESULT_HIDDEN:
205 case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
206 setKeyboardVisibility(false, System.nanoTime());
217 final int inputHints,
final int enterKeyType)
222 QtNative.runAction(() -> {
223 if (m_imm ==
null || m_currentEditText ==
null)
226 if (updateSoftInputMode(activity,
height))
229 m_currentEditText.setEditTextOptions(enterKeyType, inputHints);
230 m_currentEditText.setLayoutParams(
new QtLayout.LayoutParams(
width,
height,
x,
y));
231 m_currentEditText.requestFocus();
232 m_currentEditText.postDelayed(() -> {
233 showKeyboard(activity,
x,
y,
width,
height, inputHints, enterKeyType);
234 if (m_currentEditText.m_optionsChanged) {
235 m_imm.restartInput(m_currentEditText);
236 m_currentEditText.m_optionsChanged =
false;
243 public int getSelectionHandleWidth()
245 return m_currentEditText ==
null ? 0 : m_currentEditText.getSelectionHandleWidth();
254 int x1,
int y1,
int x2,
int y2,
boolean rtl)
256 QtNative.runAction(() -> {
257 if (m_currentEditText !=
null)
258 m_currentEditText.updateHandles(
mode, editX, editY, editButtons,
x1,
y1,
x2,
y2, rtl);
263 public QtInputConnection.QtInputConnectionListener getInputConnectionListener()
271 if (m_imm ==
null || m_currentEditText ==
null)
273 m_currentEditText.postDelayed(() -> {
274 if (m_imm ==
null || m_currentEditText ==
null)
276 m_imm.restartInput(m_currentEditText);
277 m_currentEditText.m_optionsChanged =
false;
284 if (m_imm ==
null || m_currentEditText ==
null)
287 m_isKeyboardHidingAnimationOngoing =
true;
288 QtNative.runAction(() -> {
289 if (m_imm ==
null || m_currentEditText ==
null)
292 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
293 Activity
activity = QtNative.activity();
294 if (activity ==
null) {
295 Log.w(
TAG,
"hideSoftwareKeyboard: The activity reference is null");
298 activity.getWindow().getInsetsController().hide(
Type.ime());
300 m_imm.hideSoftInputFromWindow(m_currentEditText.getWindowToken(), 0,
301 new ResultReceiver(
new Handler(Looper.getMainLooper())) {
303 protected void onReceiveResult(int resultCode, Bundle resultData) {
304 switch (resultCode) {
305 case InputMethodManager.RESULT_SHOWN:
306 case InputMethodManager.RESULT_UNCHANGED_SHOWN:
307 setKeyboardVisibility(true, System.nanoTime());
309 case InputMethodManager.RESULT_HIDDEN:
310 case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
311 setKeyboardVisibility(false, System.nanoTime());
324 return isKeyboardVisible() && !m_isKeyboardHidingAnimationOngoing;
330 public boolean keyboardTransitionInProgress() {
331 return m_keyboardTransitionInProgress;
335 public boolean isKeyboardHidden() {
336 Activity
activity = QtNative.activity();
337 if (activity ==
null) {
338 Log.w(
TAG,
"isKeyboardHidden: The activity reference is null");
342 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
344 activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(
r);
345 DisplayMetrics
metrics =
new DisplayMetrics();
346 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
347 int screenHeight =
metrics.heightPixels;
348 final int kbHeight = screenHeight -
r.bottom;
349 return kbHeight < screenHeight * KEYBOARD_TO_SCREEN_RATIO;
352 return !m_keyboardIsVisible;
356 public void onSetClosing(
boolean closing) {
358 setKeyboardVisibility(
true,
System.nanoTime());
362 public void onHideKeyboardRunnableDone(
boolean visibility,
long hideTimeStamp) {
363 setKeyboardVisibility(visibility, hideTimeStamp);
367 public void onSendKeyEventDefaultCase() {
372 public void onEditTextChanged(QtEditText editText) {
373 setFocusedView(editText);
377 boolean isKeyboardVisible()
379 return m_keyboardIsVisible;
382 void setSoftInputMode(
int inputMode)
384 m_softInputMode = inputMode;
387 QtEditText getCurrentQtEditText()
389 return m_currentEditText;
392 private void keyboardVisibilityUpdated(
boolean visibility)
394 m_isKeyboardHidingAnimationOngoing =
false;
395 QtInputDelegate.keyboardVisibilityChanged(visibility);
398 void setKeyboardVisibility(
boolean visibility,
long timeStamp)
402 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
403 setKeyboardVisibility_internal(visibility, timeStamp);
406 private void setKeyboardVisibility_internal(
boolean visibility,
long timeStamp)
408 if (m_showHideTimeStamp > timeStamp)
410 m_showHideTimeStamp = timeStamp;
412 if (m_keyboardIsVisible == visibility)
414 m_keyboardIsVisible = visibility;
415 keyboardVisibilityUpdated(m_keyboardIsVisible);
416 setKeyboardTransitionInProgress(visibility);
421 if (m_currentEditText !=
null)
422 m_currentEditText.clearFocus();
426 void setFocusedView(QtEditText currentEditText)
428 setKeyboardTransitionInProgress(
false);
429 m_currentEditText = currentEditText;
432 private boolean updateSoftInputMode(Activity activity,
int height)
434 DisplayMetrics
metrics =
new DisplayMetrics();
435 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
440 final int visibleHeight;
442 visibleHeight = m_portraitKeyboardHeight != 0 ?
443 m_portraitKeyboardHeight : metrics.heightPixels * 3 / 5;
445 visibleHeight = m_landscapeKeyboardHeight != 0 ?
446 m_landscapeKeyboardHeight : metrics.heightPixels / 3;
449 if (m_softInputMode != 0) {
450 activity.getWindow().setSoftInputMode(m_softInputMode);
451 int stateHidden = WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
452 return (m_softInputMode & stateHidden) != 0;
454 int stateUnchanged = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
455 if (
height > visibleHeight) {
456 int adjustResize = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
457 activity.getWindow().setSoftInputMode(stateUnchanged | adjustResize);
459 int adjustPan = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
460 activity.getWindow().setSoftInputMode(stateUnchanged | adjustPan);
466 private void probeForKeyboardHeight(Activity activity,
int x,
int y,
467 int width,
int height,
int inputHints,
int enterKeyType)
469 if (m_currentEditText ==
null) {
470 Log.w(
TAG,
"probeForKeyboardHeight: null QtEditText");
473 m_currentEditText.postDelayed(() -> {
474 if (!m_keyboardIsVisible)
476 DisplayMetrics
metrics =
new DisplayMetrics();
477 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
479 activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(
r);
480 if (
metrics.heightPixels !=
r.bottom) {
482 if (m_landscapeKeyboardHeight !=
r.bottom) {
483 m_landscapeKeyboardHeight =
r.bottom;
485 inputHints, enterKeyType);
488 if (m_portraitKeyboardHeight !=
r.bottom) {
489 m_portraitKeyboardHeight =
r.bottom;
491 inputHints, enterKeyType);
497 if (m_probeKeyboardHeightDelayMs < 1000)
498 m_probeKeyboardHeightDelayMs *= 2;
500 }, m_probeKeyboardHeightDelayMs);
505 m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode,
event);
506 int metaState = MetaKeyKeyListener.getMetaState(m_metaState) |
event.getMetaState();
507 int c =
event.getUnicodeChar(metaState);
509 m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState);
511 if ((
c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
512 c =
c & KeyCharacterMap.COMBINING_ACCENT_MASK;
516 if ((keyCode ==
KeyEvent.KEYCODE_VOLUME_UP
517 || keyCode ==
KeyEvent.KEYCODE_VOLUME_DOWN
518 || keyCode ==
KeyEvent.KEYCODE_MUTE)
519 &&
System.getenv(
"QT_ANDROID_VOLUME_KEYS") ==
null) {
524 if (keyCode ==
KeyEvent.KEYCODE_BACK) {
525 m_backKeyPressedSent = !isKeyboardVisible();
526 if (!m_backKeyPressedSent)
530 QtInputDelegate.keyDown(keyCode,
c,
event.getMetaState(),
event.getRepeatCount() > 0);
537 if ((keyCode ==
KeyEvent.KEYCODE_VOLUME_UP
538 || keyCode ==
KeyEvent.KEYCODE_VOLUME_DOWN
539 || keyCode ==
KeyEvent.KEYCODE_MUTE)
540 &&
System.getenv(
"QT_ANDROID_VOLUME_KEYS") ==
null) {
544 if (keyCode ==
KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
546 setKeyboardVisibility(
false,
System.nanoTime());
550 m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode,
event);
551 boolean autoRepeat =
event.getRepeatCount() > 0;
552 QtInputDelegate.keyUp(keyCode,
event.getUnicodeChar(),
event.getMetaState(), autoRepeat);
560 &&
event.getCharacters() !=
null
561 &&
event.getCharacters().length() == 1
562 &&
event.getKeyCode() == 0) {
563 keyDown(0,
event.getCharacters().charAt(0),
event.getMetaState(),
564 event.getRepeatCount() > 0);
565 keyUp(0,
event.getCharacters().charAt(0),
event.getMetaState(),
566 event.getRepeatCount() > 0);
572 boolean handleDispatchGenericMotionEvent(MotionEvent
event)
577 void registerBackGestureCallback(Activity activity)
579 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
582 activity.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
583 OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER,
584 m_backInvokeCallback);
585 }
catch (IllegalStateException ise) {
586 Log.e(
TAG,
"Failed to register OnBackInvokedCallback: " + ise.getMessage());
590 void unregisterBackGestureCallback(Activity activity)
592 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
595 activity.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(
596 m_backInvokeCallback);
597 }
catch (IllegalStateException ise) {
598 Log.e(
TAG,
"Failed to unregister OnBackInvokedCallback: " + ise.getMessage());
607 static native
boolean isTabletEventSupported();
608 static native
void tabletEvent(
int winId,
int deviceId,
long time,
int action,
614 static native
void mouseDown(
int winId,
int x,
int y,
int mouseButtonState);
615 static native
void mouseUp(
int winId,
int x,
int y,
int mouseButtonState);
616 static native
void mouseMove(
int winId,
int x,
int y,
int mouseButtonState);
617 static native
void mouseWheel(
int winId,
int x,
int y,
float hDelta,
float vDelta);
618 static native
void touchBegin(
int winId);
619 static native
void touchAdd(
int winId,
int pointerId,
int action,
boolean primary,
620 int x,
int y,
float major,
float minor,
float rotation,
622 static native
void touchEnd(
int winId,
int action);
623 static native
void touchCancel(
int winId);
624 static native
void longPress(
int winId,
int x,
int y);
627 static private int getAction(
int index, MotionEvent
event)
629 int action =
event.getActionMasked();
630 if (action == MotionEvent.ACTION_MOVE) {
631 int hsz =
event.getHistorySize();
633 float x =
event.getX(
index);
634 float y =
event.getY(
index);
635 for (
int h = 0;
h < hsz; ++
h) {
644 if (action == MotionEvent.ACTION_DOWN
645 || action == MotionEvent.ACTION_POINTER_DOWN &&
index ==
event.getActionIndex()) {
647 }
else if (action == MotionEvent.ACTION_UP
648 || action == MotionEvent.ACTION_POINTER_UP &&
index ==
event.getActionIndex()) {
654 static void sendTouchEvent(MotionEvent
event,
int id)
658 if (m_tabletEventSupported ==
null)
659 m_tabletEventSupported = isTabletEventSupported();
661 switch (
event.getToolType(0)) {
662 case MotionEvent.TOOL_TYPE_STYLUS:
665 case MotionEvent.TOOL_TYPE_ERASER:
670 if (
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
671 sendMouseEvent(
event,
id);
672 }
else if (m_tabletEventSupported &&
pointerType != 0) {
673 tabletEvent(
id,
event.getDeviceId(),
event.getEventTime(),
event.getActionMasked(),
678 for (
int i = 0;
i <
event.getPointerCount(); ++
i) {
680 event.getPointerId(i),
685 event.getTouchMajor(i),
686 event.getTouchMinor(i),
687 event.getOrientation(i),
688 event.getPressure(i));
691 switch (
event.getAction()) {
692 case MotionEvent.ACTION_DOWN:
696 case MotionEvent.ACTION_UP:
700 case MotionEvent.ACTION_CANCEL:
710 static void sendTrackballEvent(MotionEvent
event,
int id)
712 sendMouseEvent(
event,
id);
715 static boolean sendGenericMotionEvent(MotionEvent
event,
int id)
717 int scrollOrHoverMove = MotionEvent.ACTION_SCROLL | MotionEvent.ACTION_HOVER_MOVE;
718 int pointerDeviceModifier = (
event.getSource() & InputDevice.SOURCE_CLASS_POINTER);
719 boolean isPointerDevice = pointerDeviceModifier == InputDevice.SOURCE_CLASS_POINTER;
721 if ((
event.getAction() & scrollOrHoverMove) == 0 || !isPointerDevice )
724 return sendMouseEvent(
event,
id);
727 static boolean sendMouseEvent(MotionEvent
event,
int id)
729 switch (
event.getActionMasked()) {
730 case MotionEvent.ACTION_UP:
731 mouseUp(
id, (
int)
event.getX(), (
int)
event.getY(),
event.getButtonState());
734 case MotionEvent.ACTION_DOWN:
735 mouseDown(
id, (
int)
event.getX(), (
int)
event.getY(),
event.getButtonState());
736 m_oldX = (int)
event.getX();
737 m_oldY = (int)
event.getY();
739 case MotionEvent.ACTION_HOVER_MOVE:
740 case MotionEvent.ACTION_MOVE:
741 if (
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
744 int dx = (int) (
event.getX() - m_oldX);
745 int dy = (int) (
event.getY() - m_oldY);
746 if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
748 m_oldX = (int)
event.getX();
749 m_oldY = (int)
event.getY();
753 case MotionEvent.ACTION_SCROLL:
754 mouseWheel(
id, (
int)
event.getX(), (
int)
event.getY(),
755 event.getAxisValue(MotionEvent.AXIS_HSCROLL),
756 event.getAxisValue(MotionEvent.AXIS_VSCROLL));
void mouseMove(QWindow *window, QPoint pos=QPoint(), int delay=-1)
Q_CORE_EXPORT QtJniTypes::Activity activity()
static jboolean dispatchGenericMotionEvent(JNIEnv *, jclass, QtJniTypes::MotionEvent event)
static jboolean dispatchKeyEvent(JNIEnv *, jclass, QtJniTypes::KeyEvent event)
GLint GLint GLint GLint GLint x
GLuint GLfloat GLfloat GLfloat GLfloat y1
GLuint GLfloat GLfloat GLfloat x1
GLsizei GLenum const void GLuint GLsizei GLfloat * metrics
GLfloat GLfloat GLfloat GLfloat h
GLfixed GLfixed GLfixed y2
static QPointingDevice::PointerType pointerType(unsigned currentCursor)