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;
79 private boolean m_backKeyDown =
false;
81 private final OnBackInvokedCallback m_backInvokeCallback;
94 m_keyboardVisibilityListener = listener;
95 m_backInvokeCallback = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
105 void initInputMethodManager(Activity activity)
107 m_imm = (InputMethodManager) activity.getSystemService(
Context.INPUT_METHOD_SERVICE);
109 Log.w(
TAG,
"getSystemService() returned a null InputMethodManager instance");
111 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
112 View rootView = activity.getWindow().getDecorView();
113 ViewTreeObserver observer = rootView.getViewTreeObserver();
114 observer.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
115 private boolean m_lastImeVisibility = false;
118 public void onGlobalLayout() {
119 WindowInsets windowInsets = rootView.getRootWindowInsets();
120 if (windowInsets == null)
123 boolean imeVisible = windowInsets.isVisible(WindowInsets.Type.ime());
124 if (m_lastImeVisibility != imeVisible) {
125 m_lastImeVisibility = imeVisible;
126 setKeyboardVisibility_internal(imeVisible, System.nanoTime());
129 if (!isKeyboardHidden())
130 setKeyboardTransitionInProgress(false);
136 private void setKeyboardTransitionInProgress(
boolean state)
138 if (m_currentEditText ==
null || m_keyboardTransitionInProgress ==
state)
141 m_keyboardTransitionInProgress =
state;
147 final int candidatesStart,
final int candidatesEnd)
152 m_imm.updateSelection(m_currentEditText, selStart, selEnd,
153 candidatesStart, candidatesEnd);
159 private void showKeyboard(Activity activity,
161 final int inputHints,
final int enterKeyType)
163 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
165 View decorView =
window.getDecorView();
166 decorView.setWindowInsetsAnimationCallback(
167 new WindowInsetsAnimation.Callback(
168 WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
170 public WindowInsets onProgress(
171 WindowInsets insets, List<WindowInsetsAnimation> animationList) {
175 public void onEnd(WindowInsetsAnimation animation) {
176 decorView.setWindowInsetsAnimationCallback(
null);
177 if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
178 QtInputConnection.QtNativeInputConnection.updateCursorPosition();
179 if (m_softInputMode == 0) {
181 inputHints, enterKeyType);
186 window.getInsetsController().show(
Type.ime());
190 m_imm.showSoftInput(m_currentEditText, 0,
new ResultReceiver(
new Handler(Looper.getMainLooper())) {
192 @SuppressWarnings(
"fallthrough")
193 protected void onReceiveResult(int resultCode, Bundle resultData) {
194 switch (resultCode) {
195 case InputMethodManager.RESULT_SHOWN:
196 QtInputConnection.QtNativeInputConnection.updateCursorPosition();
198 case InputMethodManager.RESULT_UNCHANGED_SHOWN:
199 setKeyboardVisibility(true, System.nanoTime());
200 if (m_softInputMode == 0) {
201 probeForKeyboardHeight(activity,
202 x, y, width, height, inputHints, enterKeyType);
205 case InputMethodManager.RESULT_HIDDEN:
206 case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
207 setKeyboardVisibility(false, System.nanoTime());
218 final int inputHints,
final int enterKeyType)
223 QtNative.runAction(() -> {
224 if (m_imm ==
null || m_currentEditText ==
null)
227 if (updateSoftInputMode(activity,
height))
230 m_currentEditText.setEditTextOptions(enterKeyType, inputHints);
231 m_currentEditText.setLayoutParams(
new QtLayout.LayoutParams(
width,
height,
x,
y));
232 m_currentEditText.requestFocus();
233 m_currentEditText.postDelayed(() -> {
234 showKeyboard(activity,
x,
y,
width,
height, inputHints, enterKeyType);
235 if (m_currentEditText.m_optionsChanged) {
236 m_imm.restartInput(m_currentEditText);
237 m_currentEditText.m_optionsChanged =
false;
244 public int getSelectionHandleWidth()
246 return m_currentEditText ==
null ? 0 : m_currentEditText.getSelectionHandleWidth();
255 int x1,
int y1,
int x2,
int y2,
boolean rtl)
257 QtNative.runAction(() -> {
258 if (m_currentEditText !=
null)
259 m_currentEditText.updateHandles(
mode, editX, editY, editButtons,
x1,
y1,
x2,
y2, rtl);
264 public QtInputConnection.QtInputConnectionListener getInputConnectionListener()
272 if (m_imm ==
null || m_currentEditText ==
null)
274 m_currentEditText.postDelayed(() -> {
275 if (m_imm ==
null || m_currentEditText ==
null)
277 m_imm.restartInput(m_currentEditText);
278 m_currentEditText.m_optionsChanged =
false;
285 if (m_imm ==
null || m_currentEditText ==
null)
288 m_isKeyboardHidingAnimationOngoing =
true;
289 QtNative.runAction(() -> {
290 if (m_imm ==
null || m_currentEditText ==
null)
293 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
294 Activity
activity = QtNative.activity();
295 if (activity ==
null) {
296 Log.w(
TAG,
"hideSoftwareKeyboard: The activity reference is null");
299 activity.getWindow().getInsetsController().hide(
Type.ime());
301 m_imm.hideSoftInputFromWindow(m_currentEditText.getWindowToken(), 0,
302 new ResultReceiver(
new Handler(Looper.getMainLooper())) {
304 protected void onReceiveResult(int resultCode, Bundle resultData) {
305 switch (resultCode) {
306 case InputMethodManager.RESULT_SHOWN:
307 case InputMethodManager.RESULT_UNCHANGED_SHOWN:
308 setKeyboardVisibility(true, System.nanoTime());
310 case InputMethodManager.RESULT_HIDDEN:
311 case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
312 setKeyboardVisibility(false, System.nanoTime());
325 return isKeyboardVisible() && !m_isKeyboardHidingAnimationOngoing;
331 public boolean keyboardTransitionInProgress() {
332 return m_keyboardTransitionInProgress;
336 public boolean isKeyboardHidden() {
337 Activity
activity = QtNative.activity();
338 if (activity ==
null) {
339 Log.w(
TAG,
"isKeyboardHidden: The activity reference is null");
343 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
345 activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(
r);
346 DisplayMetrics
metrics =
new DisplayMetrics();
347 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
348 int screenHeight =
metrics.heightPixels;
349 final int kbHeight = screenHeight -
r.bottom;
350 return kbHeight < screenHeight * KEYBOARD_TO_SCREEN_RATIO;
353 return !m_keyboardIsVisible;
357 public void onSetClosing(
boolean closing) {
359 setKeyboardVisibility(
true,
System.nanoTime());
363 public void onHideKeyboardRunnableDone(
boolean visibility,
long hideTimeStamp) {
364 setKeyboardVisibility(visibility, hideTimeStamp);
368 public void onSendKeyEventDefaultCase() {
373 public void onEditTextChanged(QtEditText editText) {
374 setFocusedView(editText);
378 boolean isKeyboardVisible()
380 return m_keyboardIsVisible;
383 void setSoftInputMode(
int inputMode)
385 m_softInputMode = inputMode;
388 QtEditText getCurrentQtEditText()
390 return m_currentEditText;
393 private void keyboardVisibilityUpdated(
boolean visibility)
395 m_isKeyboardHidingAnimationOngoing =
false;
396 QtInputDelegate.keyboardVisibilityChanged(visibility);
399 void setKeyboardVisibility(
boolean visibility,
long timeStamp)
403 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
404 setKeyboardVisibility_internal(visibility, timeStamp);
407 private void setKeyboardVisibility_internal(
boolean visibility,
long timeStamp)
409 if (m_showHideTimeStamp > timeStamp)
411 m_showHideTimeStamp = timeStamp;
413 if (m_keyboardIsVisible == visibility)
415 m_keyboardIsVisible = visibility;
416 keyboardVisibilityUpdated(m_keyboardIsVisible);
417 setKeyboardTransitionInProgress(visibility);
422 if (m_currentEditText !=
null)
423 m_currentEditText.clearFocus();
427 void setFocusedView(QtEditText currentEditText)
429 setKeyboardTransitionInProgress(
false);
430 m_currentEditText = currentEditText;
433 private boolean updateSoftInputMode(Activity activity,
int height)
435 DisplayMetrics
metrics =
new DisplayMetrics();
436 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
441 final int visibleHeight;
443 visibleHeight = m_portraitKeyboardHeight != 0 ?
444 m_portraitKeyboardHeight : metrics.heightPixels * 3 / 5;
446 visibleHeight = m_landscapeKeyboardHeight != 0 ?
447 m_landscapeKeyboardHeight : metrics.heightPixels / 3;
450 if (m_softInputMode != 0) {
451 activity.getWindow().setSoftInputMode(m_softInputMode);
452 int stateHidden = WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
453 return (m_softInputMode & stateHidden) != 0;
455 int stateUnchanged = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
456 if (
height > visibleHeight) {
457 int adjustResize = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
458 activity.getWindow().setSoftInputMode(stateUnchanged | adjustResize);
460 int adjustPan = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
461 activity.getWindow().setSoftInputMode(stateUnchanged | adjustPan);
467 private void probeForKeyboardHeight(Activity activity,
int x,
int y,
468 int width,
int height,
int inputHints,
int enterKeyType)
470 if (m_currentEditText ==
null) {
471 Log.w(
TAG,
"probeForKeyboardHeight: null QtEditText");
474 m_currentEditText.postDelayed(() -> {
475 if (!m_keyboardIsVisible)
477 DisplayMetrics
metrics =
new DisplayMetrics();
478 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
480 activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(
r);
481 if (
metrics.heightPixels !=
r.bottom) {
483 if (m_landscapeKeyboardHeight !=
r.bottom) {
484 m_landscapeKeyboardHeight =
r.bottom;
486 inputHints, enterKeyType);
489 if (m_portraitKeyboardHeight !=
r.bottom) {
490 m_portraitKeyboardHeight =
r.bottom;
492 inputHints, enterKeyType);
498 if (m_probeKeyboardHeightDelayMs < 1000)
499 m_probeKeyboardHeightDelayMs *= 2;
501 }, m_probeKeyboardHeightDelayMs);
506 m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode,
event);
507 int metaState = MetaKeyKeyListener.getMetaState(m_metaState) |
event.getMetaState();
508 int c =
event.getUnicodeChar(metaState);
510 m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState);
512 if ((
c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
513 c =
c & KeyCharacterMap.COMBINING_ACCENT_MASK;
517 if ((keyCode ==
KeyEvent.KEYCODE_VOLUME_UP
518 || keyCode ==
KeyEvent.KEYCODE_VOLUME_DOWN
519 || keyCode ==
KeyEvent.KEYCODE_MUTE)
520 &&
System.getenv(
"QT_ANDROID_VOLUME_KEYS") ==
null) {
525 if (keyCode ==
KeyEvent.KEYCODE_BACK) {
527 if (m_backKeyDown &&
event.getRepeatCount() == 0)
529 m_backKeyDown =
true;
530 m_backKeyPressedSent = !isKeyboardVisible();
531 if (!m_backKeyPressedSent)
535 QtInputDelegate.keyDown(keyCode,
c,
event.getMetaState(),
event.getRepeatCount() > 0);
542 if ((keyCode ==
KeyEvent.KEYCODE_VOLUME_UP
543 || keyCode ==
KeyEvent.KEYCODE_VOLUME_DOWN
544 || keyCode ==
KeyEvent.KEYCODE_MUTE)
545 &&
System.getenv(
"QT_ANDROID_VOLUME_KEYS") ==
null) {
549 if (keyCode ==
KeyEvent.KEYCODE_BACK) {
552 m_backKeyDown =
false;
553 if (!m_backKeyPressedSent) {
555 setKeyboardVisibility(
false,
System.nanoTime());
560 m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode,
event);
561 boolean autoRepeat =
event.getRepeatCount() > 0;
562 QtInputDelegate.keyUp(keyCode,
event.getUnicodeChar(),
event.getMetaState(), autoRepeat);
570 &&
event.getCharacters() !=
null
571 &&
event.getCharacters().length() == 1
572 &&
event.getKeyCode() == 0) {
573 keyDown(0,
event.getCharacters().charAt(0),
event.getMetaState(),
574 event.getRepeatCount() > 0);
575 keyUp(0,
event.getCharacters().charAt(0),
event.getMetaState(),
576 event.getRepeatCount() > 0);
579 return dispatchKeyEvent(
event);
582 boolean handleDispatchGenericMotionEvent(MotionEvent
event)
584 return dispatchGenericMotionEvent(
event);
587 void registerBackGestureCallback(Activity activity)
589 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
592 activity.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
593 OnBackInvokedDispatcher.PRIORITY_DEFAULT,
594 m_backInvokeCallback);
595 }
catch (IllegalStateException ise) {
596 Log.e(
TAG,
"Failed to register OnBackInvokedCallback: " + ise.getMessage());
600 void unregisterBackGestureCallback(Activity activity)
602 m_backKeyDown =
false;
603 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
606 activity.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(
607 m_backInvokeCallback);
608 }
catch (IllegalStateException ise) {
609 Log.e(
TAG,
"Failed to unregister OnBackInvokedCallback: " + ise.getMessage());
618 static native
boolean isTabletEventSupported();
619 static native
void tabletEvent(
int winId,
int deviceId,
long time,
int action,
620 int pointerType,
int buttonState,
float x,
float y,
621 float pressure,
int metaState);
625 static native
void mouseDown(
int winId,
int x,
int y,
int mouseButtonState,
int metaState);
626 static native
void mouseUp(
int winId,
int x,
int y,
int mouseButtonState,
int metaState);
627 static native
void mouseMove(
int winId,
int x,
int y,
int mouseButtonState,
int metaState);
628 static native
void mouseWheel(
int winId,
int x,
int y,
float hDelta,
float vDelta,
int metaState);
629 static native
void touchBegin(
int winId);
630 static native
void touchAdd(
int winId,
int pointerId,
int action,
boolean primary,
631 int x,
int y,
float major,
float minor,
float rotation,
633 static native
void touchEnd(
int winId,
int action,
int metaState);
634 static native
void touchCancel(
int winId,
int metaState);
635 static native
void longPress(
int winId,
int x,
int y,
int metaState);
638 static private int getAction(
int index, MotionEvent
event)
640 int action =
event.getActionMasked();
641 if (action == MotionEvent.ACTION_MOVE) {
642 int hsz =
event.getHistorySize();
644 float x =
event.getX(
index);
645 float y =
event.getY(
index);
646 for (
int h = 0;
h < hsz; ++
h) {
655 if (action == MotionEvent.ACTION_DOWN
656 || action == MotionEvent.ACTION_POINTER_DOWN &&
index ==
event.getActionIndex()) {
658 }
else if (action == MotionEvent.ACTION_UP
659 || action == MotionEvent.ACTION_POINTER_UP &&
index ==
event.getActionIndex()) {
665 static void sendTouchEvent(MotionEvent
event,
int id)
669 if (m_tabletEventSupported ==
null)
670 m_tabletEventSupported = isTabletEventSupported();
672 switch (
event.getToolType(0)) {
673 case MotionEvent.TOOL_TYPE_STYLUS:
676 case MotionEvent.TOOL_TYPE_ERASER:
681 if (
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
682 sendMouseEvent(
event,
id);
683 }
else if (m_tabletEventSupported && pointerType != 0) {
684 tabletEvent(
id,
event.getDeviceId(),
event.getEventTime(),
event.getActionMasked(),
685 pointerType,
event.getButtonState(),
687 event.getMetaState());
690 for (
int i = 0;
i <
event.getPointerCount(); ++
i) {
692 event.getPointerId(i),
697 event.getTouchMajor(i),
698 event.getTouchMinor(i),
699 event.getOrientation(i),
700 event.getPressure(i));
703 switch (
event.getAction()) {
704 case MotionEvent.ACTION_DOWN:
705 touchEnd(
id, 0,
event.getMetaState());
708 case MotionEvent.ACTION_UP:
709 touchEnd(
id, 2,
event.getMetaState());
712 case MotionEvent.ACTION_CANCEL:
713 touchCancel(
id,
event.getMetaState());
717 touchEnd(
id, 1,
event.getMetaState());
722 static void sendTrackballEvent(MotionEvent
event,
int id)
724 sendMouseEvent(
event,
id);
727 static boolean sendGenericMotionEvent(MotionEvent
event,
int id)
729 int scrollOrHoverMove = MotionEvent.ACTION_SCROLL | MotionEvent.ACTION_HOVER_MOVE;
730 int pointerDeviceModifier = (
event.getSource() & InputDevice.SOURCE_CLASS_POINTER);
731 boolean isPointerDevice = pointerDeviceModifier == InputDevice.SOURCE_CLASS_POINTER;
733 if ((
event.getAction() & scrollOrHoverMove) == 0 || !isPointerDevice )
736 return sendMouseEvent(
event,
id);
739 static boolean sendMouseEvent(MotionEvent
event,
int id)
741 switch (
event.getActionMasked()) {
742 case MotionEvent.ACTION_UP:
743 mouseUp(
id, (
int)
event.getX(), (
int)
event.getY(),
event.getButtonState(),
744 event.getMetaState());
747 case MotionEvent.ACTION_DOWN:
748 mouseDown(
id, (
int)
event.getX(), (
int)
event.getY(),
event.getButtonState(),
749 event.getMetaState());
750 m_oldX = (int)
event.getX();
751 m_oldY = (int)
event.getY();
753 case MotionEvent.ACTION_HOVER_MOVE:
754 case MotionEvent.ACTION_MOVE:
755 if (
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
757 event.getMetaState());
759 int dx = (int) (
event.getX() - m_oldX);
760 int dy = (int) (
event.getY() - m_oldY);
761 if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
763 event.getMetaState());
764 m_oldX = (int)
event.getX();
765 m_oldY = (int)
event.getY();
769 case MotionEvent.ACTION_SCROLL:
770 mouseWheel(
id, (
int)
event.getX(), (
int)
event.getY(),
771 event.getAxisValue(MotionEvent.AXIS_HSCROLL),
772 event.getAxisValue(MotionEvent.AXIS_VSCROLL),
773 event.getMetaState());
void mouseMove(QWindow *window, QPoint pos=QPoint(), int delay=-1)
Q_CORE_EXPORT QtJniTypes::Activity activity()
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