diff options
| author | Jeff Brown <jeffbrown@google.com> | 2013-04-04 23:04:03 -0700 |
|---|---|---|
| committer | Jeff Brown <jeffbrown@google.com> | 2013-04-08 15:31:47 -0700 |
| commit | f9e989d5f09e72f5c9a59d713521f37d3fdd93dd (patch) | |
| tree | 1495fe6c1ac72db7420839e7ec068e1e152571fa /core/java/android | |
| parent | 1951ce86c21445ac191e4d2d95233f4f5c096b56 (diff) | |
| download | frameworks_base-f9e989d5f09e72f5c9a59d713521f37d3fdd93dd.zip frameworks_base-f9e989d5f09e72f5c9a59d713521f37d3fdd93dd.tar.gz frameworks_base-f9e989d5f09e72f5c9a59d713521f37d3fdd93dd.tar.bz2 | |
Queues, queues, queues and input.
Redesigned how ViewRootImpl delivers input events to views,
the IME and to native activities to fix several issues.
The prior change to make IME input event delegation use
InputChannels failed to take into account that InputMethodManager
is a singleton attached to the main looper whereas UI may be
attached to any looper. Consequently interactions with the
InputChannel might occur on the wrong thread. Fixed this
problem by checking the current thread and posting input
events or callbacks to the correct looper when necessary.
NativeActivity has also been broken for a while because the
default event handling logic for joysticks and touch navigation
was unable to dispatch events back into the native activity.
In particular, this meant that DPad synthesis from touch navigation
would not work in any native activity. The plan is to fix
this problem by passing all events through ViewRootImpl as usual
then forwarding them to native activity as needed. This should
greatly simplify IME pre-dispatch and system key handling
and make everything more robust overall.
Fixed issues related to when input events are synthesized.
In particular, added a more robust mechanism to ensure that
synthetic events are canceled appropriately when we discover
that events are no longer being resynthesized (because the
application or IME is handling or dropping them).
The new design is structured as a pipeline with a chain of
responsibility consisting of InputStage objects. Each InputStage
is responsible for some part of handling each input event
such as dispatching to the view hierarchy or to the IME.
As a stage processes an input event, it has the option of
finishing the event, forwarding the event to the next stage
or handling the event asynchronously. Some queueing logic
takes care to ensure that events are forwarded downstream in
the correct order even if they are handled out of order
by a given stage.
Cleaned up the InputMethodManager singleton initialization logic
to make it clearer that it must be attached to the main looper.
We don't actually need to pass this looper around.
Deleted the LatencyTimer class since no one uses it and we have
better ways of measuring latency these days using systrace.
Added a hidden helper to Looper to determine whether the current
thread is the indicated Looper thread.
Note: NativeActivity's IME dispatch is broken by this patch.
This will be fixed later in another patch.
Bug: 8473020
Change-Id: Iac2a1277545195a7a0137bbbdf04514c29165c60
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); + } + } } } |
