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;
35 private static final String TAG =
"QtInputDelegate";
37 static native
void keyDown(
int key,
int unicode,
int modifier,
boolean autoRepeat);
38 static native
void keyUp(
int key,
int unicode,
int modifier,
boolean autoRepeat);
39 static native
void keyboardVisibilityChanged(
boolean visibility);
40 static native
void keyboardGeometryChanged(
int x,
int y,
int width,
int height);
44 static native
boolean dispatchGenericMotionEvent(MotionEvent
event);
45 static native
boolean dispatchKeyEvent(KeyEvent
event);
49 static native
void handleLocationChanged(
int id,
int x,
int y);
52 private QtEditText m_currentEditText =
null;
53 private InputMethodManager m_imm;
57 private static final float KEYBOARD_TO_SCREEN_RATIO = 0.15f;
59 private boolean m_keyboardTransitionInProgress =
false;
60 private boolean m_keyboardIsVisible =
false;
61 private boolean m_isKeyboardHidingAnimationOngoing =
false;
62 private long m_showHideTimeStamp =
System.nanoTime();
63 private int m_portraitKeyboardHeight = 0;
64 private int m_landscapeKeyboardHeight = 0;
65 private int m_probeKeyboardHeightDelayMs = 50;
67 private int m_softInputMode = 0;
69 private static Boolean m_tabletEventSupported =
null;
71 private static int m_oldX, m_oldY;
74 private long m_metaState;
75 private int m_lastChar = 0;
76 private boolean m_backKeyPressedSent =
false;
89 m_keyboardVisibilityListener = listener;
92 void initInputMethodManager(Activity activity)
94 m_imm = (InputMethodManager) activity.getSystemService(
Context.INPUT_METHOD_SERVICE);
96 Log.w(
TAG,
"getSystemService() returned a null InputMethodManager instance");
98 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
99 View rootView = activity.getWindow().getDecorView();
100 ViewTreeObserver observer = rootView.getViewTreeObserver();
101 observer.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
102 private boolean m_lastImeVisibility = false;
105 public void onGlobalLayout() {
106 WindowInsets windowInsets = rootView.getRootWindowInsets();
107 if (windowInsets == null)
110 boolean imeVisible = windowInsets.isVisible(WindowInsets.Type.ime());
111 if (m_lastImeVisibility != imeVisible) {
112 m_lastImeVisibility = imeVisible;
113 setKeyboardVisibility_internal(imeVisible, System.nanoTime());
116 if (!isKeyboardHidden())
117 setKeyboardTransitionInProgress(false);
123 private void setKeyboardTransitionInProgress(
boolean state)
125 if (m_currentEditText ==
null || m_keyboardTransitionInProgress ==
state)
128 m_keyboardTransitionInProgress =
state;
133 public void updateSelection(
final int selStart,
final int selEnd,
134 final int candidatesStart,
final int candidatesEnd)
137 QtNative.runAction(() -> {
139 m_imm.updateSelection(m_currentEditText, selStart, selEnd,
140 candidatesStart, candidatesEnd);
146 private void showKeyboard(Activity activity,
148 final int inputHints,
final int enterKeyType)
150 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
152 View decorView =
window.getDecorView();
153 decorView.setWindowInsetsAnimationCallback(
154 new WindowInsetsAnimation.Callback(
155 WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
157 public WindowInsets onProgress(
158 WindowInsets insets, List<WindowInsetsAnimation> animationList) {
162 public void onEnd(WindowInsetsAnimation animation) {
163 decorView.setWindowInsetsAnimationCallback(
null);
164 if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
165 QtNativeInputConnection.updateCursorPosition();
166 if (m_softInputMode == 0) {
168 inputHints, enterKeyType);
173 window.getInsetsController().show(
Type.ime());
177 m_imm.showSoftInput(m_currentEditText, 0,
new ResultReceiver(
new Handler(Looper.getMainLooper())) {
179 @SuppressWarnings(
"fallthrough")
180 protected void onReceiveResult(int resultCode, Bundle resultData) {
181 switch (resultCode) {
182 case InputMethodManager.RESULT_SHOWN:
183 QtNativeInputConnection.updateCursorPosition();
185 case InputMethodManager.RESULT_UNCHANGED_SHOWN:
186 setKeyboardVisibility(true, System.nanoTime());
187 if (m_softInputMode == 0) {
188 probeForKeyboardHeight(activity,
189 x, y, width, height, inputHints, enterKeyType);
192 case InputMethodManager.RESULT_HIDDEN:
193 case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
194 setKeyboardVisibility(false, System.nanoTime());
205 final int inputHints,
final int enterKeyType)
210 QtNative.runAction(() -> {
211 if (m_imm ==
null || m_currentEditText ==
null)
214 if (updateSoftInputMode(activity,
height))
217 m_currentEditText.setEditTextOptions(enterKeyType, inputHints);
218 m_currentEditText.setLayoutParams(
new QtLayout.LayoutParams(
width,
height,
x,
y));
219 m_currentEditText.requestFocus();
220 m_currentEditText.postDelayed(() -> {
221 showKeyboard(activity,
x,
y,
width,
height, inputHints, enterKeyType);
222 if (m_currentEditText.m_optionsChanged) {
223 m_imm.restartInput(m_currentEditText);
224 m_currentEditText.m_optionsChanged =
false;
231 public int getSelectionHandleWidth()
233 return m_currentEditText ==
null ? 0 : m_currentEditText.getSelectionHandleWidth();
242 int x1,
int y1,
int x2,
int y2,
boolean rtl)
244 QtNative.runAction(() -> {
245 if (m_currentEditText !=
null)
246 m_currentEditText.updateHandles(
mode, editX, editY, editButtons,
x1,
y1,
x2,
y2, rtl);
251 public QtInputConnection.QtInputConnectionListener getInputConnectionListener()
259 if (m_imm ==
null || m_currentEditText ==
null)
261 m_currentEditText.postDelayed(() -> {
262 if (m_imm ==
null || m_currentEditText ==
null)
264 m_imm.restartInput(m_currentEditText);
265 m_currentEditText.m_optionsChanged =
false;
272 if (m_imm ==
null || m_currentEditText ==
null)
275 m_isKeyboardHidingAnimationOngoing =
true;
276 QtNative.runAction(() -> {
277 if (m_imm ==
null || m_currentEditText ==
null)
280 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
281 Activity
activity = QtNative.activity();
282 if (activity ==
null) {
283 Log.w(
TAG,
"hideSoftwareKeyboard: The activity reference is null");
286 activity.getWindow().getInsetsController().hide(
Type.ime());
288 m_imm.hideSoftInputFromWindow(m_currentEditText.getWindowToken(), 0,
289 new ResultReceiver(
new Handler(Looper.getMainLooper())) {
291 protected void onReceiveResult(int resultCode, Bundle resultData) {
292 switch (resultCode) {
293 case InputMethodManager.RESULT_SHOWN:
294 case InputMethodManager.RESULT_UNCHANGED_SHOWN:
295 setKeyboardVisibility(true, System.nanoTime());
297 case InputMethodManager.RESULT_HIDDEN:
298 case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
299 setKeyboardVisibility(false, System.nanoTime());
312 return isKeyboardVisible() && !m_isKeyboardHidingAnimationOngoing;
318 public boolean keyboardTransitionInProgress() {
319 return m_keyboardTransitionInProgress;
323 public boolean isKeyboardHidden() {
324 Activity
activity = QtNative.activity();
325 if (activity ==
null) {
326 Log.w(
TAG,
"isKeyboardHidden: The activity reference is null");
330 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
332 activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(
r);
333 DisplayMetrics
metrics =
new DisplayMetrics();
334 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
335 int screenHeight =
metrics.heightPixels;
336 final int kbHeight = screenHeight -
r.bottom;
337 return kbHeight < screenHeight * KEYBOARD_TO_SCREEN_RATIO;
340 return !m_keyboardIsVisible;
344 public void onSetClosing(
boolean closing) {
346 setKeyboardVisibility(
true,
System.nanoTime());
350 public void onHideKeyboardRunnableDone(
boolean visibility,
long hideTimeStamp) {
351 setKeyboardVisibility(visibility, hideTimeStamp);
355 public void onSendKeyEventDefaultCase() {
360 public void onEditTextChanged(QtEditText editText) {
361 setFocusedView(editText);
365 boolean isKeyboardVisible()
367 return m_keyboardIsVisible;
370 void setSoftInputMode(
int inputMode)
372 m_softInputMode = inputMode;
375 QtEditText getCurrentQtEditText()
377 return m_currentEditText;
380 private void keyboardVisibilityUpdated(
boolean visibility)
382 m_isKeyboardHidingAnimationOngoing =
false;
383 QtInputDelegate.keyboardVisibilityChanged(visibility);
386 void setKeyboardVisibility(
boolean visibility,
long timeStamp)
390 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
391 setKeyboardVisibility_internal(visibility, timeStamp);
394 private void setKeyboardVisibility_internal(
boolean visibility,
long timeStamp)
396 if (m_showHideTimeStamp > timeStamp)
398 m_showHideTimeStamp = timeStamp;
400 if (m_keyboardIsVisible == visibility)
402 m_keyboardIsVisible = visibility;
403 keyboardVisibilityUpdated(m_keyboardIsVisible);
404 setKeyboardTransitionInProgress(visibility);
409 if (m_currentEditText !=
null)
410 m_currentEditText.clearFocus();
414 void setFocusedView(QtEditText currentEditText)
416 setKeyboardTransitionInProgress(
false);
417 m_currentEditText = currentEditText;
420 private boolean updateSoftInputMode(Activity activity,
int height)
422 DisplayMetrics
metrics =
new DisplayMetrics();
423 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
428 final int visibleHeight;
430 visibleHeight = m_portraitKeyboardHeight != 0 ?
431 m_portraitKeyboardHeight : metrics.heightPixels * 3 / 5;
433 visibleHeight = m_landscapeKeyboardHeight != 0 ?
434 m_landscapeKeyboardHeight : metrics.heightPixels / 3;
437 if (m_softInputMode != 0) {
438 activity.getWindow().setSoftInputMode(m_softInputMode);
439 int stateHidden = WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
440 return (m_softInputMode & stateHidden) != 0;
442 int stateUnchanged = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
443 if (
height > visibleHeight) {
444 int adjustResize = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
445 activity.getWindow().setSoftInputMode(stateUnchanged | adjustResize);
447 int adjustPan = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
448 activity.getWindow().setSoftInputMode(stateUnchanged | adjustPan);
454 private void probeForKeyboardHeight(Activity activity,
int x,
int y,
455 int width,
int height,
int inputHints,
int enterKeyType)
457 if (m_currentEditText ==
null) {
458 Log.w(
TAG,
"probeForKeyboardHeight: null QtEditText");
461 m_currentEditText.postDelayed(() -> {
462 if (!m_keyboardIsVisible)
464 DisplayMetrics
metrics =
new DisplayMetrics();
465 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
467 activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(
r);
468 if (
metrics.heightPixels !=
r.bottom) {
470 if (m_landscapeKeyboardHeight !=
r.bottom) {
471 m_landscapeKeyboardHeight =
r.bottom;
473 inputHints, enterKeyType);
476 if (m_portraitKeyboardHeight !=
r.bottom) {
477 m_portraitKeyboardHeight =
r.bottom;
479 inputHints, enterKeyType);
485 if (m_probeKeyboardHeightDelayMs < 1000)
486 m_probeKeyboardHeightDelayMs *= 2;
488 }, m_probeKeyboardHeightDelayMs);
493 m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode,
event);
494 int metaState = MetaKeyKeyListener.getMetaState(m_metaState) |
event.getMetaState();
495 int c =
event.getUnicodeChar(metaState);
497 m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState);
499 if ((
c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
500 c =
c & KeyCharacterMap.COMBINING_ACCENT_MASK;
504 if ((keyCode ==
KeyEvent.KEYCODE_VOLUME_UP
505 || keyCode ==
KeyEvent.KEYCODE_VOLUME_DOWN
506 || keyCode ==
KeyEvent.KEYCODE_MUTE)
507 &&
System.getenv(
"QT_ANDROID_VOLUME_KEYS") ==
null) {
512 if (keyCode ==
KeyEvent.KEYCODE_BACK) {
513 m_backKeyPressedSent = !isKeyboardVisible();
514 if (!m_backKeyPressedSent)
518 QtInputDelegate.keyDown(keyCode,
c,
event.getMetaState(),
event.getRepeatCount() > 0);
525 if ((keyCode ==
KeyEvent.KEYCODE_VOLUME_UP
526 || keyCode ==
KeyEvent.KEYCODE_VOLUME_DOWN
527 || keyCode ==
KeyEvent.KEYCODE_MUTE)
528 &&
System.getenv(
"QT_ANDROID_VOLUME_KEYS") ==
null) {
532 if (keyCode ==
KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
534 setKeyboardVisibility(
false,
System.nanoTime());
538 m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode,
event);
539 boolean autoRepeat =
event.getRepeatCount() > 0;
540 QtInputDelegate.keyUp(keyCode,
event.getUnicodeChar(),
event.getMetaState(), autoRepeat);
548 &&
event.getCharacters() !=
null
549 &&
event.getCharacters().length() == 1
550 &&
event.getKeyCode() == 0) {
551 keyDown(0,
event.getCharacters().charAt(0),
event.getMetaState(),
552 event.getRepeatCount() > 0);
553 keyUp(0,
event.getCharacters().charAt(0),
event.getMetaState(),
554 event.getRepeatCount() > 0);
560 boolean handleDispatchGenericMotionEvent(MotionEvent
event)
570 static native
boolean isTabletEventSupported();
571 static native
void tabletEvent(
int winId,
int deviceId,
long time,
int action,
577 static native
void mouseDown(
int winId,
int x,
int y,
int mouseButtonState);
578 static native
void mouseUp(
int winId,
int x,
int y,
int mouseButtonState);
579 static native
void mouseMove(
int winId,
int x,
int y,
int mouseButtonState);
580 static native
void mouseWheel(
int winId,
int x,
int y,
float hDelta,
float vDelta);
581 static native
void touchBegin(
int winId);
582 static native
void touchAdd(
int winId,
int pointerId,
int action,
boolean primary,
583 int x,
int y,
float major,
float minor,
float rotation,
585 static native
void touchEnd(
int winId,
int action);
586 static native
void touchCancel(
int winId);
587 static native
void longPress(
int winId,
int x,
int y);
590 static private int getAction(
int index, MotionEvent
event)
592 int action =
event.getActionMasked();
593 if (action == MotionEvent.ACTION_MOVE) {
594 int hsz =
event.getHistorySize();
596 float x =
event.getX(
index);
597 float y =
event.getY(
index);
598 for (
int h = 0;
h < hsz; ++
h) {
607 if (action == MotionEvent.ACTION_DOWN
608 || action == MotionEvent.ACTION_POINTER_DOWN &&
index ==
event.getActionIndex()) {
610 }
else if (action == MotionEvent.ACTION_UP
611 || action == MotionEvent.ACTION_POINTER_UP &&
index ==
event.getActionIndex()) {
617 static void sendTouchEvent(MotionEvent
event,
int id)
621 if (m_tabletEventSupported ==
null)
622 m_tabletEventSupported = isTabletEventSupported();
624 switch (
event.getToolType(0)) {
625 case MotionEvent.TOOL_TYPE_STYLUS:
628 case MotionEvent.TOOL_TYPE_ERASER:
633 if (
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
634 sendMouseEvent(
event,
id);
635 }
else if (m_tabletEventSupported &&
pointerType != 0) {
636 tabletEvent(
id,
event.getDeviceId(),
event.getEventTime(),
event.getActionMasked(),
641 for (
int i = 0;
i <
event.getPointerCount(); ++
i) {
643 event.getPointerId(i),
648 event.getTouchMajor(i),
649 event.getTouchMinor(i),
650 event.getOrientation(i),
651 event.getPressure(i));
654 switch (
event.getAction()) {
655 case MotionEvent.ACTION_DOWN:
659 case MotionEvent.ACTION_UP:
663 case MotionEvent.ACTION_CANCEL:
673 static void sendTrackballEvent(MotionEvent
event,
int id)
675 sendMouseEvent(
event,
id);
678 static boolean sendGenericMotionEvent(MotionEvent
event,
int id)
680 int scrollOrHoverMove = MotionEvent.ACTION_SCROLL | MotionEvent.ACTION_HOVER_MOVE;
681 int pointerDeviceModifier = (
event.getSource() & InputDevice.SOURCE_CLASS_POINTER);
682 boolean isPointerDevice = pointerDeviceModifier == InputDevice.SOURCE_CLASS_POINTER;
684 if ((
event.getAction() & scrollOrHoverMove) == 0 || !isPointerDevice )
687 return sendMouseEvent(
event,
id);
690 static boolean sendMouseEvent(MotionEvent
event,
int id)
692 switch (
event.getActionMasked()) {
693 case MotionEvent.ACTION_UP:
694 mouseUp(
id, (
int)
event.getX(), (
int)
event.getY(),
event.getButtonState());
697 case MotionEvent.ACTION_DOWN:
698 mouseDown(
id, (
int)
event.getX(), (
int)
event.getY(),
event.getButtonState());
699 m_oldX = (int)
event.getX();
700 m_oldY = (int)
event.getY();
702 case MotionEvent.ACTION_HOVER_MOVE:
703 case MotionEvent.ACTION_MOVE:
704 if (
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
707 int dx = (int) (
event.getX() - m_oldX);
708 int dy = (int) (
event.getY() - m_oldY);
709 if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
711 m_oldX = (int)
event.getX();
712 m_oldY = (int)
event.getY();
716 case MotionEvent.ACTION_SCROLL:
717 mouseWheel(
id, (
int)
event.getX(), (
int)
event.getY(),
718 event.getAxisValue(MotionEvent.AXIS_HSCROLL),
719 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)