diff options
18 files changed, 819 insertions, 321 deletions
@@ -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 401db1f..7b34ce1 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; @@ -2977,8 +2976,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: @@ -3024,9 +3021,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(); @@ -3462,26 +3456,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) { @@ -3616,26 +3599,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) { @@ -3834,6 +3807,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); @@ -3843,20 +3817,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) { @@ -4345,14 +4308,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); @@ -4561,6 +4516,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) { @@ -5160,7 +5130,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: |