4package org.qtproject.qt.android;
6import android.app.Activity;
7import android.content.Context;
8import android.graphics.Rect;
9import android.os.Build;
10import android.os.Bundle;
11import android.os.Handler;
12import android.os.Looper;
13import android.os.ResultReceiver;
14import android.text.method.MetaKeyKeyListener;
15import android.util.DisplayMetrics;
16import android.util.Log;
17import android.view.InputDevice;
18import android.view.KeyCharacterMap;
19import android.view.KeyEvent;
20import android.view.MotionEvent;
21import android.view.WindowInsets;
22import android.view.WindowInsets.Type;
23import android.view.WindowInsetsAnimationController;
24import android.view.WindowInsetsAnimationControlListener;
25import android.view.WindowManager;
26import android.view.View;
27import android.view.ViewTreeObserver;
28import android.view.inputmethod.InputMethodManager;
33 private static final String TAG =
"QtInputDelegate";
35 static native
void keyDown(
int key,
int unicode,
int modifier,
boolean autoRepeat);
36 static native
void keyUp(
int key,
int unicode,
int modifier,
boolean autoRepeat);
37 static native
void keyboardVisibilityChanged(
boolean visibility);
38 static native
void keyboardGeometryChanged(
int x,
int y,
int width,
int height);
42 static native
boolean dispatchGenericMotionEvent(MotionEvent
event);
43 static native
boolean dispatchKeyEvent(KeyEvent
event);
47 static native
void handleLocationChanged(
int id,
int x,
int y);
50 private QtEditText m_currentEditText =
null;
51 private InputMethodManager m_imm;
55 private static final float KEYBOARD_TO_SCREEN_RATIO = 0.15f;
57 private boolean m_keyboardTransitionInProgress =
false;
58 private boolean m_keyboardIsVisible =
false;
59 private boolean m_isKeyboardHidingAnimationOngoing =
false;
60 private long m_showHideTimeStamp =
System.nanoTime();
61 private int m_portraitKeyboardHeight = 0;
62 private int m_landscapeKeyboardHeight = 0;
63 private int m_probeKeyboardHeightDelayMs = 50;
65 private int m_softInputMode = 0;
67 private static Boolean m_tabletEventSupported =
null;
69 private static int m_oldX, m_oldY;
72 private long m_metaState;
73 private int m_lastChar = 0;
74 private boolean m_backKeyPressedSent =
false;
87 m_keyboardVisibilityListener = listener;
90 void initInputMethodManager(Activity activity)
92 m_imm = (InputMethodManager) activity.getSystemService(
Context.INPUT_METHOD_SERVICE);
94 Log.w(
TAG,
"getSystemService() returned a null InputMethodManager instance");
96 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
97 View rootView = activity.getWindow().getDecorView();
98 rootView.setOnApplyWindowInsetsListener((
view, insets) -> {
99 WindowInsets windowInsets =
view.onApplyWindowInsets(insets);
100 if (m_keyboardIsVisible != windowInsets.isVisible(WindowInsets.Type.ime()))
101 setKeyboardVisibility_internal(!m_keyboardIsVisible, System.nanoTime());
107 private final ViewTreeObserver.OnGlobalLayoutListener keyboardListener =
108 new ViewTreeObserver.OnGlobalLayoutListener() {
110 public void onGlobalLayout() {
112 setKeyboardTransitionInProgress(
false);
116 private void setKeyboardTransitionInProgress(
boolean state)
118 if (m_keyboardTransitionInProgress ==
state || m_currentEditText ==
null)
121 m_keyboardTransitionInProgress =
state;
122 ViewTreeObserver observer = m_currentEditText.getViewTreeObserver();
124 observer.addOnGlobalLayoutListener(keyboardListener);
126 observer.removeOnGlobalLayoutListener(keyboardListener);
131 public void updateSelection(
final int selStart,
final int selEnd,
132 final int candidatesStart,
final int candidatesEnd)
135 QtNative.runAction(() -> {
137 m_imm.updateSelection(m_currentEditText, selStart, selEnd,
138 candidatesStart, candidatesEnd);
144 private void showKeyboard(Activity activity,
146 final int inputHints,
final int enterKeyType)
148 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
149 activity.getWindow().getInsetsController().controlWindowInsetsAnimation(
150 WindowInsets.Type.ime(), -1,
null,
null,
151 new WindowInsetsAnimationControlListener() {
153 public void onCancelled(WindowInsetsAnimationController controller) { }
156 public void onReady(WindowInsetsAnimationController controller,
int types) { }
159 public void onFinished(WindowInsetsAnimationController controller) {
160 QtNativeInputConnection.updateCursorPosition();
161 if (m_softInputMode == 0)
163 inputHints, enterKeyType);
166 activity.getWindow().getInsetsController().show(
Type.ime());
170 m_imm.showSoftInput(m_currentEditText, 0,
new ResultReceiver(
new Handler(Looper.getMainLooper())) {
172 @SuppressWarnings(
"fallthrough")
173 protected void onReceiveResult(int resultCode, Bundle resultData) {
174 switch (resultCode) {
175 case InputMethodManager.RESULT_SHOWN:
176 QtNativeInputConnection.updateCursorPosition();
178 case InputMethodManager.RESULT_UNCHANGED_SHOWN:
179 setKeyboardVisibility(true, System.nanoTime());
180 if (m_softInputMode == 0) {
181 probeForKeyboardHeight(activity,
182 x, y, width, height, inputHints, enterKeyType);
185 case InputMethodManager.RESULT_HIDDEN:
186 case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
187 setKeyboardVisibility(false, System.nanoTime());
198 final int inputHints,
final int enterKeyType)
204 if (m_imm ==
null || m_currentEditText ==
null)
207 if (updateSoftInputMode(activity,
height))
210 m_currentEditText.setEditTextOptions(enterKeyType, inputHints);
211 m_currentEditText.setLayoutParams(
new QtLayout.LayoutParams(
width,
height,
x,
y));
212 m_currentEditText.requestFocus();
213 m_currentEditText.postDelayed(() -> {
214 showKeyboard(activity,
x,
y,
width,
height, inputHints, enterKeyType);
215 if (m_currentEditText.m_optionsChanged) {
216 m_imm.restartInput(m_currentEditText);
217 m_currentEditText.m_optionsChanged =
false;
224 public int getSelectionHandleWidth()
226 return m_currentEditText ==
null ? 0 : m_currentEditText.getSelectionHandleWidth();
235 int x1,
int y1,
int x2,
int y2,
boolean rtl)
238 if (m_currentEditText !=
null)
239 m_currentEditText.updateHandles(
mode, editX, editY, editButtons,
x1,
y1,
x2,
y2, rtl);
252 if (m_imm ==
null || m_currentEditText ==
null)
254 m_currentEditText.postDelayed(() -> {
255 if (m_imm ==
null || m_currentEditText ==
null)
257 m_imm.restartInput(m_currentEditText);
258 m_currentEditText.m_optionsChanged =
false;
265 if (m_imm ==
null || m_currentEditText ==
null)
268 m_isKeyboardHidingAnimationOngoing =
true;
270 if (m_imm ==
null || m_currentEditText ==
null)
273 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
275 if (activity ==
null) {
276 Log.w(
TAG,
"hideSoftwareKeyboard: The activity reference is null");
279 activity.getWindow().getInsetsController().hide(
Type.ime());
281 m_imm.hideSoftInputFromWindow(m_currentEditText.getWindowToken(), 0,
282 new ResultReceiver(
new Handler(Looper.getMainLooper())) {
284 protected void onReceiveResult(int resultCode, Bundle resultData) {
285 switch (resultCode) {
286 case InputMethodManager.RESULT_SHOWN:
287 case InputMethodManager.RESULT_UNCHANGED_SHOWN:
288 setKeyboardVisibility(true, System.nanoTime());
290 case InputMethodManager.RESULT_HIDDEN:
291 case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
292 setKeyboardVisibility(false, System.nanoTime());
305 return isKeyboardVisible() && !m_isKeyboardHidingAnimationOngoing;
311 public boolean keyboardTransitionInProgress() {
312 return m_keyboardTransitionInProgress;
316 public boolean isKeyboardHidden() {
317 Activity
activity = QtNative.activity();
318 if (activity ==
null) {
319 Log.w(
TAG,
"isKeyboardHidden: The activity reference is null");
323 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
325 activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(
r);
326 DisplayMetrics
metrics =
new DisplayMetrics();
327 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
328 int screenHeight =
metrics.heightPixels;
329 final int kbHeight = screenHeight -
r.bottom;
330 return kbHeight < screenHeight * KEYBOARD_TO_SCREEN_RATIO;
333 return !m_keyboardIsVisible;
337 public void onSetClosing(
boolean closing) {
339 setKeyboardVisibility(
true,
System.nanoTime());
343 public void onHideKeyboardRunnableDone(
boolean visibility,
long hideTimeStamp) {
344 setKeyboardVisibility(visibility, hideTimeStamp);
348 public void onSendKeyEventDefaultCase() {
353 public void onEditTextChanged(QtEditText editText) {
354 setFocusedView(editText);
358 boolean isKeyboardVisible()
360 return m_keyboardIsVisible;
363 void setSoftInputMode(
int inputMode)
365 m_softInputMode = inputMode;
368 QtEditText getCurrentQtEditText()
370 return m_currentEditText;
373 private void keyboardVisibilityUpdated(
boolean visibility)
375 m_isKeyboardHidingAnimationOngoing =
false;
376 QtInputDelegate.keyboardVisibilityChanged(visibility);
379 void setKeyboardVisibility(
boolean visibility,
long timeStamp)
383 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
384 setKeyboardVisibility_internal(visibility, timeStamp);
387 private void setKeyboardVisibility_internal(
boolean visibility,
long timeStamp)
389 if (m_showHideTimeStamp > timeStamp)
391 m_showHideTimeStamp = timeStamp;
393 if (m_keyboardIsVisible == visibility)
395 m_keyboardIsVisible = visibility;
396 keyboardVisibilityUpdated(m_keyboardIsVisible);
397 setKeyboardTransitionInProgress(visibility);
402 if (m_currentEditText !=
null)
403 m_currentEditText.clearFocus();
407 void setFocusedView(QtEditText currentEditText)
409 setKeyboardTransitionInProgress(
false);
410 m_currentEditText = currentEditText;
413 private boolean updateSoftInputMode(Activity activity,
int height)
415 DisplayMetrics
metrics =
new DisplayMetrics();
416 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
421 final int visibleHeight;
423 visibleHeight = m_portraitKeyboardHeight != 0 ?
424 m_portraitKeyboardHeight : metrics.heightPixels * 3 / 5;
426 visibleHeight = m_landscapeKeyboardHeight != 0 ?
427 m_landscapeKeyboardHeight : metrics.heightPixels / 3;
430 if (m_softInputMode != 0) {
431 activity.getWindow().setSoftInputMode(m_softInputMode);
432 int stateHidden = WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
433 return (m_softInputMode & stateHidden) != 0;
435 int stateUnchanged = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
436 if (
height > visibleHeight) {
437 int adjustResize = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
438 activity.getWindow().setSoftInputMode(stateUnchanged | adjustResize);
440 int adjustPan = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
441 activity.getWindow().setSoftInputMode(stateUnchanged | adjustPan);
447 private void probeForKeyboardHeight(Activity activity,
int x,
int y,
448 int width,
int height,
int inputHints,
int enterKeyType)
450 if (m_currentEditText ==
null) {
451 Log.w(
TAG,
"probeForKeyboardHeight: null QtEditText");
454 m_currentEditText.postDelayed(() -> {
455 if (!m_keyboardIsVisible)
457 DisplayMetrics
metrics =
new DisplayMetrics();
458 QtDisplayManager.getDisplay(activity).getMetrics(
metrics);
460 activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(
r);
461 if (
metrics.heightPixels !=
r.bottom) {
463 if (m_landscapeKeyboardHeight !=
r.bottom) {
464 m_landscapeKeyboardHeight =
r.bottom;
466 inputHints, enterKeyType);
469 if (m_portraitKeyboardHeight !=
r.bottom) {
470 m_portraitKeyboardHeight =
r.bottom;
472 inputHints, enterKeyType);
478 if (m_probeKeyboardHeightDelayMs < 1000)
479 m_probeKeyboardHeightDelayMs *= 2;
481 }, m_probeKeyboardHeightDelayMs);
486 m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode,
event);
487 int metaState = MetaKeyKeyListener.getMetaState(m_metaState) |
event.getMetaState();
488 int c =
event.getUnicodeChar(metaState);
490 m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState);
492 if ((
c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
493 c =
c & KeyCharacterMap.COMBINING_ACCENT_MASK;
497 if ((keyCode ==
KeyEvent.KEYCODE_VOLUME_UP
498 || keyCode ==
KeyEvent.KEYCODE_VOLUME_DOWN
499 || keyCode ==
KeyEvent.KEYCODE_MUTE)
500 &&
System.getenv(
"QT_ANDROID_VOLUME_KEYS") ==
null) {
505 if (keyCode ==
KeyEvent.KEYCODE_BACK) {
506 m_backKeyPressedSent = !isKeyboardVisible();
507 if (!m_backKeyPressedSent)
511 QtInputDelegate.keyDown(keyCode,
c,
event.getMetaState(),
event.getRepeatCount() > 0);
518 if ((keyCode ==
KeyEvent.KEYCODE_VOLUME_UP
519 || keyCode ==
KeyEvent.KEYCODE_VOLUME_DOWN
520 || keyCode ==
KeyEvent.KEYCODE_MUTE)
521 &&
System.getenv(
"QT_ANDROID_VOLUME_KEYS") ==
null) {
525 if (keyCode ==
KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
527 setKeyboardVisibility(
false,
System.nanoTime());
531 m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode,
event);
532 boolean autoRepeat =
event.getRepeatCount() > 0;
533 QtInputDelegate.keyUp(keyCode,
event.getUnicodeChar(),
event.getMetaState(), autoRepeat);
541 &&
event.getCharacters() !=
null
542 &&
event.getCharacters().length() == 1
543 &&
event.getKeyCode() == 0) {
544 keyDown(0,
event.getCharacters().charAt(0),
event.getMetaState(),
545 event.getRepeatCount() > 0);
546 keyUp(0,
event.getCharacters().charAt(0),
event.getMetaState(),
547 event.getRepeatCount() > 0);
553 boolean handleDispatchGenericMotionEvent(MotionEvent
event)
563 static native
boolean isTabletEventSupported();
564 static native
void tabletEvent(
int winId,
int deviceId,
long time,
int action,
570 static native
void mouseDown(
int winId,
int x,
int y,
int mouseButtonState);
571 static native
void mouseUp(
int winId,
int x,
int y,
int mouseButtonState);
572 static native
void mouseMove(
int winId,
int x,
int y,
int mouseButtonState);
573 static native
void mouseWheel(
int winId,
int x,
int y,
float hDelta,
float vDelta);
574 static native
void touchBegin(
int winId);
575 static native
void touchAdd(
int winId,
int pointerId,
int action,
boolean primary,
576 int x,
int y,
float major,
float minor,
float rotation,
578 static native
void touchEnd(
int winId,
int action);
579 static native
void touchCancel(
int winId);
580 static native
void longPress(
int winId,
int x,
int y);
583 static private int getAction(
int index, MotionEvent
event)
585 int action =
event.getActionMasked();
586 if (action == MotionEvent.ACTION_MOVE) {
587 int hsz =
event.getHistorySize();
589 float x =
event.getX(
index);
590 float y =
event.getY(
index);
591 for (
int h = 0;
h < hsz; ++
h) {
600 if (action == MotionEvent.ACTION_DOWN
601 || action == MotionEvent.ACTION_POINTER_DOWN &&
index ==
event.getActionIndex()) {
603 }
else if (action == MotionEvent.ACTION_UP
604 || action == MotionEvent.ACTION_POINTER_UP &&
index ==
event.getActionIndex()) {
610 static void sendTouchEvent(MotionEvent
event,
int id)
614 if (m_tabletEventSupported ==
null)
615 m_tabletEventSupported = isTabletEventSupported();
617 switch (
event.getToolType(0)) {
618 case MotionEvent.TOOL_TYPE_STYLUS:
621 case MotionEvent.TOOL_TYPE_ERASER:
626 if (
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
627 sendMouseEvent(
event,
id);
628 }
else if (m_tabletEventSupported &&
pointerType != 0) {
629 tabletEvent(
id,
event.getDeviceId(),
event.getEventTime(),
event.getActionMasked(),
634 for (
int i = 0;
i <
event.getPointerCount(); ++
i) {
636 event.getPointerId(i),
641 event.getTouchMajor(i),
642 event.getTouchMinor(i),
643 event.getOrientation(i),
644 event.getPressure(i));
647 switch (
event.getAction()) {
648 case MotionEvent.ACTION_DOWN:
652 case MotionEvent.ACTION_UP:
656 case MotionEvent.ACTION_CANCEL:
666 static void sendTrackballEvent(MotionEvent
event,
int id)
668 sendMouseEvent(
event,
id);
671 static boolean sendGenericMotionEvent(MotionEvent
event,
int id)
673 int scrollOrHoverMove = MotionEvent.ACTION_SCROLL | MotionEvent.ACTION_HOVER_MOVE;
674 int pointerDeviceModifier = (
event.getSource() & InputDevice.SOURCE_CLASS_POINTER);
675 boolean isPointerDevice = pointerDeviceModifier == InputDevice.SOURCE_CLASS_POINTER;
677 if ((
event.getAction() & scrollOrHoverMove) == 0 || !isPointerDevice )
680 return sendMouseEvent(
event,
id);
683 static boolean sendMouseEvent(MotionEvent
event,
int id)
685 switch (
event.getActionMasked()) {
686 case MotionEvent.ACTION_UP:
687 mouseUp(
id, (
int)
event.getX(), (
int)
event.getY(),
event.getButtonState());
690 case MotionEvent.ACTION_DOWN:
691 mouseDown(
id, (
int)
event.getX(), (
int)
event.getY(),
event.getButtonState());
692 m_oldX = (int)
event.getX();
693 m_oldY = (int)
event.getY();
695 case MotionEvent.ACTION_HOVER_MOVE:
696 case MotionEvent.ACTION_MOVE:
697 if (
event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
700 int dx = (int) (
event.getX() - m_oldX);
701 int dy = (int) (
event.getY() - m_oldY);
702 if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
704 m_oldX = (int)
event.getX();
705 m_oldY = (int)
event.getY();
709 case MotionEvent.ACTION_SCROLL:
710 mouseWheel(
id, (
int)
event.getX(), (
int)
event.getY(),
711 event.getAxisValue(MotionEvent.AXIS_HSCROLL),
712 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
GLsizei GLenum GLenum * types
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)