Android: Handle virtual keyboard visibility changes.

emitInputPanelVisibleChanged when virtual keyboard visibility is changed.

Task-number: QTBUG-34347

Change-Id: Iab7374db42ff8ce6f33dcc793b23f84d3c8692d5
Reviewed-by: Paul Olav Tvete <paul.tvete@digia.com>
This commit is contained in:
BogDan Vatra 2013-10-31 18:00:30 +02:00 committed by The Qt Project
parent d1114669e3
commit b7440536c7
7 changed files with 133 additions and 66 deletions

View File

@ -42,13 +42,6 @@
package org.qtproject.qt5.android;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
@ -68,11 +61,18 @@ import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.view.Surface;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
public class QtActivityDelegate
{
@ -111,7 +111,7 @@ public class QtActivityDelegate
private boolean m_quitApp = true;
private Process m_debuggerProcess = null; // debugger process
public boolean m_keyboardIsVisible = false;
private boolean m_keyboardIsVisible = false;
public boolean m_backKeyPressedSent = false;
@ -176,6 +176,13 @@ public class QtActivityDelegate
private final int ApplicationInactive = 0x2;
private final int ApplicationActive = 0x4;
public void setKeyboardVisibility(boolean visibility)
{
if (m_keyboardIsVisible == visibility)
return;
m_keyboardIsVisible = visibility;
QtNative.keyboardVisibilityChanged(m_keyboardIsVisible);
}
public void resetSoftwareKeyboard()
{
if (m_imm == null)
@ -256,27 +263,21 @@ public class QtActivityDelegate
m_editText.postDelayed(new Runnable() {
@Override
public void run() {
m_imm.showSoftInput(m_editText, 0, new ResultReceiver( new Handler()){
m_imm.showSoftInput(m_editText, 0, new ResultReceiver(new Handler()) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case InputMethodManager.RESULT_SHOWN:
case InputMethodManager.RESULT_UNCHANGED_SHOWN:
m_keyboardIsVisible = true;
setKeyboardVisibility(true);
break;
case InputMethodManager.RESULT_HIDDEN:
case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
m_keyboardIsVisible = false;
setKeyboardVisibility(false);
break;
}
}
}) ;
m_editText.postDelayed(new Runnable() {
@Override
public void run() {
m_imm.restartInput(m_editText);
}
}, 25);
});
}
}, 15);
}
@ -291,11 +292,11 @@ public class QtActivityDelegate
switch (resultCode) {
case InputMethodManager.RESULT_SHOWN:
case InputMethodManager.RESULT_UNCHANGED_SHOWN:
m_keyboardIsVisible = true;
setKeyboardVisibility(true);
break;
case InputMethodManager.RESULT_HIDDEN:
case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
m_keyboardIsVisible = false;
setKeyboardVisibility(false);
break;
}
}
@ -612,7 +613,7 @@ public class QtActivityDelegate
}
m_layout = new QtLayout(m_activity);
m_surface = new QtSurface(m_activity, 0);
m_editText = new QtEditText(m_activity);
m_editText = new QtEditText(m_activity, this);
m_imm = (InputMethodManager)m_activity.getSystemService(Context.INPUT_METHOD_SERVICE);
m_layout.addView(m_surface,0);
m_activity.setContentView(m_layout,
@ -770,7 +771,7 @@ public class QtActivityDelegate
if (keyCode == KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
hideSoftwareKeyboard();
m_keyboardIsVisible = false;
setKeyboardVisibility(false);
return true;
}

View File

@ -50,10 +50,10 @@ import android.view.inputmethod.InputConnection;
public class QtEditText extends View
{
QtInputConnection m_inputConnection;
int m_initialCapsMode = 0;
int m_imeOptions = 0;
int m_inputType = InputType.TYPE_CLASS_TEXT;
QtActivityDelegate m_activityDelegate;
public void setImeOptions(int m_imeOptions)
{
@ -71,12 +71,16 @@ public class QtEditText extends View
this.m_inputType = m_inputType;
}
public QtEditText(Context context)
public QtEditText(Context context, QtActivityDelegate activityDelegate)
{
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
m_inputConnection = new QtInputConnection(this);
m_activityDelegate = activityDelegate;
}
public QtActivityDelegate getActivityDelegate()
{
return m_activityDelegate;
}
@Override
@ -86,8 +90,9 @@ public class QtEditText extends View
outAttrs.imeOptions = m_imeOptions;
outAttrs.initialCapsMode = m_initialCapsMode;
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
return m_inputConnection;
return new QtInputConnection(this);
}
// // DEBUG CODE
// @Override
// protected void onDraw(Canvas canvas) {

View File

@ -43,7 +43,6 @@
package org.qtproject.qt5.android;
import android.content.Context;
import android.view.View;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedText;
@ -81,6 +80,22 @@ class QtNativeInputConnection
static native boolean paste();
}
class HideKeyboardRunnable implements Runnable {
private QtInputConnection m_connection;
HideKeyboardRunnable(QtInputConnection connection)
{
m_connection = connection;
}
@Override
public void run() {
if (m_connection.getInputState() == QtInputConnection.InputStates.Hiding) {
QtNative.activityDelegate().setKeyboardVisibility(false);
m_connection.reset();
}
}
}
public class QtInputConnection extends BaseInputConnection
{
private static final int ID_SELECT_ALL = android.R.id.selectAll;
@ -91,65 +106,83 @@ public class QtInputConnection extends BaseInputConnection
private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
View m_view;
boolean m_closing;
public QtInputConnection(View targetView)
enum InputStates { Visible, FinishComposing, Hiding };
private QtEditText m_view = null;
private InputStates m_inputState = InputStates.Visible;
public void reset()
{
m_inputState = InputStates.Visible;
}
public InputStates getInputState()
{
return m_inputState;
}
private void setClosing(boolean closing)
{
if (closing && m_inputState == InputStates.Hiding)
return;
if (closing && m_inputState == InputStates.FinishComposing && m_view.getActivityDelegate().isSoftwareKeyboardVisible()) {
m_view.postDelayed(new HideKeyboardRunnable(this), 100);
m_inputState = InputStates.Hiding;
} else {
if (m_inputState == InputStates.Hiding)
QtNative.activityDelegate().setKeyboardVisibility(true);
m_inputState = closing ? InputStates.FinishComposing : InputStates.Visible;
}
}
public QtInputConnection(QtEditText targetView)
{
super(targetView, true);
m_view = targetView;
m_closing = false;
}
@Override
public boolean beginBatchEdit()
{
m_closing = false;
setClosing(false);
return true;
}
@Override
public boolean endBatchEdit()
{
m_closing = false;
// setClosing(false);
return true;
}
@Override
public boolean commitCompletion(CompletionInfo text)
{
m_closing = false;
setClosing(false);
return QtNativeInputConnection.commitCompletion(text.getText().toString(), text.getPosition());
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition)
{
m_closing = false;
setClosing(false);
return QtNativeInputConnection.commitText(text.toString(), newCursorPosition);
}
@Override
public boolean deleteSurroundingText(int leftLength, int rightLength)
{
m_closing = false;
setClosing(false);
return QtNativeInputConnection.deleteSurroundingText(leftLength, rightLength);
}
@Override
public boolean finishComposingText()
{
if (m_closing) {
m_view.postDelayed(new Runnable() {
@Override
public void run() {
QtNative.activityDelegate().m_keyboardIsVisible=false;
}
}, 100); // it seems finishComposingText comes much faster than onKeyUp event,
// so we must delay hide notification
m_closing = false;
} else {
m_closing = true;
}
// on some/all android devices hide event is not coming, but instead finishComposingText() is called twice
setClosing(true);
return QtNativeInputConnection.finishComposingText();
}
@ -231,18 +264,21 @@ public class QtInputConnection extends BaseInputConnection
@Override
public boolean setComposingText(CharSequence text, int newCursorPosition)
{
setClosing(false);
return QtNativeInputConnection.setComposingText(text.toString(), newCursorPosition);
}
@Override
public boolean setComposingRegion(int start, int end)
{
setClosing(false);
return QtNativeInputConnection.setComposingRegion(start, end);
}
@Override
public boolean setSelection(int start, int end)
{
setClosing(false);
return QtNativeInputConnection.setSelection(start, end);
}
}

View File

@ -418,30 +418,21 @@ public class QtNative
private static boolean isSoftwareKeyboardVisible()
{
Semaphore semaphore = new Semaphore(1);
Boolean ret = false;
class RunnableRes implements Runnable {
@SuppressWarnings("unused")
Boolean returnValue = null;
Semaphore semaphore = null;
RunnableRes(Boolean ret, Semaphore sem) {
semaphore = sem;
returnValue = ret;
}
final Semaphore semaphore = new Semaphore(0);
final Boolean[] ret = {false};
runAction(new Runnable() {
@Override
public void run() {
returnValue = m_activityDelegate.isSoftwareKeyboardVisible();
ret[0] = m_activityDelegate.isSoftwareKeyboardVisible();
semaphore.release();
}
}
runAction(new RunnableRes(ret, semaphore));
});
try {
semaphore.acquire();
} catch (Exception e) {
e.printStackTrace();
}
return ret;
return ret[0];
}
private static void setFullScreen(final boolean fullScreen)
@ -568,6 +559,7 @@ public class QtNative
// keyboard methods
public static native void keyDown(int key, int unicode, int modifier);
public static native void keyUp(int key, int unicode, int modifier);
public static native void keyboardVisibilityChanged(boolean visibility);
// keyboard methods
// surface methods

View File

@ -47,6 +47,10 @@
#include <QTouchEvent>
#include <QPointer>
#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
# include <QDebug>
#endif
using namespace QtAndroid;
namespace QtAndroidInput
@ -86,6 +90,9 @@ namespace QtAndroidInput
width,
height,
inputHints);
#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
qDebug() << "@@@ SHOWSOFTWAREKEYBOARD" << left << top << width << height << inputHints;
#endif
}
void resetSoftwareKeyboard()
@ -95,6 +102,9 @@ namespace QtAndroidInput
return;
env.jniEnv->CallStaticVoidMethod(applicationClass(), m_resetSoftwareKeyboardMethodID);
#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
qDebug() << "@@@ RESETSOFTWAREKEYBOARD";
#endif
}
void hideSoftwareKeyboard()
@ -104,6 +114,9 @@ namespace QtAndroidInput
return;
env.jniEnv->CallStaticVoidMethod(applicationClass(), m_hideSoftwareKeyboardMethodID);
#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
qDebug() << "@@@ HIDESOFTWAREKEYBOARD";
#endif
}
bool isSoftwareKeyboardVisible()
@ -112,7 +125,11 @@ namespace QtAndroidInput
if (!env.jniEnv)
return false;
return env.jniEnv->CallStaticBooleanMethod(applicationClass(), m_isSoftwareKeyboardVisibleMethodID);
bool visibility = env.jniEnv->CallStaticBooleanMethod(applicationClass(), m_isSoftwareKeyboardVisibleMethodID);
#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
qDebug() << "@@@ ISSOFTWAREKEYBOARDVISIBLE" << visibility;
#endif
return visibility;
}
@ -511,6 +528,15 @@ namespace QtAndroidInput
false);
}
static void keyboardVisibilityChanged(JNIEnv */*env*/, jobject /*thiz*/, jboolean /*visibility*/)
{
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
if (inputContext)
inputContext->emitInputPanelVisibleChanged();
#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
qDebug() << "@@@ KEYBOARDVISIBILITYCHANGED" << inputContext;
#endif
}
static JNINativeMethod methods[] = {
{"touchBegin","(I)V",(void*)touchBegin},
@ -521,7 +547,8 @@ namespace QtAndroidInput
{"mouseMove", "(III)V", (void *)mouseMove},
{"longPress", "(III)V", (void *)longPress},
{"keyDown", "(III)V", (void *)keyDown},
{"keyUp", "(III)V", (void *)keyUp}
{"keyUp", "(III)V", (void *)keyUp},
{"keyboardVisibilityChanged", "(Z)V", (void *)keyboardVisibilityChanged}
};
#define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \

View File

@ -381,6 +381,11 @@ QAndroidInputContext::~QAndroidInputContext()
m_textFieldID = 0;
}
QAndroidInputContext *QAndroidInputContext::androidInputContext()
{
return m_androidInputContext;
}
void QAndroidInputContext::reset()
{
clear();

View File

@ -80,6 +80,7 @@ public:
public:
QAndroidInputContext();
~QAndroidInputContext();
static QAndroidInputContext * androidInputContext();
bool isValid() const { return true; }
void reset();