Merge "Use input transport for communications between app and IME." into jb-mr2-dev

This commit is contained in:
Jeff Brown
2013-03-27 02:43:54 +00:00
committed by Android (Google) Code Review
18 changed files with 823 additions and 325 deletions

View File

@@ -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 \

View File

@@ -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
# ************************************************

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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));
}
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);
}
}
}
}

View File

@@ -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));

View File

@@ -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");

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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,
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 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;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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 appPrivateCommand(String action, in Bundle data);

View File

@@ -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;
}
}

View File

@@ -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 \

View File

@@ -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),

View File

@@ -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

View File

@@ -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: