diff options
Diffstat (limited to 'core/java/android/view/inputmethod')
| -rw-r--r-- | core/java/android/view/inputmethod/InputMethodManager.java | 329 |
1 files changed, 173 insertions, 156 deletions
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 855b6d4..4df4734 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -34,9 +34,11 @@ import android.os.Message; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; -import android.os.SystemClock; +import android.os.Trace; import android.text.style.SuggestionSpan; import android.util.Log; +import android.util.Pools.Pool; +import android.util.Pools.SimplePool; import android.util.PrintWriterPrinter; import android.util.Printer; import android.view.InputChannel; @@ -45,6 +47,7 @@ import android.view.InputEventSender; import android.view.KeyEvent; import android.view.View; import android.view.ViewRootImpl; +import android.util.SparseArray; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -200,8 +203,9 @@ public final class InputMethodManager { static final boolean DEBUG = false; static final String TAG = "InputMethodManager"; - static final Object mInstanceSync = new Object(); - static InputMethodManager mInstance; + static final String PENDING_EVENT_COUNTER = "aq:imm"; + + static InputMethodManager sInstance; /** * @hide Flag for IInputMethodManager.windowGainedFocus: a view in @@ -232,7 +236,14 @@ public final class InputMethodManager { */ static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500; - private static final int MAX_PENDING_EVENT_POOL_SIZE = 4; + /** @hide */ + public static final int DISPATCH_IN_PROGRESS = -1; + + /** @hide */ + public static final int DISPATCH_NOT_HANDLED = 0; + + /** @hide */ + public static final int DISPATCH_HANDLED = 1; final IInputMethodManager mService; final Looper mMainLooper; @@ -323,10 +334,8 @@ public final class InputMethodManager { InputChannel mCurChannel; ImeInputEventSender mCurSender; - PendingEvent mPendingEventPool; - int mPendingEventPoolSize; - PendingEvent mPendingEventHead; - PendingEvent mPendingEventTail; + final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20); + final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20); // ----------------------------------------------------------- @@ -334,8 +343,10 @@ public final class InputMethodManager { static final int MSG_BIND = 2; static final int MSG_UNBIND = 3; static final int MSG_SET_ACTIVE = 4; - static final int MSG_EVENT_TIMEOUT = 5; - + static final int MSG_SEND_INPUT_EVENT = 5; + static final int MSG_TIMEOUT_INPUT_EVENT = 6; + static final int MSG_FLUSH_INPUT_EVENT = 7; + class H extends Handler { H(Looper looper) { super(looper, null, true); @@ -453,15 +464,16 @@ public final class InputMethodManager { } return; } - case MSG_EVENT_TIMEOUT: { - // Even though the message contains both the sequence number - // and the PendingEvent object itself, we only pass the - // sequence number to the timeoutEvent function because it's - // possible for the PendingEvent object to be dequeued and - // recycled concurrently. To avoid a possible race, we make - // a point of always looking up the PendingEvent within the - // queue given only the sequence number of the event. - timeoutEvent(msg.arg1); + case MSG_SEND_INPUT_EVENT: { + sendInputEventAndReportResultOnMainLooper((PendingEvent)msg.obj); + return; + } + case MSG_TIMEOUT_INPUT_EVENT: { + finishedInputEvent(msg.arg1, false, true); + return; + } + case MSG_FLUSH_INPUT_EVENT: { + finishedInputEvent(msg.arg1, false, false); return; } } @@ -538,10 +550,6 @@ public final class InputMethodManager { mH = new H(looper); mIInputContext = new ControlledInputConnectionWrapper(looper, mDummyInputConnection, this); - - if (mInstance == null) { - mInstance = this; - } } /** @@ -549,25 +557,15 @@ public final class InputMethodManager { * doesn't already exist. * @hide */ - static public InputMethodManager getInstance(Context context) { - return getInstance(context.getMainLooper()); - } - - /** - * Internally, the input method manager can't be context-dependent, so - * we have this here for the places that need it. - * @hide - */ - static public InputMethodManager getInstance(Looper mainLooper) { - synchronized (mInstanceSync) { - if (mInstance != null) { - return mInstance; + public static InputMethodManager getInstance() { + synchronized (InputMethodManager.class) { + if (sInstance == null) { + IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); + IInputMethodManager service = IInputMethodManager.Stub.asInterface(b); + sInstance = new InputMethodManager(service, Looper.getMainLooper()); } - IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); - IInputMethodManager service = IInputMethodManager.Stub.asInterface(b); - mInstance = new InputMethodManager(service, mainLooper); + return sInstance; } - return mInstance; } /** @@ -575,8 +573,8 @@ public final class InputMethodManager { * if it exists. * @hide */ - static public InputMethodManager peekInstance() { - return mInstance; + public static InputMethodManager peekInstance() { + return sInstance; } /** @hide */ @@ -1585,13 +1583,18 @@ public final class InputMethodManager { } /** + * Dispatches an input event to the IME. + * + * Returns {@link #DISPATCH_HANDLED} if the event was handled. + * Returns {@link #DISPATCH_NOT_HANDLED} if the event was not handled. + * Returns {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the + * callback will be invoked later. + * * @hide */ - public int dispatchInputEvent(Context context, int seq, InputEvent event, - FinishedEventCallback callback) { + public int dispatchInputEvent(InputEvent event, Object token, + FinishedInputEventCallback callback, Handler handler) { synchronized (mH) { - if (DEBUG) Log.d(TAG, "dispatchInputEvent"); - if (mCurMethod != null) { if (event instanceof KeyEvent) { KeyEvent keyEvent = (KeyEvent)event; @@ -1599,142 +1602,138 @@ public final class InputMethodManager { && keyEvent.getKeyCode() == KeyEvent.KEYCODE_SYM && keyEvent.getRepeatCount() == 0) { showInputMethodPickerLocked(); - return ViewRootImpl.EVENT_HANDLED; + return DISPATCH_HANDLED; } } 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); - } + + PendingEvent p = obtainPendingEventLocked( + event, token, mCurId, callback, handler); + if (mMainLooper.isCurrentThread()) { + // Already running on the IMM thread so we can send the event immediately. + return sendInputEventOnMainLooperLocked(p); } + + // Post the event to the IMM thread. + Message msg = mH.obtainMessage(MSG_SEND_INPUT_EVENT, p); + msg.setAsynchronous(true); + mH.sendMessage(msg); + return DISPATCH_IN_PROGRESS; } } - return ViewRootImpl.EVENT_POST_IME; + return DISPATCH_NOT_HANDLED; } - void finishedEvent(int seq, boolean handled) { - final FinishedEventCallback callback; + // Must be called on the main looper + void sendInputEventAndReportResultOnMainLooper(PendingEvent p) { + final boolean handled; synchronized (mH) { - PendingEvent p = dequeuePendingEventLocked(seq); - if (p == null) { - return; // spurious, event already finished or timed out + int result = sendInputEventOnMainLooperLocked(p); + if (result == DISPATCH_IN_PROGRESS) { + return; } - mH.removeMessages(MSG_EVENT_TIMEOUT, p); - callback = p.mCallback; - recyclePendingEventLocked(p); + + handled = (result == DISPATCH_HANDLED); + } + + invokeFinishedInputEventCallback(p, handled); + } + + // Must be called on the main looper + int sendInputEventOnMainLooperLocked(PendingEvent p) { + if (mCurChannel != null) { + if (mCurSender == null) { + mCurSender = new ImeInputEventSender(mCurChannel, mH.getLooper()); + } + + final InputEvent event = p.mEvent; + final int seq = event.getSequenceNumber(); + if (mCurSender.sendInputEvent(seq, event)) { + mPendingEvents.put(seq, p); + Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER, + mPendingEvents.size()); + + Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, p); + msg.setAsynchronous(true); + mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT); + return DISPATCH_IN_PROGRESS; + } + + Log.w(TAG, "Unable to send input event to IME: " + + mCurId + " dropping: " + event); } - callback.finishedEvent(seq, handled); + return DISPATCH_NOT_HANDLED; } - void timeoutEvent(int seq) { - final FinishedEventCallback callback; + void finishedInputEvent(int seq, boolean handled, boolean timeout) { + final PendingEvent p; synchronized (mH) { - PendingEvent p = dequeuePendingEventLocked(seq); - if (p == null) { + int index = mPendingEvents.indexOfKey(seq); + if (index < 0) { return; // spurious, event already finished or timed out } - long delay = SystemClock.uptimeMillis() - p.mStartTime; - Log.w(TAG, "Timeout waiting for IME to handle input event after " - + delay + "ms: " + p.mInputMethodId); - callback = p.mCallback; - recyclePendingEventLocked(p); + + p = mPendingEvents.valueAt(index); + mPendingEvents.removeAt(index); + Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER, mPendingEvents.size()); + + if (timeout) { + Log.w(TAG, "Timeout waiting for IME to handle input event after " + + INPUT_METHOD_NOT_RESPONDING_TIMEOUT + " ms: " + p.mInputMethodId); + } else { + mH.removeMessages(MSG_TIMEOUT_INPUT_EVENT, p); + } } - callback.finishedEvent(seq, false); + + invokeFinishedInputEventCallback(p, handled); } - private void enqueuePendingEventLocked( - long startTime, int seq, String inputMethodId, FinishedEventCallback callback) { - PendingEvent p = obtainPendingEventLocked(startTime, seq, inputMethodId, callback); - if (mPendingEventTail != null) { - mPendingEventTail.mNext = p; - mPendingEventTail = p; + // Assumes the event has already been removed from the queue. + void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { + p.mHandled = handled; + if (p.mHandler.getLooper().isCurrentThread()) { + // Already running on the callback handler thread so we can send the + // callback immediately. + p.run(); } else { - mPendingEventHead = p; - mPendingEventTail = p; + // Post the event to the callback handler thread. + // In this case, the callback will be responsible for recycling the event. + Message msg = Message.obtain(p.mHandler, p); + msg.setAsynchronous(true); + msg.sendToTarget(); } - - Message msg = mH.obtainMessage(MSG_EVENT_TIMEOUT, seq, 0, p); - msg.setAsynchronous(true); - mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT); } - private PendingEvent dequeuePendingEventLocked(int seq) { - PendingEvent p = mPendingEventHead; - if (p == null) { - return null; - } - if (p.mSeq == seq) { - mPendingEventHead = p.mNext; - if (mPendingEventHead == null) { - mPendingEventTail = null; - } - } else { - PendingEvent prev; - do { - prev = p; - p = p.mNext; - if (p == null) { - return null; - } - } while (p.mSeq != seq); - prev.mNext = p.mNext; - if (mPendingEventTail == p) { - mPendingEventTail = prev; - } + private void flushPendingEventsLocked() { + mH.removeMessages(MSG_FLUSH_INPUT_EVENT); + + final int count = mPendingEvents.size(); + for (int i = 0; i < count; i++) { + int seq = mPendingEvents.keyAt(i); + Message msg = mH.obtainMessage(MSG_FLUSH_INPUT_EVENT, seq, 0); + msg.setAsynchronous(true); + msg.sendToTarget(); } - p.mNext = null; - return p; } - private PendingEvent obtainPendingEventLocked( - long startTime, int seq, String inputMethodId, FinishedEventCallback callback) { - PendingEvent p = mPendingEventPool; - if (p != null) { - mPendingEventPoolSize -= 1; - mPendingEventPool = p.mNext; - p.mNext = null; - } else { + private PendingEvent obtainPendingEventLocked(InputEvent event, Object token, + String inputMethodId, FinishedInputEventCallback callback, Handler handler) { + PendingEvent p = mPendingEventPool.acquire(); + if (p == null) { p = new PendingEvent(); } - - p.mStartTime = startTime; - p.mSeq = seq; + p.mEvent = event; + p.mToken = token; p.mInputMethodId = inputMethodId; p.mCallback = callback; + p.mHandler = handler; return p; } private void recyclePendingEventLocked(PendingEvent p) { - p.mInputMethodId = null; - p.mCallback = null; - - if (mPendingEventPoolSize < MAX_PENDING_EVENT_POOL_SIZE) { - mPendingEventPoolSize += 1; - p.mNext = mPendingEventPool; - mPendingEventPool = p; - } - } - - private void flushPendingEventsLocked() { - mH.removeMessages(MSG_EVENT_TIMEOUT); - - PendingEvent p = mPendingEventHead; - while (p != null) { - Message msg = mH.obtainMessage(MSG_EVENT_TIMEOUT, p.mSeq, 0, p); - msg.setAsynchronous(true); - mH.sendMessage(msg); - p = p.mNext; - } + p.recycle(); + mPendingEventPool.release(p); } public void showInputMethodPicker() { @@ -1946,8 +1945,8 @@ public final class InputMethodManager { * the IME has been finished. * @hide */ - public interface FinishedEventCallback { - public void finishedEvent(int seq, boolean handled); + public interface FinishedInputEventCallback { + public void onFinishedInputEvent(Object token, boolean handled); } private final class ImeInputEventSender extends InputEventSender { @@ -1957,16 +1956,34 @@ public final class InputMethodManager { @Override public void onInputEventFinished(int seq, boolean handled) { - finishedEvent(seq, handled); + finishedInputEvent(seq, handled, false); } } - private static final class PendingEvent { - public PendingEvent mNext; - - public long mStartTime; - public int mSeq; + private final class PendingEvent implements Runnable { + public InputEvent mEvent; + public Object mToken; public String mInputMethodId; - public FinishedEventCallback mCallback; + public FinishedInputEventCallback mCallback; + public Handler mHandler; + public boolean mHandled; + + public void recycle() { + mEvent = null; + mToken = null; + mInputMethodId = null; + mCallback = null; + mHandler = null; + mHandled = false; + } + + @Override + public void run() { + mCallback.onFinishedInputEvent(mToken, mHandled); + + synchronized (mH) { + recyclePendingEventLocked(this); + } + } } } |
