diff options
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/app/ContextImpl.java | 6 | ||||
| -rw-r--r-- | core/java/android/app/NativeActivity.java | 39 | ||||
| -rw-r--r-- | core/java/android/app/WallpaperManager.java | 6 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 6 | ||||
| -rw-r--r-- | core/java/android/os/LatencyTimer.java | 94 | ||||
| -rw-r--r-- | core/java/android/os/Looper.java | 8 | ||||
| -rw-r--r-- | core/java/android/service/wallpaper/WallpaperService.java | 2 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 1429 | ||||
| -rw-r--r-- | core/java/android/view/WindowManagerGlobal.java | 6 | ||||
| -rw-r--r-- | core/java/android/view/inputmethod/InputMethodManager.java | 329 |
10 files changed, 1020 insertions, 905 deletions
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 9bf8830..19d495b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -371,9 +371,9 @@ class ContextImpl extends Context { return new DisplayManager(ctx.getOuterContext()); }}); - registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() { - public Object createService(ContextImpl ctx) { - return InputMethodManager.getInstance(ctx); + registerService(INPUT_METHOD_SERVICE, new StaticServiceFetcher() { + public Object createStaticService() { + return InputMethodManager.getInstance(); }}); registerService(TEXT_SERVICES_MANAGER_SERVICE, new ServiceFetcher() { diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java index 51867bc..7d8a36e 100644 --- a/core/java/android/app/NativeActivity.java +++ b/core/java/android/app/NativeActivity.java @@ -1,7 +1,20 @@ +/* + * Copyright (C) 2010 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.app; -import com.android.internal.view.IInputMethodSession; - import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; @@ -25,7 +38,6 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.inputmethod.InputMethodManager; import java.io.File; -import java.lang.ref.WeakReference; /** * Convenience for implementing an activity that will be implemented @@ -65,7 +77,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, private NativeContentView mNativeContentView; private InputMethodManager mIMM; - private InputMethodCallback mInputMethodCallback; private int mNativeHandle; @@ -117,22 +128,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, super(context, attrs); } } - - static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback { - WeakReference<NativeActivity> mNa; - - InputMethodCallback(NativeActivity na) { - mNa = new WeakReference<NativeActivity>(na); - } - - @Override - public void finishedEvent(int seq, boolean handled) { - NativeActivity na = mNa.get(); - if (na != null) { - na.finishPreDispatchKeyEventNative(na.mNativeHandle, seq, handled); - } - } - } @Override protected void onCreate(Bundle savedInstanceState) { @@ -141,7 +136,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, ActivityInfo ai; mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - mInputMethodCallback = new InputMethodCallback(this); getWindow().takeSurface(this); getWindow().takeInputQueue(this); @@ -353,7 +347,8 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, } void preDispatchKeyEvent(KeyEvent event, int seq) { - mIMM.dispatchInputEvent(this, seq, event, mInputMethodCallback); + // FIXME: Input dispatch should be redirected back through ViewRootImpl again. + finishPreDispatchKeyEventNative(mNativeHandle, seq, false); } void setWindowFlags(int flags, int mask) { diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 9c0064e..3342068 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -709,7 +709,7 @@ public class WallpaperManager { public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) { try { //Log.v(TAG, "Sending new wallpaper offsets from app..."); - WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( + WindowManagerGlobal.getWindowSession().setWallpaperPosition( windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep); //Log.v(TAG, "...app returning after sending offsets!"); } catch (RemoteException e) { @@ -747,7 +747,7 @@ public class WallpaperManager { int x, int y, int z, Bundle extras) { try { //Log.v(TAG, "Sending new wallpaper offsets from app..."); - WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand( + WindowManagerGlobal.getWindowSession().sendWallpaperCommand( windowToken, action, x, y, z, extras, false); //Log.v(TAG, "...app returning after sending offsets!"); } catch (RemoteException e) { @@ -767,7 +767,7 @@ public class WallpaperManager { */ public void clearWallpaperOffsets(IBinder windowToken) { try { - WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( + WindowManagerGlobal.getWindowSession().setWallpaperPosition( windowToken, -1, -1, -1, -1); } catch (RemoteException e) { // Ignore. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index ef9b0bf..03e241a 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -256,6 +256,12 @@ public abstract class Context { * Return the Looper for the main thread of the current process. This is * the thread used to dispatch calls to application components (activities, * services, etc). + * <p> + * By definition, this method returns the same result as would be obtained + * by calling {@link Looper#getMainLooper() Looper.getMainLooper()}. + * </p> + * + * @return The main looper. */ public abstract Looper getMainLooper(); diff --git a/core/java/android/os/LatencyTimer.java b/core/java/android/os/LatencyTimer.java deleted file mode 100644 index ed2f0f9e..0000000 --- a/core/java/android/os/LatencyTimer.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2009 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.os; - -import android.util.Log; - -import java.util.HashMap; - -/** - * A class to help with measuring latency in your code. - * - * Suggested usage: - * 1) Instanciate a LatencyTimer as a class field. - * private [static] LatencyTimer mLt = new LatencyTimer(100, 1000); - * 2) At various points in the code call sample with a string and the time delta to some fixed time. - * The string should be unique at each point of the code you are measuring. - * mLt.sample("before processing event", System.nanoTime() - event.getEventTimeNano()); - * processEvent(event); - * mLt.sample("after processing event ", System.nanoTime() - event.getEventTimeNano()); - * - * @hide - */ -public final class LatencyTimer -{ - final String TAG = "LatencyTimer"; - final int mSampleSize; - final int mScaleFactor; - volatile HashMap<String, long[]> store = new HashMap<String, long[]>(); - - /** - * Creates a LatencyTimer object - * @param sampleSize number of samples to collect before printing out the average - * @param scaleFactor divisor used to make each sample smaller to prevent overflow when - * (sampleSize * average sample value)/scaleFactor > Long.MAX_VALUE - */ - public LatencyTimer(int sampleSize, int scaleFactor) { - if (scaleFactor == 0) { - scaleFactor = 1; - } - mScaleFactor = scaleFactor; - mSampleSize = sampleSize; - } - - /** - * Add a sample delay for averaging. - * @param tag string used for printing out the result. This should be unique at each point of - * this called. - * @param delta time difference from an unique point of reference for a particular iteration - */ - public void sample(String tag, long delta) { - long[] array = getArray(tag); - - // array[mSampleSize] holds the number of used entries - final int index = (int) array[mSampleSize]++; - array[index] = delta; - if (array[mSampleSize] == mSampleSize) { - long totalDelta = 0; - for (long d : array) { - totalDelta += d/mScaleFactor; - } - array[mSampleSize] = 0; - Log.i(TAG, tag + " average = " + totalDelta / mSampleSize); - } - } - - private long[] getArray(String tag) { - long[] data = store.get(tag); - if (data == null) { - synchronized(store) { - data = store.get(tag); - if (data == null) { - data = new long[mSampleSize + 1]; - store.put(tag, data); - data[mSampleSize] = 0; - } - } - } - return data; - } -} diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index 38f4d5e..363a1bf 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -192,6 +192,14 @@ public final class Looper { } /** + * Returns true if the current thread is this looper's thread. + * @hide + */ + public boolean isCurrentThread() { + return Thread.currentThread() == mThread; + } + + /** * Quits the looper. * * Causes the {@link #loop} method to terminate as soon as possible. diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 71d8fb6..5db8168 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -762,7 +762,7 @@ public abstract class WallpaperService extends Service { mWindowToken = wrapper.mWindowToken; mSurfaceHolder.setSizeFromLayout(); mInitializing = true; - mSession = WindowManagerGlobal.getWindowSession(getMainLooper()); + mSession = WindowManagerGlobal.getWindowSession(); mWindow.setSession(mSession); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9387624..b2e3b4d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -42,7 +42,6 @@ import android.os.Binder; import android.os.Bundle; import android.os.Debug; import android.os.Handler; -import android.os.LatencyTimer; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; @@ -116,9 +115,6 @@ public final class ViewRootImpl implements ViewParent, * at 60 Hz. This can be used to measure the potential framerate. */ private static final String PROPERTY_PROFILE_RENDERING = "viewancestor.profile_rendering"; - - private static final boolean MEASURE_LATENCY = false; - private static LatencyTimer lt; /** * Maximum time we allow the user to roll the trackball enough to generate @@ -138,26 +134,15 @@ public final class ViewRootImpl implements ViewParent, private static boolean sRenderThreadQueried = false; private static final Object[] sRenderThreadQueryLock = new Object[0]; + final Context mContext; final IWindowSession mWindowSession; final Display mDisplay; final String mBasePackageName; - long mLastTrackballTime = 0; - final TrackballAxis mTrackballAxisX = new TrackballAxis(); - final TrackballAxis mTrackballAxisY = new TrackballAxis(); - - final SimulatedDpad mSimulatedDpad; - - int mLastJoystickXDirection; - int mLastJoystickYDirection; - int mLastJoystickXKeyCode; - int mLastJoystickYKeyCode; - final int[] mTmpLocation = new int[2]; final TypedValue mTmpValue = new TypedValue(); - - final InputMethodCallback mInputMethodCallback; + final Thread mThread; final WindowLeaked mLocation; @@ -232,38 +217,23 @@ public final class ViewRootImpl implements ViewParent, int mClientWindowLayoutFlags; boolean mLastOverscanRequested; - /** Event was not handled and is finished. - * @hide */ - public static final int EVENT_NOT_HANDLED = 0; - /** Event was handled and is finished. - * @hide */ - public static final int EVENT_HANDLED = 1; - /** Event is waiting on the IME. - * @hide */ - public static final int EVENT_PENDING_IME = 2; - /** Event requires post-IME dispatch. - * @hide */ - public static final int EVENT_POST_IME = 3; - // Pool of queued input events. private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10; private QueuedInputEvent mQueuedInputEventPool; private int mQueuedInputEventPoolSize; /* Input event queue. - * Pending input events are input events waiting to be handled by the application. Current - * input events are input events which are being handled but are waiting on some action by the - * IME, even if they themselves may not need to be handled by the IME. + * Pending input events are input events waiting to be delivered to the input stages + * and handled by the application. */ QueuedInputEvent mPendingInputEventHead; QueuedInputEvent mPendingInputEventTail; int mPendingInputEventCount; - QueuedInputEvent mActiveInputEventHead; - QueuedInputEvent mActiveInputEventTail; - int mActiveInputEventCount; boolean mProcessInputEventsScheduled; String mPendingInputEventQueueLengthCounterName = "pq"; - String mActiveInputEventQueueLengthCounterName = "aq"; + + InputStage mFirstInputStage; + InputStage mFirstPostImeInputStage; boolean mWindowAttributesChanged = false; int mWindowAttributesChangesFlag = 0; @@ -362,18 +332,8 @@ public final class ViewRootImpl implements ViewParent, } public ViewRootImpl(Context context, Display display) { - super(); - - if (MEASURE_LATENCY) { - if (lt == null) { - lt = new LatencyTimer(100, 1000); - } - } - - // Initialize the statics when this class is first instantiated. This is - // done here instead of in the static block because Zygote does not - // allow the spawning of threads. - mWindowSession = WindowManagerGlobal.getWindowSession(context.getMainLooper()); + mContext = context; + mWindowSession = WindowManagerGlobal.getWindowSession(); mDisplay = display; mBasePackageName = context.getBasePackageName(); @@ -391,7 +351,6 @@ public final class ViewRootImpl implements ViewParent, mWinFrame = new Rect(); mWindow = new W(this); mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; - mInputMethodCallback = new InputMethodCallback(this); mViewVisibility = View.GONE; mTransparentRegion = new Region(); mPreviousTransparentRegion = new Region(); @@ -412,7 +371,6 @@ public final class ViewRootImpl implements ViewParent, PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mAttachInfo.mScreenOn = powerManager.isScreenOn(); loadSystemProperties(); - mSimulatedDpad = new SimulatedDpad(context); } /** @@ -660,8 +618,22 @@ public final class ViewRootImpl implements ViewParent, view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } - mPendingInputEventQueueLengthCounterName = "pq:" + attrs.getTitle(); - mActiveInputEventQueueLengthCounterName = "aq:" + attrs.getTitle(); + // Set up the input pipeline. + CharSequence counterSuffix = attrs.getTitle(); + InputStage syntheticStage = new SyntheticInputStage(); + InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticStage); + InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, + "aq:native-post-ime:" + counterSuffix); + InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); + InputStage imeStage = new ImeInputStage(earlyPostImeStage, + "aq:ime:" + counterSuffix); + InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage); + InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, + "aq:native-pre-ime:" + counterSuffix); + + mFirstInputStage = nativePreImeStage; + mFirstPostImeInputStage = earlyPostImeStage; + mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix; } } } @@ -2861,7 +2833,7 @@ public final class ViewRootImpl implements ViewParent, mWindowSession.remove(mWindow); } catch (RemoteException e) { } - + // Dispose the input channel after removing the window so the Window Manager // doesn't interpret the input channel being closed as an abnormal termination. if (mInputChannel != null) { @@ -3365,365 +3337,822 @@ public final class ViewRootImpl implements ViewParent, return false; } - private int deliverInputEvent(QueuedInputEvent q) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent"); - try { - if (q.mEvent instanceof KeyEvent) { - return deliverKeyEvent(q); + /** + * Base class for implementing a stage in the chain of responsibility + * for processing input events. + * <p> + * Events are delivered to the stage by the {@link #deliver} method. The stage + * then has the choice of finishing the event or forwarding it to the next stage. + * </p> + */ + abstract class InputStage { + private final InputStage mNext; + + protected static final int FORWARD = 0; + protected static final int FINISH_HANDLED = 1; + protected static final int FINISH_NOT_HANDLED = 2; + + /** + * Creates an input stage. + * @param next The next stage to which events should be forwarded. + */ + public InputStage(InputStage next) { + mNext = next; + } + + /** + * Delivers an event to be processed. + */ + public final void deliver(QueuedInputEvent q) { + if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) { + forward(q); + } else if (mView == null || !mAdded) { + finish(q, false); } else { - final int source = q.mEvent.getSource(); - if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { - return deliverPointerEvent(q); - } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { - return deliverTrackballEvent(q); - } else { - return deliverGenericMotionEvent(q); - } + apply(q, onProcess(q)); } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); } - } - private int deliverInputEventPostIme(QueuedInputEvent q) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEventPostIme"); - try { - if (q.mEvent instanceof KeyEvent) { - return deliverKeyEventPostIme(q); + /** + * Marks the the input event as finished then forwards it to the next stage. + */ + protected void finish(QueuedInputEvent q, boolean handled) { + q.mFlags |= QueuedInputEvent.FLAG_FINISHED; + if (handled) { + q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED; + } + forward(q); + } + + /** + * Forwards the event to the next stage. + */ + protected void forward(QueuedInputEvent q) { + onDeliverToNext(q); + } + + /** + * Applies a result code from {@link #onProcess} to the specified event. + */ + protected void apply(QueuedInputEvent q, int result) { + if (result == FORWARD) { + forward(q); + } else if (result == FINISH_HANDLED) { + finish(q, true); + } else if (result == FINISH_NOT_HANDLED) { + finish(q, false); } else { - final int source = q.mEvent.getSource(); - if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { - return deliverTrackballEventPostIme(q); + throw new IllegalArgumentException("Invalid result: " + result); + } + } + + /** + * Called when an event is ready to be processed. + * @return A result code indicating how the event was handled. + */ + protected int onProcess(QueuedInputEvent q) { + return FORWARD; + } + + /** + * Called when an event is being delivered to the next stage. + */ + protected void onDeliverToNext(QueuedInputEvent q) { + if (mNext != null) { + mNext.deliver(q); + } else { + finishInputEvent(q); + } + } + } + + /** + * Base class for implementing an input pipeline stage that supports + * asynchronous and out-of-order processing of input events. + * <p> + * In addition to what a normal input stage can do, an asynchronous + * input stage may also defer an input event that has been delivered to it + * and finish or forward it later. + * </p> + */ + abstract class AsyncInputStage extends InputStage { + private final String mTraceCounter; + + private QueuedInputEvent mQueueHead; + private QueuedInputEvent mQueueTail; + private int mQueueLength; + + protected static final int DEFER = 3; + + /** + * Creates an asynchronous input stage. + * @param next The next stage to which events should be forwarded. + * @param traceCounter The name of a counter to record the size of + * the queue of pending events. + */ + public AsyncInputStage(InputStage next, String traceCounter) { + super(next); + mTraceCounter = traceCounter; + } + + /** + * Marks the event as deferred, which is to say that it will be handled + * asynchronously. The caller is responsible for calling {@link #forward} + * or {@link #finish} later when it is done handling the event. + */ + protected void defer(QueuedInputEvent q) { + q.mFlags |= QueuedInputEvent.FLAG_DEFERRED; + enqueue(q); + } + + @Override + protected void forward(QueuedInputEvent q) { + // Clear the deferred flag. + q.mFlags &= ~QueuedInputEvent.FLAG_DEFERRED; + + // Fast path if the queue is empty. + QueuedInputEvent curr = mQueueHead; + if (curr == null) { + super.forward(q); + return; + } + + // Determine whether the event must be serialized behind any others + // before it can be delivered to the next stage. This is done because + // deferred events might be handled out of order by the stage. + final int deviceId = q.mEvent.getDeviceId(); + QueuedInputEvent prev = null; + boolean blocked = false; + while (curr != null && curr != q) { + if (!blocked && deviceId == curr.mEvent.getDeviceId()) { + blocked = true; + } + prev = curr; + curr = curr.mNext; + } + + // If the event is blocked, then leave it in the queue to be delivered later. + // Note that the event might not yet be in the queue if it was not previously + // deferred so we will enqueue it if needed. + if (blocked) { + if (curr == null) { + enqueue(q); + } + return; + } + + // The event is not blocked. Deliver it immediately. + if (curr != null) { + curr = curr.mNext; + dequeue(q, prev); + } + super.forward(q); + + // Dequeuing this event may have unblocked successors. Deliver them. + while (curr != null) { + if (deviceId == curr.mEvent.getDeviceId()) { + if ((curr.mFlags & QueuedInputEvent.FLAG_DEFERRED) != 0) { + break; + } + QueuedInputEvent next = curr.mNext; + dequeue(curr, prev); + super.forward(curr); + curr = next; } else { - return deliverGenericMotionEventPostIme(q); + prev = curr; + curr = curr.mNext; } } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); } - } - private int deliverPointerEvent(QueuedInputEvent q) { - final MotionEvent event = (MotionEvent)q.mEvent; - final boolean isTouchEvent = event.isTouchEvent(); - if (mInputEventConsistencyVerifier != null) { - if (isTouchEvent) { - mInputEventConsistencyVerifier.onTouchEvent(event, 0); + @Override + protected void apply(QueuedInputEvent q, int result) { + if (result == DEFER) { + defer(q); } else { - mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0); + super.apply(q, result); } } - // If there is no view, then the event will not be handled. - if (mView == null || !mAdded) { - return EVENT_NOT_HANDLED; + private void enqueue(QueuedInputEvent q) { + if (mQueueTail == null) { + mQueueHead = q; + mQueueTail = q; + } else { + mQueueTail.mNext = q; + mQueueTail = q; + } + + mQueueLength += 1; + Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength); } - // Translate the pointer event for compatibility, if needed. - if (mTranslator != null) { - mTranslator.translateEventInScreenToAppWindow(event); + private void dequeue(QueuedInputEvent q, QueuedInputEvent prev) { + if (prev == null) { + mQueueHead = q.mNext; + } else { + prev.mNext = q.mNext; + } + if (mQueueTail == q) { + mQueueTail = prev; + } + q.mNext = null; + + mQueueLength -= 1; + Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength); } + } - // Enter touch mode on down or scroll. - final int action = event.getAction(); - if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) { - ensureTouchMode(true); + /** + * Delivers pre-ime input events to a native activity. + * Does not support pointer events. + */ + final class NativePreImeInputStage extends AsyncInputStage { + public NativePreImeInputStage(InputStage next, String traceCounter) { + super(next, traceCounter); } - // Offset the scroll position. - if (mCurScrollY != 0) { - event.offsetLocation(0, mCurScrollY); + @Override + protected int onProcess(QueuedInputEvent q) { + return FORWARD; } - if (MEASURE_LATENCY) { - lt.sample("A Dispatching PointerEvents", System.nanoTime() - event.getEventTimeNano()); + } + + /** + * Delivers pre-ime input events to the view hierarchy. + * Does not support pointer events. + */ + final class ViewPreImeInputStage extends InputStage { + public ViewPreImeInputStage(InputStage next) { + super(next); } - // Remember the touch position for possible drag-initiation. - if (isTouchEvent) { - mLastTouchPoint.x = event.getRawX(); - mLastTouchPoint.y = event.getRawY(); + @Override + protected int onProcess(QueuedInputEvent q) { + if (q.mEvent instanceof KeyEvent) { + return processKeyEvent(q); + } + return FORWARD; } - // Dispatch touch to view hierarchy. - boolean handled = mView.dispatchPointerEvent(event); - if (MEASURE_LATENCY) { - lt.sample("B Dispatched PointerEvents ", System.nanoTime() - event.getEventTimeNano()); + private int processKeyEvent(QueuedInputEvent q) { + final KeyEvent event = (KeyEvent)q.mEvent; + if (mView.dispatchKeyEventPreIme(event)) { + return FINISH_HANDLED; + } + return FORWARD; } - return handled ? EVENT_HANDLED : EVENT_NOT_HANDLED; } - private int deliverTrackballEvent(QueuedInputEvent q) { - final MotionEvent event = (MotionEvent)q.mEvent; - if (mInputEventConsistencyVerifier != null) { - mInputEventConsistencyVerifier.onTrackballEvent(event, 0); + /** + * Delivers input events to the ime. + * Does not support pointer events. + */ + final class ImeInputStage extends AsyncInputStage + implements InputMethodManager.FinishedInputEventCallback { + public ImeInputStage(InputStage next, String traceCounter) { + super(next, traceCounter); + } + + @Override + protected int onProcess(QueuedInputEvent q) { + if (mLastWasImTarget) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + final InputEvent event = q.mEvent; + if (DEBUG_IMF) Log.v(TAG, "Sending input event to IME: " + event); + int result = imm.dispatchInputEvent(event, q, this, mHandler); + if (result == InputMethodManager.DISPATCH_HANDLED) { + return FINISH_HANDLED; + } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) { + return FINISH_NOT_HANDLED; + } else { + return DEFER; // callback will be invoked later + } + } + } + return FORWARD; } - 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); + @Override + public void onFinishedInputEvent(Object token, boolean handled) { + QueuedInputEvent q = (QueuedInputEvent)token; + if (handled) { + finish(q, true); + return; + } - // Dispatch to the IME before propagating down the view hierarchy. - result = dispatchImeInputEvent(q); + // If the window doesn't currently have input focus, then drop + // this event. This could be an event that came back from the + // IME dispatch but the window has lost focus in the meantime. + if (!mAttachInfo.mHasWindowFocus && !isTerminalInputEvent(q.mEvent)) { + Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent); + finish(q, false); + return; + } + + forward(q); } - return result; } - private int deliverTrackballEventPostIme(QueuedInputEvent q) { - final MotionEvent event = (MotionEvent) q.mEvent; + /** + * Performs early processing of post-ime input events. + */ + final class EarlyPostImeInputStage extends InputStage { + public EarlyPostImeInputStage(InputStage next) { + super(next); + } - // If there is no view, then the event will not be handled. - if (mView == null || !mAdded) { - return EVENT_NOT_HANDLED; + @Override + protected int onProcess(QueuedInputEvent q) { + if (q.mEvent instanceof KeyEvent) { + return processKeyEvent(q); + } else { + final int source = q.mEvent.getSource(); + if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { + return processPointerEvent(q); + } + } + return FORWARD; } - // Deliver the trackball event to the view. - if (mView.dispatchTrackballEvent(event)) { - // If we reach this, we delivered a trackball event to mView and - // mView consumed it. Because we will not translate the trackball - // event into a key event, touch mode will not exit, so we exit - // touch mode here. - ensureTouchMode(false); - mLastTrackballTime = Integer.MIN_VALUE; - return EVENT_HANDLED; + private int processKeyEvent(QueuedInputEvent q) { + final KeyEvent event = (KeyEvent)q.mEvent; + + // If the key's purpose is to exit touch mode then we consume it + // and consider it handled. + if (checkForLeavingTouchModeAndConsume(event)) { + return FINISH_HANDLED; + } + + // Make sure the fallback event policy sees all keys that will be + // delivered to the view hierarchy. + mFallbackEventHandler.preDispatchKeyEvent(event); + return FORWARD; } - // Translate the trackball event into DPAD keys and try to deliver those. - final TrackballAxis x = mTrackballAxisX; - final TrackballAxis y = mTrackballAxisY; + private int processPointerEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; + + // Translate the pointer event for compatibility, if needed. + if (mTranslator != null) { + mTranslator.translateEventInScreenToAppWindow(event); + } - long curTime = SystemClock.uptimeMillis(); - if ((mLastTrackballTime + MAX_TRACKBALL_DELAY) < curTime) { - // It has been too long since the last movement, - // so restart at the beginning. - x.reset(0); - y.reset(0); - mLastTrackballTime = curTime; + // Enter touch mode on down or scroll. + final int action = event.getAction(); + if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) { + ensureTouchMode(true); + } + + // Offset the scroll position. + if (mCurScrollY != 0) { + event.offsetLocation(0, mCurScrollY); + } + + // Remember the touch position for possible drag-initiation. + if (event.isTouchEvent()) { + mLastTouchPoint.x = event.getRawX(); + mLastTouchPoint.y = event.getRawY(); + } + return FORWARD; } + } - final int action = event.getAction(); - final int metaState = event.getMetaState(); - switch (action) { - case MotionEvent.ACTION_DOWN: - x.reset(2); - y.reset(2); - enqueueInputEvent(new KeyEvent(curTime, curTime, - KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, - InputDevice.SOURCE_KEYBOARD)); - break; - case MotionEvent.ACTION_UP: - x.reset(2); - y.reset(2); - enqueueInputEvent(new KeyEvent(curTime, curTime, - KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, - InputDevice.SOURCE_KEYBOARD)); - break; + /** + * Delivers post-ime input events to a native activity. + */ + final class NativePostImeInputStage extends AsyncInputStage { + public NativePostImeInputStage(InputStage next, String traceCounter) { + super(next, traceCounter); } - if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step=" - + x.step + " dir=" + x.dir + " acc=" + x.acceleration - + " move=" + event.getX() - + " / Y=" + y.position + " step=" - + y.step + " dir=" + y.dir + " acc=" + y.acceleration - + " move=" + event.getY()); - final float xOff = x.collect(event.getX(), event.getEventTime(), "X"); - final float yOff = y.collect(event.getY(), event.getEventTime(), "Y"); - - // Generate DPAD events based on the trackball movement. - // We pick the axis that has moved the most as the direction of - // the DPAD. When we generate DPAD events for one axis, then the - // other axis is reset -- we don't want to perform DPAD jumps due - // to slight movements in the trackball when making major movements - // along the other axis. - int keycode = 0; - int movement = 0; - float accel = 1; - if (xOff > yOff) { - movement = x.generate((2/event.getXPrecision())); - if (movement != 0) { - keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT - : KeyEvent.KEYCODE_DPAD_LEFT; - accel = x.acceleration; - y.reset(2); - } - } else if (yOff > 0) { - movement = y.generate((2/event.getYPrecision())); - if (movement != 0) { - keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN - : KeyEvent.KEYCODE_DPAD_UP; - accel = y.acceleration; - x.reset(2); - } - } - - if (keycode != 0) { - if (movement < 0) movement = -movement; - int accelMovement = (int)(movement * accel); - if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement - + " accelMovement=" + accelMovement - + " accel=" + accel); - if (accelMovement > movement) { - if (DEBUG_TRACKBALL) Log.v(TAG, "Delivering fake DPAD: " - + keycode); - movement--; - int repeatCount = accelMovement - movement; - enqueueInputEvent(new KeyEvent(curTime, curTime, - KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, - InputDevice.SOURCE_KEYBOARD)); - } - while (movement > 0) { - if (DEBUG_TRACKBALL) Log.v(TAG, "Delivering fake DPAD: " - + keycode); - movement--; - curTime = SystemClock.uptimeMillis(); - enqueueInputEvent(new KeyEvent(curTime, curTime, - KeyEvent.ACTION_DOWN, keycode, 0, metaState, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, - InputDevice.SOURCE_KEYBOARD)); - enqueueInputEvent(new KeyEvent(curTime, curTime, - KeyEvent.ACTION_UP, keycode, 0, metaState, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, - InputDevice.SOURCE_KEYBOARD)); - } - mLastTrackballTime = curTime; - } - - // Unfortunately we can't tell whether the application consumed the keys, so - // we always consider the trackball event handled. - return EVENT_HANDLED; - } - - private int deliverGenericMotionEvent(QueuedInputEvent q) { - final MotionEvent event = (MotionEvent)q.mEvent; - 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. - result = dispatchImeInputEvent(q); - } - return result; - } - - private int deliverGenericMotionEventPostIme(QueuedInputEvent q) { - final MotionEvent event = (MotionEvent) q.mEvent; - final int source = event.getSource(); - final boolean isJoystick = event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK); - final boolean isTouchNavigation = event.isFromSource(InputDevice.SOURCE_TOUCH_NAVIGATION); - - // If there is no view, then the event will not be handled. - if (mView == null || !mAdded) { - if (isJoystick) { - updateJoystickDirection(event, false); - } else if (isTouchNavigation) { - mSimulatedDpad.updateTouchNavigation(this, event, false); - } - return EVENT_NOT_HANDLED; - } - - // Deliver the event to the view. - if (mView.dispatchGenericMotionEvent(event)) { - if (isJoystick) { - updateJoystickDirection(event, false); - } else if (isTouchNavigation) { - mSimulatedDpad.updateTouchNavigation(this, event, false); - } - return EVENT_HANDLED; - } - - if (isJoystick) { - // Translate the joystick event into DPAD keys and try to deliver - // those. - updateJoystickDirection(event, true); - return EVENT_HANDLED; + @Override + protected int onProcess(QueuedInputEvent q) { + return FORWARD; + } + } + + /** + * Delivers post-ime input events to the view hierarchy. + */ + final class ViewPostImeInputStage extends InputStage { + public ViewPostImeInputStage(InputStage next) { + super(next); + } + + @Override + protected int onProcess(QueuedInputEvent q) { + if (q.mEvent instanceof KeyEvent) { + return processKeyEvent(q); + } else { + final int source = q.mEvent.getSource(); + if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { + return processPointerEvent(q); + } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { + return processTrackballEvent(q); + } else { + return processGenericMotionEvent(q); + } + } + } + + private int processKeyEvent(QueuedInputEvent q) { + final KeyEvent event = (KeyEvent)q.mEvent; + + // Deliver the key to the view hierarchy. + if (mView.dispatchKeyEvent(event)) { + return FINISH_HANDLED; + } + + // If the Control modifier is held, try to interpret the key as a shortcut. + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.isCtrlPressed() + && event.getRepeatCount() == 0 + && !KeyEvent.isModifierKey(event.getKeyCode())) { + if (mView.dispatchKeyShortcutEvent(event)) { + return FINISH_HANDLED; + } + } + + // Apply the fallback event policy. + if (mFallbackEventHandler.dispatchKeyEvent(event)) { + return FINISH_HANDLED; + } + + // Handle automatic focus changes. + if (event.getAction() == KeyEvent.ACTION_DOWN) { + int direction = 0; + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (event.hasNoModifiers()) { + direction = View.FOCUS_LEFT; + } + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (event.hasNoModifiers()) { + direction = View.FOCUS_RIGHT; + } + break; + case KeyEvent.KEYCODE_DPAD_UP: + if (event.hasNoModifiers()) { + direction = View.FOCUS_UP; + } + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (event.hasNoModifiers()) { + direction = View.FOCUS_DOWN; + } + break; + case KeyEvent.KEYCODE_TAB: + if (event.hasNoModifiers()) { + direction = View.FOCUS_FORWARD; + } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { + direction = View.FOCUS_BACKWARD; + } + break; + } + if (direction != 0) { + View focused = mView.findFocus(); + if (focused != null) { + View v = focused.focusSearch(direction); + if (v != null && v != focused) { + // do the math the get the interesting rect + // of previous focused into the coord system of + // newly focused view + focused.getFocusedRect(mTempRect); + if (mView instanceof ViewGroup) { + ((ViewGroup) mView).offsetDescendantRectToMyCoords( + focused, mTempRect); + ((ViewGroup) mView).offsetRectIntoDescendantCoords( + v, mTempRect); + } + if (v.requestFocus(direction, mTempRect)) { + playSoundEffect(SoundEffectConstants + .getContantForFocusDirection(direction)); + return FINISH_HANDLED; + } + } + + // Give the focused view a last chance to handle the dpad key. + if (mView.dispatchUnhandledMove(focused, direction)) { + return FINISH_HANDLED; + } + } else { + // find the best view to give focus to in this non-touch-mode with no-focus + View v = focusSearch(null, direction); + if (v != null && v.requestFocus(direction)) { + return FINISH_HANDLED; + } + } + } + } + return FORWARD; + } + + private int processPointerEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; + + if (mView.dispatchPointerEvent(event)) { + return FINISH_HANDLED; + } + return FORWARD; + } + + private int processTrackballEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; + + if (mView.dispatchTrackballEvent(event)) { + return FINISH_HANDLED; + } + return FORWARD; } - if (isTouchNavigation) { - mSimulatedDpad.updateTouchNavigation(this, event, true); - return EVENT_HANDLED; + + private int processGenericMotionEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; + + // Deliver the event to the view. + if (mView.dispatchGenericMotionEvent(event)) { + return FINISH_HANDLED; + } + return FORWARD; } - return EVENT_NOT_HANDLED; } - private void updateJoystickDirection(MotionEvent event, boolean synthesizeNewKeys) { - final long time = event.getEventTime(); - final int metaState = event.getMetaState(); - final int deviceId = event.getDeviceId(); - final int source = event.getSource(); + /** + * Performs default processing of unhandled input events. + */ + final class SyntheticInputStage extends InputStage { + private final TrackballAxis mTrackballAxisX = new TrackballAxis(); + private final TrackballAxis mTrackballAxisY = new TrackballAxis(); + private long mLastTrackballTime; - int xDirection = joystickAxisValueToDirection(event.getAxisValue(MotionEvent.AXIS_HAT_X)); - if (xDirection == 0) { - xDirection = joystickAxisValueToDirection(event.getX()); + private int mLastJoystickXDirection; + private int mLastJoystickYDirection; + private int mLastJoystickXKeyCode; + private int mLastJoystickYKeyCode; + + private SimulatedDpad mSimulatedDpad; + + public SyntheticInputStage() { + super(null); + mSimulatedDpad = new SimulatedDpad(mContext); } - int yDirection = joystickAxisValueToDirection(event.getAxisValue(MotionEvent.AXIS_HAT_Y)); - if (yDirection == 0) { - yDirection = joystickAxisValueToDirection(event.getY()); + @Override + protected int onProcess(QueuedInputEvent q) { + q.mFlags |= QueuedInputEvent.FLAG_RESYNTHESIZED; + if (q.mEvent instanceof MotionEvent) { + final int source = q.mEvent.getSource(); + if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { + return processTrackballEvent(q); + } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + return processJoystickEvent(q); + } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION) + == InputDevice.SOURCE_TOUCH_NAVIGATION) { + return processTouchNavigationEvent(q); + } + } + return FORWARD; + } + + @Override + protected void onDeliverToNext(QueuedInputEvent q) { + if ((q.mFlags & QueuedInputEvent.FLAG_RESYNTHESIZED) == 0) { + // Cancel related synthetic events if any prior stage has handled the event. + if (q.mEvent instanceof MotionEvent) { + final int source = q.mEvent.getSource(); + if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { + cancelTrackballEvent(q); + } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + cancelJoystickEvent(q); + } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION) + == InputDevice.SOURCE_TOUCH_NAVIGATION) { + cancelTouchNavigationEvent(q); + } + } + } + super.onDeliverToNext(q); } - if (xDirection != mLastJoystickXDirection) { - if (mLastJoystickXKeyCode != 0) { - mHandler.removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT); - enqueueInputEvent(new KeyEvent(time, time, - KeyEvent.ACTION_UP, mLastJoystickXKeyCode, 0, metaState, - deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); - mLastJoystickXKeyCode = 0; + private int processTrackballEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; + + // Translate the trackball event into DPAD keys and try to deliver those. + final TrackballAxis x = mTrackballAxisX; + final TrackballAxis y = mTrackballAxisY; + long curTime = SystemClock.uptimeMillis(); + if ((mLastTrackballTime + MAX_TRACKBALL_DELAY) < curTime) { + // It has been too long since the last movement, + // so restart at the beginning. + x.reset(0); + y.reset(0); + mLastTrackballTime = curTime; + } + + final int action = event.getAction(); + final int metaState = event.getMetaState(); + switch (action) { + case MotionEvent.ACTION_DOWN: + x.reset(2); + y.reset(2); + enqueueInputEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, + InputDevice.SOURCE_KEYBOARD)); + break; + case MotionEvent.ACTION_UP: + x.reset(2); + y.reset(2); + enqueueInputEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, + InputDevice.SOURCE_KEYBOARD)); + break; + } + + if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step=" + + x.step + " dir=" + x.dir + " acc=" + x.acceleration + + " move=" + event.getX() + + " / Y=" + y.position + " step=" + + y.step + " dir=" + y.dir + " acc=" + y.acceleration + + " move=" + event.getY()); + final float xOff = x.collect(event.getX(), event.getEventTime(), "X"); + final float yOff = y.collect(event.getY(), event.getEventTime(), "Y"); + + // Generate DPAD events based on the trackball movement. + // We pick the axis that has moved the most as the direction of + // the DPAD. When we generate DPAD events for one axis, then the + // other axis is reset -- we don't want to perform DPAD jumps due + // to slight movements in the trackball when making major movements + // along the other axis. + int keycode = 0; + int movement = 0; + float accel = 1; + if (xOff > yOff) { + movement = x.generate((2/event.getXPrecision())); + if (movement != 0) { + keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT + : KeyEvent.KEYCODE_DPAD_LEFT; + accel = x.acceleration; + y.reset(2); + } + } else if (yOff > 0) { + movement = y.generate((2/event.getYPrecision())); + if (movement != 0) { + keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN + : KeyEvent.KEYCODE_DPAD_UP; + accel = y.acceleration; + x.reset(2); + } } - mLastJoystickXDirection = xDirection; + if (keycode != 0) { + if (movement < 0) movement = -movement; + int accelMovement = (int)(movement * accel); + if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement + + " accelMovement=" + accelMovement + + " accel=" + accel); + if (accelMovement > movement) { + if (DEBUG_TRACKBALL) Log.v(TAG, "Delivering fake DPAD: " + + keycode); + movement--; + int repeatCount = accelMovement - movement; + enqueueInputEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, + InputDevice.SOURCE_KEYBOARD)); + } + while (movement > 0) { + if (DEBUG_TRACKBALL) Log.v(TAG, "Delivering fake DPAD: " + + keycode); + movement--; + curTime = SystemClock.uptimeMillis(); + enqueueInputEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_DOWN, keycode, 0, metaState, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, + InputDevice.SOURCE_KEYBOARD)); + enqueueInputEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_UP, keycode, 0, metaState, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK, + InputDevice.SOURCE_KEYBOARD)); + } + mLastTrackballTime = curTime; + } + + // Unfortunately we can't tell whether the application consumed the keys, so + // we always consider the trackball event handled. + return FINISH_HANDLED; + } + + private void cancelTrackballEvent(QueuedInputEvent q) { + mLastTrackballTime = Integer.MIN_VALUE; - if (xDirection != 0 && synthesizeNewKeys) { - mLastJoystickXKeyCode = xDirection > 0 - ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT; - final KeyEvent e = new KeyEvent(time, time, - KeyEvent.ACTION_DOWN, mLastJoystickXKeyCode, 0, metaState, - deviceId, 0, KeyEvent.FLAG_FALLBACK, source); - enqueueInputEvent(e); - Message m = mHandler.obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e); - m.setAsynchronous(true); - mHandler.sendMessageDelayed(m, mViewConfiguration.getKeyRepeatTimeout()); + // If we reach this, we consumed a trackball event. + // Because we will not translate the trackball event into a key event, + // touch mode will not exit, so we exit touch mode here. + if (mView != null && mAdded) { + ensureTouchMode(false); } } - if (yDirection != mLastJoystickYDirection) { - if (mLastJoystickYKeyCode != 0) { - mHandler.removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT); - enqueueInputEvent(new KeyEvent(time, time, - KeyEvent.ACTION_UP, mLastJoystickYKeyCode, 0, metaState, - deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); - mLastJoystickYKeyCode = 0; + private int processJoystickEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; + updateJoystickDirection(event, true); + return FINISH_HANDLED; + } + + private void cancelJoystickEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; + updateJoystickDirection(event, false); + } + + private void updateJoystickDirection(MotionEvent event, boolean synthesizeNewKeys) { + final long time = event.getEventTime(); + final int metaState = event.getMetaState(); + final int deviceId = event.getDeviceId(); + final int source = event.getSource(); + + int xDirection = joystickAxisValueToDirection( + event.getAxisValue(MotionEvent.AXIS_HAT_X)); + if (xDirection == 0) { + xDirection = joystickAxisValueToDirection(event.getX()); + } + + int yDirection = joystickAxisValueToDirection( + event.getAxisValue(MotionEvent.AXIS_HAT_Y)); + if (yDirection == 0) { + yDirection = joystickAxisValueToDirection(event.getY()); + } + + if (xDirection != mLastJoystickXDirection) { + if (mLastJoystickXKeyCode != 0) { + mHandler.removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT); + enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_UP, mLastJoystickXKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); + mLastJoystickXKeyCode = 0; + } + + mLastJoystickXDirection = xDirection; + + if (xDirection != 0 && synthesizeNewKeys) { + mLastJoystickXKeyCode = xDirection > 0 + ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT; + final KeyEvent e = new KeyEvent(time, time, + KeyEvent.ACTION_DOWN, mLastJoystickXKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source); + enqueueInputEvent(e); + Message m = mHandler.obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e); + m.setAsynchronous(true); + mHandler.sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout()); + } } - mLastJoystickYDirection = yDirection; + if (yDirection != mLastJoystickYDirection) { + if (mLastJoystickYKeyCode != 0) { + mHandler.removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT); + enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_UP, mLastJoystickYKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); + mLastJoystickYKeyCode = 0; + } + + mLastJoystickYDirection = yDirection; - if (yDirection != 0 && synthesizeNewKeys) { - mLastJoystickYKeyCode = yDirection > 0 - ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP; - final KeyEvent e = new KeyEvent(time, time, - KeyEvent.ACTION_DOWN, mLastJoystickYKeyCode, 0, metaState, - deviceId, 0, KeyEvent.FLAG_FALLBACK, source); - enqueueInputEvent(e); - Message m = mHandler.obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e); - m.setAsynchronous(true); - mHandler.sendMessageDelayed(m, mViewConfiguration.getKeyRepeatTimeout()); + if (yDirection != 0 && synthesizeNewKeys) { + mLastJoystickYKeyCode = yDirection > 0 + ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP; + final KeyEvent e = new KeyEvent(time, time, + KeyEvent.ACTION_DOWN, mLastJoystickYKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source); + enqueueInputEvent(e); + Message m = mHandler.obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e); + m.setAsynchronous(true); + mHandler.sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout()); + } } } - } - private static int joystickAxisValueToDirection(float value) { - if (value >= 0.5f) { - return 1; - } else if (value <= -0.5f) { - return -1; - } else { - return 0; + private int joystickAxisValueToDirection(float value) { + if (value >= 0.5f) { + return 1; + } else if (value <= -0.5f) { + return -1; + } else { + return 0; + } + } + + private int processTouchNavigationEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; + mSimulatedDpad.updateTouchNavigation(ViewRootImpl.this, event, true); + return FINISH_HANDLED; + } + + private void cancelTouchNavigationEvent(QueuedInputEvent q) { + final MotionEvent event = (MotionEvent)q.mEvent; + mSimulatedDpad.updateTouchNavigation(ViewRootImpl.this, event, false); } } @@ -3803,136 +4232,6 @@ public final class ViewRootImpl implements ViewParent, return false; } - private int deliverKeyEvent(QueuedInputEvent q) { - final KeyEvent event = (KeyEvent)q.mEvent; - if (mInputEventConsistencyVerifier != null) { - 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); - - // Perform predispatching before the IME. - if (mView.dispatchKeyEventPreIme(event)) { - return EVENT_HANDLED; - } - - // Dispatch to the IME before propagating down the view hierarchy. - result = dispatchImeInputEvent(q); - } - return result; - } - - private int deliverKeyEventPostIme(QueuedInputEvent q) { - final KeyEvent event = (KeyEvent)q.mEvent; - - // If the view went away, then the event will not be handled. - if (mView == null || !mAdded) { - return EVENT_NOT_HANDLED; - } - - // If the key's purpose is to exit touch mode then we consume it and consider it handled. - if (checkForLeavingTouchModeAndConsume(event)) { - return EVENT_HANDLED; - } - - // Make sure the fallback event policy sees all keys that will be delivered to the - // view hierarchy. - mFallbackEventHandler.preDispatchKeyEvent(event); - - // Deliver the key to the view hierarchy. - if (mView.dispatchKeyEvent(event)) { - return EVENT_HANDLED; - } - - // If the Control modifier is held, try to interpret the key as a shortcut. - if (event.getAction() == KeyEvent.ACTION_DOWN - && event.isCtrlPressed() - && event.getRepeatCount() == 0 - && !KeyEvent.isModifierKey(event.getKeyCode())) { - if (mView.dispatchKeyShortcutEvent(event)) { - return EVENT_HANDLED; - } - } - - // Apply the fallback event policy. - if (mFallbackEventHandler.dispatchKeyEvent(event)) { - return EVENT_HANDLED; - } - - // Handle automatic focus changes. - if (event.getAction() == KeyEvent.ACTION_DOWN) { - int direction = 0; - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (event.hasNoModifiers()) { - direction = View.FOCUS_LEFT; - } - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (event.hasNoModifiers()) { - direction = View.FOCUS_RIGHT; - } - break; - case KeyEvent.KEYCODE_DPAD_UP: - if (event.hasNoModifiers()) { - direction = View.FOCUS_UP; - } - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (event.hasNoModifiers()) { - direction = View.FOCUS_DOWN; - } - break; - case KeyEvent.KEYCODE_TAB: - if (event.hasNoModifiers()) { - direction = View.FOCUS_FORWARD; - } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { - direction = View.FOCUS_BACKWARD; - } - break; - } - if (direction != 0) { - View focused = mView.findFocus(); - if (focused != null) { - View v = focused.focusSearch(direction); - if (v != null && v != focused) { - // do the math the get the interesting rect - // of previous focused into the coord system of - // newly focused view - focused.getFocusedRect(mTempRect); - if (mView instanceof ViewGroup) { - ((ViewGroup) mView).offsetDescendantRectToMyCoords( - focused, mTempRect); - ((ViewGroup) mView).offsetRectIntoDescendantCoords( - v, mTempRect); - } - if (v.requestFocus(direction, mTempRect)) { - playSoundEffect(SoundEffectConstants - .getContantForFocusDirection(direction)); - return EVENT_HANDLED; - } - } - - // Give the focused view a last chance to handle the dpad key. - if (mView.dispatchUnhandledMove(focused, direction)) { - return EVENT_HANDLED; - } - } else { - // find the best view to give focus to in this non-touch-mode with no-focus - View v = focusSearch(null, direction); - if (v != null && v.requestFocus(direction)) { - return EVENT_HANDLED; - } - } - } - } - - // Key was unhandled. - return EVENT_NOT_HANDLED; - } - /* drag/drop */ void setLocalDragState(Object obj) { mLocalDragState = obj; @@ -4364,13 +4663,25 @@ public final class ViewRootImpl implements ViewParent, * needing a queue on the application's side. */ private static final class QueuedInputEvent { - public static final int FLAG_DELIVER_POST_IME = 1; + public static final int FLAG_DELIVER_POST_IME = 1 << 0; + public static final int FLAG_DEFERRED = 1 << 1; + public static final int FLAG_FINISHED = 1 << 2; + public static final int FLAG_FINISHED_HANDLED = 1 << 3; + public static final int FLAG_RESYNTHESIZED = 1 << 4; public QueuedInputEvent mNext; public InputEvent mEvent; public InputEventReceiver mReceiver; public int mFlags; + + public boolean shouldSkipIme() { + if ((mFlags & FLAG_DELIVER_POST_IME) != 0) { + return true; + } + return mEvent instanceof MotionEvent + && mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER); + } } private QueuedInputEvent obtainQueuedInputEvent(InputEvent event, @@ -4443,14 +4754,7 @@ public final class ViewRootImpl implements ViewParent, } void doProcessInputEvents() { - // Handle all of the available pending input events. Currently this will immediately - // process all of the events it can until it encounters one that must go through the IME. - // After that it will continue adding events to the active input queue but will wait for a - // response from the IME, regardless of whether that particular event needs it or not, in - // order to guarantee ordering consistency. This could be slightly improved by only - // queueing events whose source has previously encountered something that needs to be - // handled by the IME, and otherwise handling them immediately since we only need to - // guarantee ordering within a given source. + // Deliver all pending input events in the queue. while (mPendingInputEventHead != null) { QueuedInputEvent q = mPendingInputEventHead; mPendingInputEventHead = q.mNext; @@ -4463,25 +4767,7 @@ public final class ViewRootImpl implements ViewParent, Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName, mPendingInputEventCount); - int result = deliverInputEvent(q); - - if (result == EVENT_HANDLED || result == EVENT_NOT_HANDLED) { - finishInputEvent(q, result == EVENT_HANDLED); - } else if (result == EVENT_PENDING_IME) { - enqueueActiveInputEvent(q); - } else { - q.mFlags |= QueuedInputEvent.FLAG_DELIVER_POST_IME; - // If the IME decided not to handle this event, and we have no events already being - // handled by the IME, go ahead and handle this one and then continue to the next - // input event. Otherwise, queue it up and handle it after whatever in front of it - // in the queue has been handled. - if (mActiveInputEventHead == null) { - result = deliverInputEventPostIme(q); - finishInputEvent(q, result == EVENT_HANDLED); - } else { - enqueueActiveInputEvent(q); - } - } + deliverInputEvent(q); } // We are done processing all input events that we can process right now @@ -4492,114 +4778,27 @@ public final class ViewRootImpl implements ViewParent, } } - private void enqueueActiveInputEvent(QueuedInputEvent q) { - if (mActiveInputEventHead == null) { - mActiveInputEventHead = q; - mActiveInputEventTail = q; - } else { - mActiveInputEventTail.mNext = q; - mActiveInputEventTail = q; - } - mActiveInputEventCount += 1; - Trace.traceCounter(Trace.TRACE_TAG_INPUT, mActiveInputEventQueueLengthCounterName, - mActiveInputEventCount); - } - - private QueuedInputEvent dequeueActiveInputEvent() { - return dequeueActiveInputEvent(mActiveInputEventHead); - } - - - private QueuedInputEvent dequeueActiveInputEvent(QueuedInputEvent q) { - QueuedInputEvent curr = mActiveInputEventHead; - QueuedInputEvent prev = null; - while (curr != null && curr != q) { - prev = curr; - curr = curr.mNext; - } - if (curr != null) { - if (mActiveInputEventHead == curr) { - mActiveInputEventHead = curr.mNext; - } else { - prev.mNext = curr.mNext; - } - if (mActiveInputEventTail == curr) { - mActiveInputEventTail = prev; - } - curr.mNext = null; - - mActiveInputEventCount -= 1; - Trace.traceCounter(Trace.TRACE_TAG_INPUT, mActiveInputEventQueueLengthCounterName, - mActiveInputEventCount); - } - return curr; - } - - private QueuedInputEvent findActiveInputEvent(int seq) { - QueuedInputEvent q = mActiveInputEventHead; - while (q != null && q.mEvent.getSequenceNumber() != seq) { - q = q.mNext; - } - 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 = findActiveInputEvent(seq); - if (q != null) { - if (DEBUG_IMF) { - Log.v(TAG, "IME finished event: seq=" + seq - + " handled=" + handled + " event=" + q); + private void deliverInputEvent(QueuedInputEvent q) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent"); + try { + if (mInputEventConsistencyVerifier != null) { + mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0); } - if (handled) { - dequeueActiveInputEvent(q); - finishInputEvent(q, true); + InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; + if (stage != null) { + stage.deliver(q); } else { - q.mFlags |= QueuedInputEvent.FLAG_DELIVER_POST_IME; - } - - - // Flush all of the input events that are no longer waiting on the IME - while (mActiveInputEventHead != null && (mActiveInputEventHead.mFlags & - QueuedInputEvent.FLAG_DELIVER_POST_IME) != 0) { - q = dequeueActiveInputEvent(); - // If the window doesn't currently have input focus, then drop - // this event. This could be an event that came back from the - // IME dispatch but the window has lost focus in the meantime. - handled = false; - if (!mAttachInfo.mHasWindowFocus && !isTerminalInputEvent(q.mEvent)) { - Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent); - } else { - handled = (deliverInputEventPostIme(q) == EVENT_HANDLED); - } - finishInputEvent(q, handled); - } - } else { - if (DEBUG_IMF) { - Log.v(TAG, "IME finished event: seq=" + seq - + " handled=" + handled + ", event not found!"); + finishInputEvent(q); } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); } - } - private void finishInputEvent(QueuedInputEvent q, boolean handled) { + private void finishInputEvent(QueuedInputEvent q) { if (q.mReceiver != null) { + boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0; q.mReceiver.finishInputEvent(q.mEvent, handled); } else { q.mEvent.recycleIfNeededAfterDispatch(); @@ -4608,7 +4807,7 @@ public final class ViewRootImpl implements ViewParent, recycleQueuedInputEvent(q); } - private static boolean isTerminalInputEvent(InputEvent event) { + static boolean isTerminalInputEvent(InputEvent event) { if (event instanceof KeyEvent) { final KeyEvent keyEvent = (KeyEvent)event; return keyEvent.getAction() == KeyEvent.ACTION_UP; @@ -5144,22 +5343,6 @@ public final class ViewRootImpl implements ViewParent, ((RootViewSurfaceTaker)mView).setSurfaceKeepScreenOn(screenOn); } } - - static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback { - private WeakReference<ViewRootImpl> mViewAncestor; - - public InputMethodCallback(ViewRootImpl viewAncestor) { - mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor); - } - - @Override - public void finishedEvent(int seq, boolean handled) { - final ViewRootImpl viewAncestor = mViewAncestor.get(); - if (viewAncestor != null) { - viewAncestor.handleImeFinishedEvent(seq, handled); - } - } - } static class W extends IWindow.Stub { private final WeakReference<ViewRootImpl> mViewAncestor; diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 7eb26fa..0ff46e9 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -136,11 +136,11 @@ public final class WindowManagerGlobal { } } - public static IWindowSession getWindowSession(Looper mainLooper) { + public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { - InputMethodManager imm = InputMethodManager.getInstance(mainLooper); + InputMethodManager imm = InputMethodManager.getInstance(); IWindowManager windowManager = getWindowManagerService(); sWindowSession = windowManager.openSession( imm.getClient(), imm.getInputContext()); @@ -351,7 +351,7 @@ public final class WindowManagerGlobal { View view = root.getView(); if (view != null) { - InputMethodManager imm = InputMethodManager.getInstance(view.getContext()); + InputMethodManager imm = InputMethodManager.getInstance(); if (imm != null) { imm.windowDismissed(mViews[index].getWindowToken()); } 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); + } + } } } |
