Merge "Use input transport for communications between app and IME." into jb-mr2-dev
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
# ************************************************
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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");
|
||||
|
||||
140
core/java/android/view/InputEventSender.java
Normal file
140
core/java/android/view/InputEventSender.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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),
|
||||
|
||||
307
core/jni/android_view_InputEventSender.cpp
Normal file
307
core/jni/android_view_InputEventSender.cpp
Normal 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
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user