Merge "Use input transport for communications between app and IME." into jb-mr2-dev
diff --git a/Android.mk b/Android.mk
index 8090448..2ad7a72 100644
--- a/Android.mk
+++ b/Android.mk
@@ -195,7 +195,6 @@
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 @@
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 $(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 @@
}
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 @@
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 @@
* 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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
return;
}
case DO_FINISH_SESSION: {
- mInputMethodSession = null;
+ doFinishSession();
return;
}
case DO_VIEW_CLICKED: {
@@ -153,37 +123,37 @@
}
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 @@
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));
- }
-
- public void appPrivateCommand(String action, Bundle data) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
- }
-
- public void toggleSoftInput(int showFlags, int hideFlags) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
+ 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));
+ }
+
+ @Override
+ public void toggleSoftInput(int showFlags, int 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.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 @@
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 @@
}
// 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 @@
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 @@
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 @@
}
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 @@
}
}
+ @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 @@
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 @@
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 @@
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 @@
* 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 @@
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 @@
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 401db1f..7b34ce1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2937,7 +2937,6 @@
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;
@@ -2977,8 +2976,6 @@
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:
@@ -3024,9 +3021,6 @@
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();
@@ -3462,26 +3456,15 @@
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) {
@@ -3616,26 +3599,16 @@
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) {
@@ -3834,6 +3807,7 @@
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);
@@ -3843,20 +3817,9 @@
}
// 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) {
@@ -4345,14 +4308,6 @@
}
}
- 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);
@@ -4561,6 +4516,21 @@
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) {
@@ -5160,7 +5130,7 @@
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 @@
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.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 @@
* 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 @@
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 @@
}
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 @@
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 @@
mBindSequence = -1;
mCurId = null;
mCurMethod = null;
+ if (mCurSender != null) {
+ mCurSender.dispose();
+ mCurSender = null;
+ }
+ if (mCurChannel != null) {
+ mCurChannel.dispose();
+ mCurChannel = null;
+ }
}
/**
@@ -1085,6 +1100,7 @@
// 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 @@
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 @@
throw new RuntimeException(e);
}
}
-
+
/**
* @hide
*/
- public int dispatchKeyEvent(Context context, int seq, KeyEvent key,
+ public int dispatchInputEvent(Context context, int seq, InputEvent event,
FinishedEventCallback callback) {
synchronized (mH) {
- if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
+ if (DEBUG) Log.d(TAG, "dispatchInputEvent");
if (mCurMethod != null) {
- if (key.getAction() == KeyEvent.ACTION_DOWN
- && key.getKeyCode() == KeyEvent.KEYCODE_SYM
- && key.getRepeatCount() == 0) {
- showInputMethodPickerLocked();
- return ViewRootImpl.EVENT_HANDLED;
+ 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;
+ }
}
- 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,
- FinishedEventCallback callback) {
- synchronized (mH) {
- if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");
-
- 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);
- }
- }
- }
- 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 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 @@
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 @@
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.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 @@
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 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 @@
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 @@
* 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 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 @@
/**
* 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 @@
/**
* 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 @@
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_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 @@
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.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 @@
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 @@
class SessionState {
final ClientState client;
final IInputMethod method;
- final IInputMethodSession session;
+
+ IInputMethodSession session;
+ InputChannel channel;
@Override
public String toString() {
@@ -211,18 +214,20 @@
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 @@
}
}
- 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 @@
return;
}
synchronized (mMethodMap) {
- mClients.remove(client.asBinder());
+ ClientState cs = mClients.remove(client.asBinder());
+ if (cs != null) {
+ clearClientSessionLocked(cs);
+ }
}
}
@@ -1059,7 +1070,7 @@
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 @@
}
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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: