summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2013-03-26 15:42:39 -0700
committerJeff Brown <jeffbrown@google.com>2013-03-26 15:42:39 -0700
commitc28867a1d67121ce5963de135e3ae2b1dbd9a33d (patch)
tree95da9070093882f0b95ee197f45f5b4a5e348d96
parent37f180b4a52e4c1d0b6a7b400b6579b7ff25f307 (diff)
downloadframeworks_base-c28867a1d67121ce5963de135e3ae2b1dbd9a33d.zip
frameworks_base-c28867a1d67121ce5963de135e3ae2b1dbd9a33d.tar.gz
frameworks_base-c28867a1d67121ce5963de135e3ae2b1dbd9a33d.tar.bz2
Use input transport for communications between app and IME.
The input method manager service now supplies an input channel for communication while creating an IME session on behalf of the application. This change significanly reduces the overhead of IME event dispatch by using a standard input channel to send input events rather than using binder. This results in fewer thread context switches and fewer object allocations. What's more, the IME may perform additional batching of the motion events that it receives which may help it catch up if it is getting behind while processing them. Bug: 7984576 Bug: 8473020 Change-Id: Ibe26311edd0060cdcae80194f1753482e635786f
-rw-r--r--Android.mk2
-rw-r--r--CleanSpec.mk2
-rw-r--r--core/java/android/app/NativeActivity.java3
-rw-r--r--core/java/android/inputmethodservice/AbstractInputMethodService.java5
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java170
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java52
-rw-r--r--core/java/android/view/InputChannel.java10
-rw-r--r--core/java/android/view/InputEventSender.java140
-rw-r--r--core/java/android/view/ViewRootImpl.java82
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java163
-rw-r--r--core/java/com/android/internal/view/IInputMethod.aidl3
-rw-r--r--core/java/com/android/internal/view/IInputMethodCallback.aidl26
-rw-r--r--core/java/com/android/internal/view/IInputMethodSession.aidl9
-rw-r--r--core/java/com/android/internal/view/InputBindResult.java36
-rw-r--r--core/jni/Android.mk1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_view_InputEventSender.cpp307
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java127
18 files changed, 819 insertions, 321 deletions
diff --git a/Android.mk b/Android.mk
index 8090448..2ad7a72 100644
--- a/Android.mk
+++ b/Android.mk
@@ -195,7 +195,6 @@ LOCAL_SRC_FILES += \
core/java/com/android/internal/view/IInputContext.aidl \
core/java/com/android/internal/view/IInputContextCallback.aidl \
core/java/com/android/internal/view/IInputMethod.aidl \
- core/java/com/android/internal/view/IInputMethodCallback.aidl \
core/java/com/android/internal/view/IInputMethodClient.aidl \
core/java/com/android/internal/view/IInputMethodManager.aidl \
core/java/com/android/internal/view/IInputMethodSession.aidl \
@@ -307,7 +306,6 @@ aidl_files := \
frameworks/base/core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl \
frameworks/base/core/java/com/android/internal/view/IInputContext.aidl \
frameworks/base/core/java/com/android/internal/view/IInputMethod.aidl \
- frameworks/base/core/java/com/android/internal/view/IInputMethodCallback.aidl \
frameworks/base/core/java/com/android/internal/view/IInputMethodClient.aidl \
frameworks/base/core/java/com/android/internal/view/IInputMethodManager.aidl \
frameworks/base/core/java/com/android/internal/view/IInputMethodSession.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index e715a9f..fc63866 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -157,6 +157,8 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/librtp_jni.so)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/android/internal/telephony/SmsRawData.*)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/view/IInputMethodCallback.*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/view/IInputMethodSession.*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/view/IInputMethodCallback.*)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index 396f910..51867bc 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -353,8 +353,7 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
}
void preDispatchKeyEvent(KeyEvent event, int seq) {
- mIMM.dispatchKeyEvent(this, seq, event,
- mInputMethodCallback);
+ mIMM.dispatchInputEvent(this, seq, event, mInputMethodCallback);
}
void setWindowFlags(int flags, int mask) {
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 3c3182a..3531926 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -126,11 +126,12 @@ public abstract class AbstractInputMethodService extends Service
mRevoked = true;
mEnabled = false;
}
-
+
/**
* Take care of dispatching incoming key events to the appropriate
* callbacks on the service, and tell the client when this is done.
*/
+ @Override
public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback) {
boolean handled = event.dispatch(AbstractInputMethodService.this,
mDispatcherState, this);
@@ -143,6 +144,7 @@ public abstract class AbstractInputMethodService extends Service
* Take care of dispatching incoming trackball events to the appropriate
* callbacks on the service, and tell the client when this is done.
*/
+ @Override
public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback) {
boolean handled = onTrackballEvent(event);
if (callback != null) {
@@ -154,6 +156,7 @@ public abstract class AbstractInputMethodService extends Service
* Take care of dispatching incoming generic motion events to the appropriate
* callbacks on the service, and tell the client when this is done.
*/
+ @Override
public void dispatchGenericMotionEvent(int seq, MotionEvent event, EventCallback callback) {
boolean handled = onGenericMotionEvent(event);
if (callback != null) {
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index d78262b..726dcec 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -18,15 +18,20 @@ package android.inputmethodservice;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
-import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodSession;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
@@ -36,14 +41,10 @@ import android.view.inputmethod.InputMethodSession;
class IInputMethodSessionWrapper extends IInputMethodSession.Stub
implements HandlerCaller.Callback {
private static final String TAG = "InputMethodWrapper";
- private static final boolean DEBUG = false;
private static final int DO_FINISH_INPUT = 60;
private static final int DO_DISPLAY_COMPLETIONS = 65;
private static final int DO_UPDATE_EXTRACTED_TEXT = 67;
- private static final int DO_DISPATCH_KEY_EVENT = 70;
- private static final int DO_DISPATCH_TRACKBALL_EVENT = 80;
- private static final int DO_DISPATCH_GENERIC_MOTION_EVENT = 85;
private static final int DO_UPDATE_SELECTION = 90;
private static final int DO_UPDATE_CURSOR = 95;
private static final int DO_APP_PRIVATE_COMMAND = 100;
@@ -53,34 +54,30 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
HandlerCaller mCaller;
InputMethodSession mInputMethodSession;
-
- // NOTE: we should have a cache of these.
- static class InputMethodEventCallbackWrapper implements InputMethodSession.EventCallback {
- final IInputMethodCallback mCb;
- InputMethodEventCallbackWrapper(IInputMethodCallback cb) {
- mCb = cb;
- }
- public void finishedEvent(int seq, boolean handled) {
- try {
- mCb.finishedEvent(seq, handled);
- } catch (RemoteException e) {
- }
- }
- }
-
+ InputChannel mChannel;
+ ImeInputEventReceiver mReceiver;
+
public IInputMethodSessionWrapper(Context context,
- InputMethodSession inputMethodSession) {
+ InputMethodSession inputMethodSession, InputChannel channel) {
mCaller = new HandlerCaller(context, null,
this, true /*asyncHandler*/);
mInputMethodSession = inputMethodSession;
+ mChannel = channel;
+ if (channel != null) {
+ mReceiver = new ImeInputEventReceiver(channel, context.getMainLooper());
+ }
}
public InputMethodSession getInternalInputMethodSession() {
return mInputMethodSession;
}
+ @Override
public void executeMessage(Message msg) {
- if (mInputMethodSession == null) return;
+ if (mInputMethodSession == null) {
+ // The session has been finished.
+ return;
+ }
switch (msg.what) {
case DO_FINISH_INPUT:
@@ -93,33 +90,6 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
mInputMethodSession.updateExtractedText(msg.arg1,
(ExtractedText)msg.obj);
return;
- case DO_DISPATCH_KEY_EVENT: {
- SomeArgs args = (SomeArgs)msg.obj;
- mInputMethodSession.dispatchKeyEvent(msg.arg1,
- (KeyEvent)args.arg1,
- new InputMethodEventCallbackWrapper(
- (IInputMethodCallback)args.arg2));
- args.recycle();
- return;
- }
- case DO_DISPATCH_TRACKBALL_EVENT: {
- SomeArgs args = (SomeArgs)msg.obj;
- mInputMethodSession.dispatchTrackballEvent(msg.arg1,
- (MotionEvent)args.arg1,
- new InputMethodEventCallbackWrapper(
- (IInputMethodCallback)args.arg2));
- args.recycle();
- return;
- }
- case DO_DISPATCH_GENERIC_MOTION_EVENT: {
- SomeArgs args = (SomeArgs)msg.obj;
- mInputMethodSession.dispatchGenericMotionEvent(msg.arg1,
- (MotionEvent)args.arg1,
- new InputMethodEventCallbackWrapper(
- (IInputMethodCallback)args.arg2));
- args.recycle();
- return;
- }
case DO_UPDATE_SELECTION: {
SomeArgs args = (SomeArgs)msg.obj;
mInputMethodSession.updateSelection(args.argi1, args.argi2,
@@ -143,7 +113,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
return;
}
case DO_FINISH_SESSION: {
- mInputMethodSession = null;
+ doFinishSession();
return;
}
case DO_VIEW_CLICKED: {
@@ -153,37 +123,37 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
-
+
+ private void doFinishSession() {
+ mInputMethodSession = null;
+ if (mReceiver != null) {
+ mReceiver.dispose();
+ mReceiver = null;
+ }
+ if (mChannel != null) {
+ mChannel.dispose();
+ mChannel = null;
+ }
+ }
+
+ @Override
public void finishInput() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_INPUT));
}
+ @Override
public void displayCompletions(CompletionInfo[] completions) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(
DO_DISPLAY_COMPLETIONS, completions));
}
-
+
+ @Override
public void updateExtractedText(int token, ExtractedText text) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
DO_UPDATE_EXTRACTED_TEXT, token, text));
}
-
- public void dispatchKeyEvent(int seq, KeyEvent event, IInputMethodCallback callback) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_KEY_EVENT, seq,
- event, callback));
- }
-
- public void dispatchTrackballEvent(int seq, MotionEvent event, IInputMethodCallback callback) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_TRACKBALL_EVENT, seq,
- event, callback));
- }
-
- public void dispatchGenericMotionEvent(int seq, MotionEvent event,
- IInputMethodCallback callback) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_GENERIC_MOTION_EVENT, seq,
- event, callback));
- }
+ @Override
public void updateSelection(int oldSelStart, int oldSelEnd,
int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION,
@@ -191,24 +161,74 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
candidatesStart, candidatesEnd));
}
+ @Override
public void viewClicked(boolean focusChanged) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
}
+ @Override
public void updateCursor(Rect newCursor) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_UPDATE_CURSOR,
- newCursor));
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor));
}
-
+
+ @Override
public void appPrivateCommand(String action, Bundle data) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
}
-
+
+ @Override
public void toggleSoftInput(int showFlags, int hideFlags) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
}
+ @Override
public void finishSession() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION));
}
+
+ private final class ImeInputEventReceiver extends InputEventReceiver
+ implements InputMethodSession.EventCallback {
+ private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>();
+
+ public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ if (mInputMethodSession == null) {
+ // The session has been finished.
+ finishInputEvent(event, false);
+ return;
+ }
+
+ final int seq = event.getSequenceNumber();
+ mPendingEvents.put(seq, event);
+ if (event instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent)event;
+ mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this);
+ } else {
+ MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
+ mInputMethodSession.dispatchTrackballEvent(seq, motionEvent, this);
+ } else {
+ mInputMethodSession.dispatchGenericMotionEvent(seq, motionEvent, this);
+ }
+ }
+ }
+
+ @Override
+ public void finishedEvent(int seq, boolean handled) {
+ int index = mPendingEvents.indexOfKey(seq);
+ if (index >= 0) {
+ InputEvent event = mPendingEvents.valueAt(index);
+ mPendingEvents.removeAt(index);
+ finishInputEvent(event, handled);
+ }
+ }
+ }
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 0d981be..9306373 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -32,6 +32,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
+import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -53,7 +54,6 @@ import java.util.concurrent.TimeUnit;
class IInputMethodWrapper extends IInputMethod.Stub
implements HandlerCaller.Callback {
private static final String TAG = "InputMethodWrapper";
- private static final boolean DEBUG = false;
private static final int DO_DUMP = 1;
private static final int DO_ATTACH_TOKEN = 10;
@@ -78,20 +78,29 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
// NOTE: we should have a cache of these.
- static class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
+ static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
final Context mContext;
+ final InputChannel mChannel;
final IInputSessionCallback mCb;
- InputMethodSessionCallbackWrapper(Context context, IInputSessionCallback cb) {
+
+ InputMethodSessionCallbackWrapper(Context context, InputChannel channel,
+ IInputSessionCallback cb) {
mContext = context;
+ mChannel = channel;
mCb = cb;
}
+
+ @Override
public void sessionCreated(InputMethodSession session) {
try {
if (session != null) {
IInputMethodSessionWrapper wrap =
- new IInputMethodSessionWrapper(mContext, session);
+ new IInputMethodSessionWrapper(mContext, session, mChannel);
mCb.sessionCreated(wrap);
} else {
+ if (mChannel != null) {
+ mChannel.dispose();
+ }
mCb.sessionCreated(null);
}
} catch (RemoteException e) {
@@ -112,6 +121,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
return mInputMethod.get();
}
+ @Override
public void executeMessage(Message msg) {
InputMethod inputMethod = mInputMethod.get();
// Need a valid reference to the inputMethod for everything except a dump.
@@ -174,8 +184,11 @@ class IInputMethodWrapper extends IInputMethod.Stub
return;
}
case DO_CREATE_SESSION: {
+ SomeArgs args = (SomeArgs)msg.obj;
inputMethod.createSession(new InputMethodSessionCallbackWrapper(
- mCaller.mContext, (IInputSessionCallback)msg.obj));
+ mCaller.mContext, (InputChannel)args.arg1,
+ (IInputSessionCallback)args.arg2));
+ args.recycle();
return;
}
case DO_SET_SESSION_ENABLED:
@@ -197,8 +210,9 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
-
- @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
AbstractInputMethodService target = mTarget.get();
if (target == null) {
return;
@@ -224,10 +238,12 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
+ @Override
public void attachToken(IBinder token) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
}
-
+
+ @Override
public void bindInput(InputBinding binding) {
InputConnection ic = new InputConnectionWrapper(
IInputContext.Stub.asInterface(binding.getConnectionToken()));
@@ -235,24 +251,30 @@ class IInputMethodWrapper extends IInputMethod.Stub
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
}
+ @Override
public void unbindInput() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
}
+ @Override
public void startInput(IInputContext inputContext, EditorInfo attribute) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
inputContext, attribute));
}
+ @Override
public void restartInput(IInputContext inputContext, EditorInfo attribute) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_RESTART_INPUT,
inputContext, attribute));
}
- public void createSession(IInputSessionCallback callback) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CREATE_SESSION, callback));
+ @Override
+ public void createSession(InputChannel channel, IInputSessionCallback callback) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
+ channel, callback));
}
+ @Override
public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
try {
InputMethodSession ls = ((IInputMethodSessionWrapper)
@@ -263,7 +285,8 @@ class IInputMethodWrapper extends IInputMethod.Stub
Log.w(TAG, "Incoming session not of correct type: " + session, e);
}
}
-
+
+ @Override
public void revokeSession(IInputMethodSession session) {
try {
InputMethodSession ls = ((IInputMethodSessionWrapper)
@@ -273,17 +296,20 @@ class IInputMethodWrapper extends IInputMethod.Stub
Log.w(TAG, "Incoming session not of correct type: " + session, e);
}
}
-
+
+ @Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
flags, resultReceiver));
}
-
+
+ @Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
flags, resultReceiver));
}
+ @Override
public void changeInputMethodSubtype(InputMethodSubtype subtype) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
subtype));
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
index 523af04..a797176 100644
--- a/core/java/android/view/InputChannel.java
+++ b/core/java/android/view/InputChannel.java
@@ -78,7 +78,9 @@ public final class InputChannel implements Parcelable {
* Creates a new input channel pair. One channel should be provided to the input
* dispatcher and the other to the application's input queue.
* @param name The descriptive (non-unique) name of the channel pair.
- * @return A pair of input channels. They are symmetric and indistinguishable.
+ * @return A pair of input channels. The first channel is designated as the
+ * server channel and should be used to publish input events. The second channel
+ * is designated as the client channel and should be used to consume input events.
*/
public static InputChannel[] openInputChannelPair(String name) {
if (name == null) {
@@ -123,10 +125,11 @@ public final class InputChannel implements Parcelable {
nativeTransferTo(outParameter);
}
+ @Override
public int describeContents() {
return Parcelable.CONTENTS_FILE_DESCRIPTOR;
}
-
+
public void readFromParcel(Parcel in) {
if (in == null) {
throw new IllegalArgumentException("in must not be null");
@@ -134,7 +137,8 @@ public final class InputChannel implements Parcelable {
nativeReadFromParcel(in);
}
-
+
+ @Override
public void writeToParcel(Parcel out, int flags) {
if (out == null) {
throw new IllegalArgumentException("out must not be null");
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
new file mode 100644
index 0000000..adf63fe
--- /dev/null
+++ b/core/java/android/view/InputEventSender.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import dalvik.system.CloseGuard;
+
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.Log;
+
+/**
+ * Provides a low-level mechanism for an application to send input events.
+ * @hide
+ */
+public abstract class InputEventSender {
+ private static final String TAG = "InputEventSender";
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private int mSenderPtr;
+
+ // We keep references to the input channel and message queue objects here so that
+ // they are not GC'd while the native peer of the receiver is using them.
+ private InputChannel mInputChannel;
+ private MessageQueue mMessageQueue;
+
+ private static native int nativeInit(InputEventSender sender,
+ InputChannel inputChannel, MessageQueue messageQueue);
+ private static native void nativeDispose(int senderPtr);
+ private static native boolean nativeSendKeyEvent(int senderPtr, int seq, KeyEvent event);
+ private static native boolean nativeSendMotionEvent(int senderPtr, int seq, MotionEvent event);
+
+ /**
+ * Creates an input event sender bound to the specified input channel.
+ *
+ * @param inputChannel The input channel.
+ * @param looper The looper to use when invoking callbacks.
+ */
+ public InputEventSender(InputChannel inputChannel, Looper looper) {
+ if (inputChannel == null) {
+ throw new IllegalArgumentException("inputChannel must not be null");
+ }
+ if (looper == null) {
+ throw new IllegalArgumentException("looper must not be null");
+ }
+
+ mInputChannel = inputChannel;
+ mMessageQueue = looper.getQueue();
+ mSenderPtr = nativeInit(this, inputChannel, mMessageQueue);
+
+ mCloseGuard.open("dispose");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Disposes the receiver.
+ */
+ public void dispose() {
+ dispose(false);
+ }
+
+ private void dispose(boolean finalized) {
+ if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
+ mCloseGuard.close();
+ }
+
+ if (mSenderPtr != 0) {
+ nativeDispose(mSenderPtr);
+ mSenderPtr = 0;
+ }
+ mInputChannel = null;
+ mMessageQueue = null;
+ }
+
+ /**
+ * Called when an input event is finished.
+ *
+ * @param seq The input event sequence number.
+ * @param handled True if the input event was handled.
+ */
+ public void onInputEventFinished(int seq, boolean handled) {
+ }
+
+ /**
+ * Sends an input event.
+ * Must be called on the same Looper thread to which the sender is attached.
+ *
+ * @param seq The input event sequence number.
+ * @param event The input event to send.
+ * @return True if the entire event was sent successfully. May return false
+ * if the input channel buffer filled before all samples were dispatched.
+ */
+ public final boolean sendInputEvent(int seq, InputEvent event) {
+ if (event == null) {
+ throw new IllegalArgumentException("event must not be null");
+ }
+ if (mSenderPtr == 0) {
+ Log.w(TAG, "Attempted to send an input event but the input event "
+ + "sender has already been disposed.");
+ return false;
+ }
+
+ if (event instanceof KeyEvent) {
+ return nativeSendKeyEvent(mSenderPtr, seq, (KeyEvent)event);
+ } else {
+ return nativeSendMotionEvent(mSenderPtr, seq, (MotionEvent)event);
+ }
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchInputEventFinished(int seq, boolean handled) {
+ onInputEventFinished(seq, handled);
+ }
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b30dc83..b228704 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2937,7 +2937,6 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_DISPATCH_KEY = 7;
private final static int MSG_DISPATCH_APP_VISIBILITY = 8;
private final static int MSG_DISPATCH_GET_NEW_SURFACE = 9;
- private final static int MSG_IME_FINISHED_EVENT = 10;
private final static int MSG_DISPATCH_KEY_FROM_IME = 11;
private final static int MSG_FINISH_INPUT_CONNECTION = 12;
private final static int MSG_CHECK_FOCUS = 13;
@@ -2975,8 +2974,6 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_DISPATCH_APP_VISIBILITY";
case MSG_DISPATCH_GET_NEW_SURFACE:
return "MSG_DISPATCH_GET_NEW_SURFACE";
- case MSG_IME_FINISHED_EVENT:
- return "MSG_IME_FINISHED_EVENT";
case MSG_DISPATCH_KEY_FROM_IME:
return "MSG_DISPATCH_KEY_FROM_IME";
case MSG_FINISH_INPUT_CONNECTION:
@@ -3018,9 +3015,6 @@ public final class ViewRootImpl implements ViewParent,
info.target.invalidate(info.left, info.top, info.right, info.bottom);
info.recycle();
break;
- case MSG_IME_FINISHED_EVENT:
- handleImeFinishedEvent(msg.arg1, msg.arg2 != 0);
- break;
case MSG_PROCESS_INPUT_EVENTS:
mProcessInputEventsScheduled = false;
doProcessInputEvents();
@@ -3444,26 +3438,15 @@ public final class ViewRootImpl implements ViewParent,
mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
}
+ int result = EVENT_POST_IME;
if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
if (LOCAL_LOGV)
Log.v(TAG, "Dispatching trackball " + event + " to " + mView);
// Dispatch to the IME before propagating down the view hierarchy.
- // The IME will eventually call back into handleImeFinishedEvent.
- if (mLastWasImTarget) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- final int seq = event.getSequenceNumber();
- if (DEBUG_IMF)
- Log.v(TAG, "Sending trackball event to IME: seq="
- + seq + " event=" + event);
- return imm.dispatchTrackballEvent(mView.getContext(), seq, event,
- mInputMethodCallback);
- }
- }
+ result = dispatchImeInputEvent(q);
}
-
- return EVENT_POST_IME;
+ return result;
}
private int deliverTrackballEventPostIme(QueuedInputEvent q) {
@@ -3598,26 +3581,16 @@ public final class ViewRootImpl implements ViewParent,
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
}
+
+ int result = EVENT_POST_IME;
if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
if (LOCAL_LOGV)
Log.v(TAG, "Dispatching generic motion " + event + " to " + mView);
// Dispatch to the IME before propagating down the view hierarchy.
- // The IME will eventually call back into handleImeFinishedEvent.
- if (mLastWasImTarget) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- final int seq = event.getSequenceNumber();
- if (DEBUG_IMF)
- Log.v(TAG, "Sending generic motion event to IME: seq="
- + seq + " event=" + event);
- return imm.dispatchGenericMotionEvent(mView.getContext(), seq, event,
- mInputMethodCallback);
- }
- }
+ result = dispatchImeInputEvent(q);
}
-
- return EVENT_POST_IME;
+ return result;
}
private int deliverGenericMotionEventPostIme(QueuedInputEvent q) {
@@ -3806,6 +3779,7 @@ public final class ViewRootImpl implements ViewParent,
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
+ int result = EVENT_POST_IME;
if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);
@@ -3815,20 +3789,9 @@ public final class ViewRootImpl implements ViewParent,
}
// Dispatch to the IME before propagating down the view hierarchy.
- // The IME will eventually call back into handleImeFinishedEvent.
- if (mLastWasImTarget) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- final int seq = event.getSequenceNumber();
- if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
- + seq + " event=" + event);
- return imm.dispatchKeyEvent(mView.getContext(), seq, event,
- mInputMethodCallback);
- }
- }
+ result = dispatchImeInputEvent(q);
}
-
- return EVENT_POST_IME;
+ return result;
}
private int deliverKeyEventPostIme(QueuedInputEvent q) {
@@ -4317,14 +4280,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
- void dispatchImeFinishedEvent(int seq, boolean handled) {
- Message msg = mHandler.obtainMessage(MSG_IME_FINISHED_EVENT);
- msg.arg1 = seq;
- msg.arg2 = handled ? 1 : 0;
- msg.setAsynchronous(true);
- mHandler.sendMessage(msg);
- }
-
public void dispatchFinishInputConnection(InputConnection connection) {
Message msg = mHandler.obtainMessage(MSG_FINISH_INPUT_CONNECTION, connection);
mHandler.sendMessage(msg);
@@ -4533,6 +4488,21 @@ public final class ViewRootImpl implements ViewParent,
return q;
}
+ int dispatchImeInputEvent(QueuedInputEvent q) {
+ if (mLastWasImTarget) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ final InputEvent event = q.mEvent;
+ final int seq = event.getSequenceNumber();
+ if (DEBUG_IMF)
+ Log.v(TAG, "Sending input event to IME: seq=" + seq + " event=" + event);
+ return imm.dispatchInputEvent(mView.getContext(), seq, event,
+ mInputMethodCallback);
+ }
+ }
+ return EVENT_POST_IME;
+ }
+
void handleImeFinishedEvent(int seq, boolean handled) {
QueuedInputEvent q = mCurrentInputEventHead;
if (q != null && q.mEvent.getSequenceNumber() == seq) {
@@ -5132,7 +5102,7 @@ public final class ViewRootImpl implements ViewParent,
public void finishedEvent(int seq, boolean handled) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.dispatchImeFinishedEvent(seq, handled);
+ viewAncestor.handleImeFinishedEvent(seq, handled);
}
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index e602eb7..4207832 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -19,7 +19,6 @@ package android.view.inputmethod;
import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInputConnectionWrapper;
import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
@@ -40,8 +39,10 @@ import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewRootImpl;
@@ -319,6 +320,8 @@ public final class InputMethodManager {
* The actual instance of the method to make calls on it.
*/
IInputMethodSession mCurMethod;
+ InputChannel mCurChannel;
+ ImeInputEventSender mCurSender;
PendingEvent mPendingEventPool;
int mPendingEventPoolSize;
@@ -363,10 +366,17 @@ public final class InputMethodManager {
if (mBindSequence < 0 || mBindSequence != res.sequence) {
Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
+ ", given seq=" + res.sequence);
+ if (res.channel != null) {
+ res.channel.dispose();
+ }
return;
}
mCurMethod = res.method;
+ if (mCurChannel != null) {
+ mCurChannel.dispose();
+ }
+ mCurChannel = res.channel;
mCurId = res.id;
mBindSequence = res.sequence;
}
@@ -482,10 +492,10 @@ public final class InputMethodManager {
}
final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
- @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
// No need to check for dump permission, since we only give this
// interface to the system.
-
CountDownLatch latch = new CountDownLatch(1);
SomeArgs sargs = SomeArgs.obtain();
sargs.arg1 = fd;
@@ -501,32 +511,29 @@ public final class InputMethodManager {
fout.println("Interrupted waiting for dump");
}
}
-
+
+ @Override
public void setUsingInputMethod(boolean state) {
}
-
+
+ @Override
public void onBindMethod(InputBindResult res) {
mH.sendMessage(mH.obtainMessage(MSG_BIND, res));
}
-
+
+ @Override
public void onUnbindMethod(int sequence) {
mH.sendMessage(mH.obtainMessage(MSG_UNBIND, sequence, 0));
}
-
+
+ @Override
public void setActive(boolean active) {
mH.sendMessage(mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, 0));
}
- };
-
+ };
+
final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
- final IInputMethodCallback mInputMethodCallback = new IInputMethodCallback.Stub() {
- @Override
- public void finishedEvent(int seq, boolean handled) {
- InputMethodManager.this.finishedEvent(seq, handled);
- }
- };
-
InputMethodManager(IInputMethodManager service, Looper looper) {
mService = service;
mMainLooper = looper;
@@ -714,6 +721,14 @@ public final class InputMethodManager {
mBindSequence = -1;
mCurId = null;
mCurMethod = null;
+ if (mCurSender != null) {
+ mCurSender.dispose();
+ mCurSender = null;
+ }
+ if (mCurChannel != null) {
+ mCurChannel.dispose();
+ mCurChannel = null;
+ }
}
/**
@@ -1085,6 +1100,7 @@ public final class InputMethodManager {
// we need to reschedule our work for over there.
if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
vh.post(new Runnable() {
+ @Override
public void run() {
startInputInner(null, 0, 0, 0);
}
@@ -1158,11 +1174,20 @@ public final class InputMethodManager {
if (res.id != null) {
mBindSequence = res.sequence;
mCurMethod = res.method;
+ if (mCurChannel != null) {
+ mCurChannel.dispose();
+ }
+ mCurChannel = res.channel;
mCurId = res.id;
- } else if (mCurMethod == null) {
- // This means there is no input method available.
- if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
- return true;
+ } else {
+ if (res.channel != null) {
+ res.channel.dispose();
+ }
+ if (mCurMethod == null) {
+ // This means there is no input method available.
+ if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
+ return true;
+ }
}
}
if (mCurMethod != null && mCompletions != null) {
@@ -1556,76 +1581,39 @@ public final class InputMethodManager {
throw new RuntimeException(e);
}
}
-
- /**
- * @hide
- */
- public int dispatchKeyEvent(Context context, int seq, KeyEvent key,
- FinishedEventCallback callback) {
- synchronized (mH) {
- if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
-
- if (mCurMethod != null) {
- if (key.getAction() == KeyEvent.ACTION_DOWN
- && key.getKeyCode() == KeyEvent.KEYCODE_SYM
- && key.getRepeatCount() == 0) {
- showInputMethodPickerLocked();
- return ViewRootImpl.EVENT_HANDLED;
- }
- try {
- if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod);
- final long startTime = SystemClock.uptimeMillis();
- enqueuePendingEventLocked(startTime, seq, mCurId, callback);
- mCurMethod.dispatchKeyEvent(seq, key, mInputMethodCallback);
- return ViewRootImpl.EVENT_PENDING_IME;
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e);
- }
- }
- }
- return ViewRootImpl.EVENT_POST_IME;
- }
/**
* @hide
*/
- public int dispatchTrackballEvent(Context context, int seq, MotionEvent motion,
+ public int dispatchInputEvent(Context context, int seq, InputEvent event,
FinishedEventCallback callback) {
synchronized (mH) {
- if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");
+ if (DEBUG) Log.d(TAG, "dispatchInputEvent");
- if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
- try {
- if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
- final long startTime = SystemClock.uptimeMillis();
- enqueuePendingEventLocked(startTime, seq, mCurId, callback);
- mCurMethod.dispatchTrackballEvent(seq, motion, mInputMethodCallback);
- return ViewRootImpl.EVENT_PENDING_IME;
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
+ if (mCurMethod != null) {
+ if (event instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent)event;
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
+ && keyEvent.getKeyCode() == KeyEvent.KEYCODE_SYM
+ && keyEvent.getRepeatCount() == 0) {
+ showInputMethodPickerLocked();
+ return ViewRootImpl.EVENT_HANDLED;
+ }
}
- }
- }
- return ViewRootImpl.EVENT_POST_IME;
- }
- /**
- * @hide
- */
- public int dispatchGenericMotionEvent(Context context, int seq, MotionEvent motion,
- FinishedEventCallback callback) {
- synchronized (mH) {
- if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent");
-
- if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
- try {
- if (DEBUG) Log.v(TAG, "DISPATCH GENERIC MOTION: " + mCurMethod);
- final long startTime = SystemClock.uptimeMillis();
- enqueuePendingEventLocked(startTime, seq, mCurId, callback);
- mCurMethod.dispatchGenericMotionEvent(seq, motion, mInputMethodCallback);
- return ViewRootImpl.EVENT_PENDING_IME;
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId + " dropping generic motion: " + motion, e);
+ if (DEBUG) Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurMethod);
+ final long startTime = SystemClock.uptimeMillis();
+ if (mCurChannel != null) {
+ if (mCurSender == null) {
+ mCurSender = new ImeInputEventSender(mCurChannel, mH.getLooper());
+ }
+ if (mCurSender.sendInputEvent(seq, event)) {
+ enqueuePendingEventLocked(startTime, seq, mCurId, callback);
+ return ViewRootImpl.EVENT_PENDING_IME;
+ } else {
+ Log.w(TAG, "Unable to send input event to IME: "
+ + mCurId + " dropping: " + event);
+ }
}
}
}
@@ -1937,6 +1925,17 @@ public final class InputMethodManager {
public void finishedEvent(int seq, boolean handled);
}
+ private final class ImeInputEventSender extends InputEventSender {
+ public ImeInputEventSender(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEventFinished(int seq, boolean handled) {
+ finishedEvent(seq, handled);
+ }
+ }
+
private static final class PendingEvent {
public PendingEvent mNext;
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index bd947e9..77456da 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -18,6 +18,7 @@ package com.android.internal.view;
import android.os.IBinder;
import android.os.ResultReceiver;
+import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
@@ -41,7 +42,7 @@ oneway interface IInputMethod {
void restartInput(in IInputContext inputContext, in EditorInfo attribute);
- void createSession(IInputSessionCallback callback);
+ void createSession(in InputChannel channel, IInputSessionCallback callback);
void setSessionEnabled(IInputMethodSession session, boolean enabled);
diff --git a/core/java/com/android/internal/view/IInputMethodCallback.aidl b/core/java/com/android/internal/view/IInputMethodCallback.aidl
deleted file mode 100644
index 717a82d..0000000
--- a/core/java/com/android/internal/view/IInputMethodCallback.aidl
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.view;
-
-/**
- * Helper interface for IInputMethod to allow the input method to call back
- * to its client with results from incoming calls.
- * {@hide}
- */
-oneway interface IInputMethodCallback {
- void finishedEvent(int seq, boolean handled);
-}
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
index cdec254..90210ce 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -22,7 +22,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedText;
-import com.android.internal.view.IInputMethodCallback;
/**
* Sub-interface of IInputMethod which is safe to give to client applications.
@@ -40,14 +39,8 @@ oneway interface IInputMethodSession {
void viewClicked(boolean focusChanged);
void updateCursor(in Rect newCursor);
-
- void displayCompletions(in CompletionInfo[] completions);
-
- void dispatchKeyEvent(int seq, in KeyEvent event, IInputMethodCallback callback);
- void dispatchTrackballEvent(int seq, in MotionEvent event, IInputMethodCallback callback);
-
- void dispatchGenericMotionEvent(int seq, in MotionEvent event, IInputMethodCallback callback);
+ void displayCompletions(in CompletionInfo[] completions);
void appPrivateCommand(String action, in Bundle data);
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index 658f098..9143c61 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -18,6 +18,7 @@ package com.android.internal.view;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.InputChannel;
/**
* Bundle of information returned by input method manager about a successful
@@ -30,7 +31,12 @@ public final class InputBindResult implements Parcelable {
* The input method service.
*/
public final IInputMethodSession method;
-
+
+ /**
+ * The input channel used to send input events to this IME.
+ */
+ public final InputChannel channel;
+
/**
* The ID for this input method, as found in InputMethodInfo; null if
* no input method will be bound.
@@ -42,18 +48,25 @@ public final class InputBindResult implements Parcelable {
*/
public final int sequence;
- public InputBindResult(IInputMethodSession _method, String _id, int _sequence) {
+ public InputBindResult(IInputMethodSession _method, InputChannel _channel,
+ String _id, int _sequence) {
method = _method;
+ channel = _channel;
id = _id;
sequence = _sequence;
}
InputBindResult(Parcel source) {
method = IInputMethodSession.Stub.asInterface(source.readStrongBinder());
+ if (source.readInt() != 0) {
+ channel = InputChannel.CREATOR.createFromParcel(source);
+ } else {
+ channel = null;
+ }
id = source.readString();
sequence = source.readInt();
}
-
+
@Override
public String toString() {
return "InputBindResult{" + method + " " + id
@@ -62,12 +75,19 @@ public final class InputBindResult implements Parcelable {
/**
* Used to package this object into a {@link Parcel}.
- *
+ *
* @param dest The {@link Parcel} to be written.
* @param flags The flags used for parceling.
*/
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongInterface(method);
+ if (channel != null) {
+ dest.writeInt(1);
+ channel.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
dest.writeString(id);
dest.writeInt(sequence);
}
@@ -75,17 +95,21 @@ public final class InputBindResult implements Parcelable {
/**
* Used to make this class parcelable.
*/
- public static final Parcelable.Creator<InputBindResult> CREATOR = new Parcelable.Creator<InputBindResult>() {
+ public static final Parcelable.Creator<InputBindResult> CREATOR =
+ new Parcelable.Creator<InputBindResult>() {
+ @Override
public InputBindResult createFromParcel(Parcel source) {
return new InputBindResult(source);
}
+ @Override
public InputBindResult[] newArray(int size) {
return new InputBindResult[size];
}
};
+ @Override
public int describeContents() {
- return 0;
+ return channel != null ? channel.describeContents() : 0;
}
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 1e27be8..66cea9d7 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -44,6 +44,7 @@ LOCAL_SRC_FILES:= \
android_view_InputChannel.cpp \
android_view_InputDevice.cpp \
android_view_InputEventReceiver.cpp \
+ android_view_InputEventSender.cpp \
android_view_KeyEvent.cpp \
android_view_KeyCharacterMap.cpp \
android_view_HardwareRenderer.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 86d3cb6..1300d01 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -163,6 +163,7 @@ extern int register_android_media_RemoteDisplay(JNIEnv *env);
extern int register_android_view_InputChannel(JNIEnv* env);
extern int register_android_view_InputDevice(JNIEnv* env);
extern int register_android_view_InputEventReceiver(JNIEnv* env);
+extern int register_android_view_InputEventSender(JNIEnv* env);
extern int register_android_view_KeyCharacterMap(JNIEnv *env);
extern int register_android_view_KeyEvent(JNIEnv* env);
extern int register_android_view_MotionEvent(JNIEnv* env);
@@ -1195,6 +1196,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_app_NativeActivity),
REG_JNI(register_android_view_InputChannel),
REG_JNI(register_android_view_InputEventReceiver),
+ REG_JNI(register_android_view_InputEventSender),
REG_JNI(register_android_view_KeyEvent),
REG_JNI(register_android_view_MotionEvent),
REG_JNI(register_android_view_PointerIcon),
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
new file mode 100644
index 0000000..bd1d103
--- /dev/null
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputEventSender"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about the dispatch cycle.
+#define DEBUG_DISPATCH_CYCLE 0
+
+
+#include "JNIHelp.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+#include <utils/threads.h>
+#include <utils/KeyedVector.h>
+#include <androidfw/InputTransport.h>
+#include "android_os_MessageQueue.h"
+#include "android_view_InputChannel.h"
+#include "android_view_KeyEvent.h"
+#include "android_view_MotionEvent.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+
+ jmethodID dispatchInputEventFinished;
+} gInputEventSenderClassInfo;
+
+
+class NativeInputEventSender : public LooperCallback {
+public:
+ NativeInputEventSender(JNIEnv* env,
+ jobject senderObj, const sp<InputChannel>& inputChannel,
+ const sp<MessageQueue>& messageQueue);
+
+ status_t initialize();
+ void dispose();
+ status_t sendKeyEvent(uint32_t seq, const KeyEvent* event);
+ status_t sendMotionEvent(uint32_t seq, const MotionEvent* event);
+
+protected:
+ virtual ~NativeInputEventSender();
+
+private:
+ jobject mSenderObjGlobal;
+ InputPublisher mInputPublisher;
+ sp<MessageQueue> mMessageQueue;
+ KeyedVector<uint32_t, uint32_t> mPublishedSeqMap;
+ uint32_t mNextPublishedSeq;
+
+ const char* getInputChannelName() {
+ return mInputPublisher.getChannel()->getName().string();
+ }
+
+ virtual int handleEvent(int receiveFd, int events, void* data);
+ status_t receiveFinishedSignals(JNIEnv* env);
+};
+
+
+NativeInputEventSender::NativeInputEventSender(JNIEnv* env,
+ jobject senderObj, const sp<InputChannel>& inputChannel,
+ const sp<MessageQueue>& messageQueue) :
+ mSenderObjGlobal(env->NewGlobalRef(senderObj)),
+ mInputPublisher(inputChannel), mMessageQueue(messageQueue),
+ mNextPublishedSeq(0) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Initializing input event sender.", getInputChannelName());
+#endif
+}
+
+NativeInputEventSender::~NativeInputEventSender() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mSenderObjGlobal);
+}
+
+status_t NativeInputEventSender::initialize() {
+ int receiveFd = mInputPublisher.getChannel()->getFd();
+ mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
+ return OK;
+}
+
+void NativeInputEventSender::dispose() {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Disposing input event sender.", getInputChannelName());
+#endif
+
+ mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
+}
+
+status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Sending key event, seq=%u.", getInputChannelName(), seq);
+#endif
+
+ uint32_t publishedSeq = mNextPublishedSeq++;
+ status_t status = mInputPublisher.publishKeyEvent(publishedSeq,
+ event->getDeviceId(), event->getSource(), event->getAction(), event->getFlags(),
+ event->getKeyCode(), event->getScanCode(), event->getMetaState(),
+ event->getRepeatCount(), event->getDownTime(), event->getEventTime());
+ if (status) {
+ ALOGW("Failed to send key event on channel '%s'. status=%d",
+ getInputChannelName(), status);
+ return status;
+ }
+ mPublishedSeqMap.add(publishedSeq, seq);
+ return OK;
+}
+
+status_t NativeInputEventSender::sendMotionEvent(uint32_t seq, const MotionEvent* event) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Sending motion event, seq=%u.", getInputChannelName(), seq);
+#endif
+
+ uint32_t publishedSeq;
+ for (size_t i = 0; i <= event->getHistorySize(); i++) {
+ publishedSeq = mNextPublishedSeq++;
+ status_t status = mInputPublisher.publishMotionEvent(publishedSeq,
+ event->getDeviceId(), event->getSource(), event->getAction(), event->getFlags(),
+ event->getEdgeFlags(), event->getMetaState(), event->getButtonState(),
+ event->getXOffset(), event->getYOffset(),
+ event->getXPrecision(), event->getYPrecision(),
+ event->getDownTime(), event->getHistoricalEventTime(i),
+ event->getPointerCount(), event->getPointerProperties(),
+ event->getHistoricalRawPointerCoords(0, i));
+ if (status) {
+ ALOGW("Failed to send motion event sample on channel '%s'. status=%d",
+ getInputChannelName(), status);
+ return status;
+ }
+ }
+ mPublishedSeqMap.add(publishedSeq, seq);
+ return OK;
+}
+
+int NativeInputEventSender::handleEvent(int receiveFd, int events, void* data) {
+ if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
+ ALOGE("channel '%s' ~ Consumer closed input channel or an error occurred. "
+ "events=0x%x", getInputChannelName(), events);
+ return 0; // remove the callback
+ }
+
+ if (!(events & ALOOPER_EVENT_INPUT)) {
+ ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
+ "events=0x%x", getInputChannelName(), events);
+ return 1;
+ }
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ status_t status = receiveFinishedSignals(env);
+ mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
+ return status == OK || status == NO_MEMORY ? 1 : 0;
+}
+
+status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName());
+#endif
+
+ bool skipCallbacks = false;
+ for (;;) {
+ uint32_t publishedSeq;
+ bool handled;
+ status_t status = mInputPublisher.receiveFinishedSignal(&publishedSeq, &handled);
+ if (status) {
+ if (status == WOULD_BLOCK) {
+ return OK;
+ }
+ ALOGE("channel '%s' ~ Failed to consume finished signals. status=%d",
+ getInputChannelName(), status);
+ return status;
+ }
+
+ ssize_t index = mPublishedSeqMap.indexOfKey(publishedSeq);
+ if (index >= 0) {
+ uint32_t seq = mPublishedSeqMap.valueAt(index);
+ mPublishedSeqMap.removeItemsAt(index);
+
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, "
+ "pendingEvents=%u.",
+ getInputChannelName(), seq, handled ? "true" : "false",
+ mPublishedSeqMap.size());
+#endif
+
+ if (!skipCallbacks) {
+ env->CallVoidMethod(mSenderObjGlobal,
+ gInputEventSenderClassInfo.dispatchInputEventFinished,
+ jint(seq), jboolean(handled));
+ if (env->ExceptionCheck()) {
+ ALOGE("Exception dispatching finished signal.");
+ skipCallbacks = true;
+ }
+ }
+ }
+ }
+}
+
+
+static jint nativeInit(JNIEnv* env, jclass clazz, jobject senderObj,
+ jobject inputChannelObj, jobject messageQueueObj) {
+ sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+ inputChannelObj);
+ if (inputChannel == NULL) {
+ jniThrowRuntimeException(env, "InputChannel is not initialized.");
+ return 0;
+ }
+
+ sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
+ if (messageQueue == NULL) {
+ jniThrowRuntimeException(env, "MessageQueue is not initialized.");
+ return 0;
+ }
+
+ sp<NativeInputEventSender> sender = new NativeInputEventSender(env,
+ senderObj, inputChannel, messageQueue);
+ status_t status = sender->initialize();
+ if (status) {
+ String8 message;
+ message.appendFormat("Failed to initialize input event sender. status=%d", status);
+ jniThrowRuntimeException(env, message.string());
+ return 0;
+ }
+
+ sender->incStrong(gInputEventSenderClassInfo.clazz); // retain a reference for the object
+ return reinterpret_cast<jint>(sender.get());
+}
+
+static void nativeDispose(JNIEnv* env, jclass clazz, jint senderPtr) {
+ sp<NativeInputEventSender> sender =
+ reinterpret_cast<NativeInputEventSender*>(senderPtr);
+ sender->dispose();
+ sender->decStrong(gInputEventSenderClassInfo.clazz); // drop reference held by the object
+}
+
+static jboolean nativeSendKeyEvent(JNIEnv* env, jclass clazz, jint senderPtr,
+ jint seq, jobject eventObj) {
+ sp<NativeInputEventSender> sender =
+ reinterpret_cast<NativeInputEventSender*>(senderPtr);
+ KeyEvent event;
+ android_view_KeyEvent_toNative(env, eventObj, &event);
+ status_t status = sender->sendKeyEvent(seq, &event);
+ return !status;
+}
+
+static jboolean nativeSendMotionEvent(JNIEnv* env, jclass clazz, jint senderPtr,
+ jint seq, jobject eventObj) {
+ sp<NativeInputEventSender> sender =
+ reinterpret_cast<NativeInputEventSender*>(senderPtr);
+ MotionEvent* event = android_view_MotionEvent_getNativePtr(env, eventObj);
+ status_t status = sender->sendMotionEvent(seq, event);
+ return !status;
+}
+
+
+static JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "nativeInit",
+ "(Landroid/view/InputEventSender;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I",
+ (void*)nativeInit },
+ { "nativeDispose", "(I)V",
+ (void*)nativeDispose },
+ { "nativeSendKeyEvent", "(IILandroid/view/KeyEvent;)Z",
+ (void*)nativeSendKeyEvent },
+ { "nativeSendMotionEvent", "(IILandroid/view/MotionEvent;)Z",
+ (void*)nativeSendMotionEvent },
+};
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className); \
+ var = jclass(env->NewGlobalRef(var));
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+ var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+int register_android_view_InputEventSender(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "android/view/InputEventSender",
+ gMethods, NELEM(gMethods));
+ LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ FIND_CLASS(gInputEventSenderClassInfo.clazz, "android/view/InputEventSender");
+
+ GET_METHOD_ID(gInputEventSenderClassInfo.dispatchInputEventFinished,
+ gInputEventSenderClassInfo.clazz,
+ "dispatchInputEventFinished", "(IZ)V");
+ return 0;
+}
+
+} // namespace android
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index dd081a1..2d53023 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -88,6 +88,7 @@ import android.util.Printer;
import android.util.Slog;
import android.util.Xml;
import android.view.IWindowManager;
+import android.view.InputChannel;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -170,7 +171,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private final HardKeyboardListener mHardKeyboardListener;
private final WindowManagerService mWindowManagerService;
- final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
+ final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1);
// All known input methods. mMethodMap also serves as the global
// lock for this class.
@@ -202,7 +203,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
class SessionState {
final ClientState client;
final IInputMethod method;
- final IInputMethodSession session;
+
+ IInputMethodSession session;
+ InputChannel channel;
@Override
public String toString() {
@@ -211,18 +214,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
System.identityHashCode(method))
+ " session " + Integer.toHexString(
System.identityHashCode(session))
+ + " channel " + channel
+ "}";
}
SessionState(ClientState _client, IInputMethod _method,
- IInputMethodSession _session) {
+ IInputMethodSession _session, InputChannel _channel) {
client = _client;
method = _method;
session = _session;
+ channel = _channel;
}
}
- class ClientState {
+ static final class ClientState {
final IInputMethodClient client;
final IInputContext inputContext;
final int uid;
@@ -555,18 +560,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- private static class MethodCallback extends IInputSessionCallback.Stub {
- private final IInputMethod mMethod;
+ private static final class MethodCallback extends IInputSessionCallback.Stub {
private final InputMethodManagerService mParentIMMS;
+ private final IInputMethod mMethod;
+ private final InputChannel mChannel;
- MethodCallback(final IInputMethod method, final InputMethodManagerService imms) {
- mMethod = method;
+ MethodCallback(InputMethodManagerService imms, IInputMethod method,
+ InputChannel channel) {
mParentIMMS = imms;
+ mMethod = method;
+ mChannel = channel;
}
@Override
- public void sessionCreated(IInputMethodSession session) throws RemoteException {
- mParentIMMS.onSessionCreated(mMethod, session);
+ public void sessionCreated(IInputMethodSession session) {
+ mParentIMMS.onSessionCreated(mMethod, session, mChannel);
}
}
@@ -984,7 +992,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return;
}
synchronized (mMethodMap) {
- mClients.remove(client.asBinder());
+ ClientState cs = mClients.remove(client.asBinder());
+ if (cs != null) {
+ clearClientSessionLocked(cs);
+ }
}
}
@@ -1059,7 +1070,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
showCurrentInputLocked(getAppShowFlags(), null);
}
- return new InputBindResult(session.session, mCurId, mCurSeq);
+ return new InputBindResult(session.session, session.channel, mCurId, mCurSeq);
}
InputBindResult startInputLocked(IInputMethodClient client,
@@ -1137,16 +1148,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
if (mHaveConnection) {
if (mCurMethod != null) {
- if (!cs.sessionRequested) {
- cs.sessionRequested = true;
- if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
- MSG_CREATE_SESSION, mCurMethod,
- new MethodCallback(mCurMethod, this)));
- }
// Return to client, and we will get back with it when
// we have had a session made for it.
- return new InputBindResult(null, mCurId, mCurSeq);
+ requestClientSessionLocked(cs);
+ return new InputBindResult(null, null, mCurId, mCurSeq);
} else if (SystemClock.uptimeMillis()
< (mLastBindTime+TIME_TO_RECONNECT)) {
// In this case we have connected to the service, but
@@ -1156,7 +1161,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// we can report back. If it has been too long, we want
// to fall through so we can try a disconnect/reconnect
// to see if we can get back in touch with the service.
- return new InputBindResult(null, mCurId, mCurSeq);
+ return new InputBindResult(null, null, mCurId, mCurSeq);
} else {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
@@ -1175,7 +1180,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (!mSystemReady) {
// If the system is not yet ready, we shouldn't be running third
// party code.
- return new InputBindResult(null, mCurMethodId, mCurSeq);
+ return new InputBindResult(null, null, mCurMethodId, mCurSeq);
}
InputMethodInfo info = mMethodMap.get(mCurMethodId);
@@ -1203,7 +1208,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
WindowManager.LayoutParams.TYPE_INPUT_METHOD);
} catch (RemoteException e) {
}
- return new InputBindResult(null, mCurId, mCurSeq);
+ return new InputBindResult(null, null, mCurId, mCurSeq);
} else {
mCurIntent = null;
Slog.w(TAG, "Failure connecting to input method service: "
@@ -1246,32 +1251,34 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
if (mCurClient != null) {
- if (DEBUG) Slog.v(TAG, "Creating first session while with client "
- + mCurClient);
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
- MSG_CREATE_SESSION, mCurMethod,
- new MethodCallback(mCurMethod, this)));
+ clearClientSessionLocked(mCurClient);
+ requestClientSessionLocked(mCurClient);
}
}
}
}
- void onSessionCreated(IInputMethod method, IInputMethodSession session) {
+ void onSessionCreated(IInputMethod method, IInputMethodSession session,
+ InputChannel channel) {
synchronized (mMethodMap) {
if (mCurMethod != null && method != null
&& mCurMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
+ clearClientSessionLocked(mCurClient);
mCurClient.curSession = new SessionState(mCurClient,
- method, session);
- mCurClient.sessionRequested = false;
+ method, session, channel);
InputBindResult res = attachNewInputLocked(true);
if (res.method != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
MSG_BIND_METHOD, mCurClient.client, res));
}
+ return;
}
}
}
+
+ // Session abandoned. Close its associated input channel.
+ channel.dispose();
}
void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) {
@@ -1306,14 +1313,38 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
}
}
-
- private void finishSession(SessionState sessionState) {
- if (sessionState != null && sessionState.session != null) {
- try {
- sessionState.session.finishSession();
- } catch (RemoteException e) {
- Slog.w(TAG, "Session failed to close due to remote exception", e);
- setImeWindowVisibilityStatusHiddenLocked();
+
+ void requestClientSessionLocked(ClientState cs) {
+ if (!cs.sessionRequested) {
+ if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
+ InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
+ cs.sessionRequested = true;
+ executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
+ MSG_CREATE_SESSION, mCurMethod, channels[1],
+ new MethodCallback(this, mCurMethod, channels[0])));
+ }
+ }
+
+ void clearClientSessionLocked(ClientState cs) {
+ finishSessionLocked(cs.curSession);
+ cs.curSession = null;
+ cs.sessionRequested = false;
+ }
+
+ private void finishSessionLocked(SessionState sessionState) {
+ if (sessionState != null) {
+ if (sessionState.session != null) {
+ try {
+ sessionState.session.finishSession();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Session failed to close due to remote exception", e);
+ setImeWindowVisibilityStatusHiddenLocked();
+ }
+ sessionState.session = null;
+ }
+ if (sessionState.channel != null) {
+ sessionState.channel.dispose();
+ sessionState.channel = null;
}
}
}
@@ -1321,12 +1352,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
void clearCurMethodLocked() {
if (mCurMethod != null) {
for (ClientState cs : mClients.values()) {
- cs.sessionRequested = false;
- finishSession(cs.curSession);
- cs.curSession = null;
+ clearClientSessionLocked(cs);
}
- finishSession(mEnabledSession);
+ finishSessionLocked(mEnabledSession);
mEnabledSession = null;
mCurMethod = null;
}
@@ -2325,15 +2354,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
args.recycle();
return true;
- case MSG_CREATE_SESSION:
+ case MSG_CREATE_SESSION: {
args = (SomeArgs)msg.obj;
+ InputChannel channel = (InputChannel)args.arg2;
try {
- ((IInputMethod)args.arg1).createSession(
- (IInputSessionCallback)args.arg2);
+ ((IInputMethod)args.arg1).createSession(channel,
+ (IInputSessionCallback)args.arg3);
} catch (RemoteException e) {
+ } finally {
+ if (channel != null) {
+ channel.dispose();
+ }
}
args.recycle();
return true;
+ }
// ---------------------------------------------------------
case MSG_START_INPUT: