summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ContextImpl.java6
-rw-r--r--core/java/android/app/NativeActivity.java39
-rw-r--r--core/java/android/app/WallpaperManager.java6
-rw-r--r--core/java/android/content/Context.java6
-rw-r--r--core/java/android/os/LatencyTimer.java94
-rw-r--r--core/java/android/os/Looper.java8
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java1429
-rw-r--r--core/java/android/view/WindowManagerGlobal.java6
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java329
-rw-r--r--packages/SystemUI/src/com/android/systemui/UniverseBackground.java2
-rw-r--r--tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java2
-rw-r--r--tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java29
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java2
14 files changed, 1031 insertions, 929 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);
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/UniverseBackground.java b/packages/SystemUI/src/com/android/systemui/UniverseBackground.java
index 7628754..f859880 100644
--- a/packages/SystemUI/src/com/android/systemui/UniverseBackground.java
+++ b/packages/SystemUI/src/com/android/systemui/UniverseBackground.java
@@ -97,7 +97,7 @@ public class UniverseBackground extends FrameLayout {
public UniverseBackground(Context context) {
super(context);
setBackgroundColor(0xff000000);
- mSession = WindowManagerGlobal.getWindowSession(context.getMainLooper());
+ mSession = WindowManagerGlobal.getWindowSession();
mContent = View.inflate(context, R.layout.universe, null);
addView(mContent);
mContent.findViewById(R.id.close).setOnClickListener(new View.OnClickListener() {
diff --git a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
index 7a6e52e..dc4f9c8 100644
--- a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
+++ b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
@@ -22,6 +22,6 @@ package android.view.inputmethod;
public class InputMethodManager_Accessor {
public static void resetInstance() {
- InputMethodManager.mInstance = null;
+ InputMethodManager.sInstance = null;
}
}
diff --git a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
index f056040..7c98847 100644
--- a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
@@ -35,28 +35,15 @@ public class InputMethodManager_Delegate {
// ---- Overridden methods ----
@LayoutlibDelegate
- /*package*/ static InputMethodManager getInstance(Looper mainLooper) {
- synchronized (InputMethodManager.mInstanceSync) {
- if (InputMethodManager.mInstance != null) {
- return InputMethodManager.mInstance;
+ /*package*/ static InputMethodManager getInstance() {
+ synchronized (InputMethodManager.class) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm == null) {
+ imm = new InputMethodManager(
+ new BridgeIInputMethodManager(), Looper.getMainLooper());
+ InputMethodManager.sInstance = imm;
}
-
- InputMethodManager.mInstance = new InputMethodManager(new BridgeIInputMethodManager(),
- mainLooper);
- }
- return InputMethodManager.mInstance;
- }
-
- @LayoutlibDelegate
- /*package*/ static InputMethodManager getInstance(Context context) {
- synchronized (InputMethodManager.mInstanceSync) {
- if (InputMethodManager.mInstance != null) {
- return InputMethodManager.mInstance;
- }
-
- InputMethodManager.mInstance = new InputMethodManager(new BridgeIInputMethodManager(),
- Looper.myLooper());
+ return imm;
}
- return InputMethodManager.mInstance;
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index f109e39..cbefd3d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -232,7 +232,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso
sCurrentContext = mContext;
// create an InputMethodManager
- InputMethodManager.getInstance(Looper.myLooper());
+ InputMethodManager.getInstance();
LayoutLog currentLog = mParams.getLog();
Bridge.setLog(currentLog);