Fixed StatusBar ANRs due to input event injection on UI thread.
Added a new asynchronous injection mode and made the existing
synchronization mechanism more robust.
Change-Id: I0464f70ff5cbd519dbb02686b2cb5d810fe7dbb2
diff --git a/api/current.xml b/api/current.xml
index efe6ce0..a89087a 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -180290,6 +180290,17 @@
>
<implements name="android.os.Parcelable">
</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getDevice"
return="android.view.InputDevice"
abstract="false"
@@ -180323,6 +180334,16 @@
visibility="public"
>
</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="mDeviceId"
type="int"
transient="false"
@@ -180966,17 +180987,6 @@
<parameter name="newFlags" type="int">
</parameter>
</method>
-<method name="describeContents"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
<method name="dispatch"
return="boolean"
abstract="false"
@@ -184222,17 +184232,6 @@
<parameter name="metaState" type="int">
</parameter>
</method>
-<method name="describeContents"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
<method name="findPointerIndex"
return="int"
abstract="false"
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 9b7b2f4..d6b9212 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -26,6 +26,7 @@
import android.view.IRotationWatcher;
import android.view.IWindowSession;
import android.view.KeyEvent;
+import android.view.InputEvent;
import android.view.MotionEvent;
/**
@@ -50,10 +51,13 @@
boolean inputMethodClientHasFocus(IInputMethodClient client);
// These can only be called when injecting events to your own window,
- // or by holding the INJECT_EVENTS permission.
+ // or by holding the INJECT_EVENTS permission. These methods may block
+ // until pending input events are finished being dispatched even when 'sync' is false.
+ // Avoid calling these methods on your UI thread or use the 'NoWait' version instead.
boolean injectKeyEvent(in KeyEvent ev, boolean sync);
boolean injectPointerEvent(in MotionEvent ev, boolean sync);
boolean injectTrackballEvent(in MotionEvent ev, boolean sync);
+ boolean injectInputEventNoWait(in InputEvent ev);
// These can only be called when holding the MANAGE_APP_TOKENS permission.
void pauseKeyDispatching(IBinder token);
diff --git a/core/java/android/view/InputEvent.aidl b/core/java/android/view/InputEvent.aidl
new file mode 100644
index 0000000..61acaaf
--- /dev/null
+++ b/core/java/android/view/InputEvent.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android.view.InputEvent.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+parcelable InputEvent;
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 78b73fe..9afd16e 100755
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -16,6 +16,7 @@
package android.view;
+import android.os.Parcel;
import android.os.Parcelable;
/**
@@ -25,6 +26,11 @@
protected int mDeviceId;
protected int mSource;
+ /** @hide */
+ protected static final int PARCEL_TOKEN_MOTION_EVENT = 1;
+ /** @hide */
+ protected static final int PARCEL_TOKEN_KEY_EVENT = 2;
+
/*package*/ InputEvent() {
}
@@ -69,4 +75,38 @@
public final void setSource(int source) {
mSource = source;
}
+
+ public final int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ protected final void readBaseFromParcel(Parcel in) {
+ mDeviceId = in.readInt();
+ mSource = in.readInt();
+ }
+
+ /** @hide */
+ protected final void writeBaseToParcel(Parcel out) {
+ out.writeInt(mDeviceId);
+ out.writeInt(mSource);
+ }
+
+ public static final Parcelable.Creator<InputEvent> CREATOR
+ = new Parcelable.Creator<InputEvent>() {
+ public InputEvent createFromParcel(Parcel in) {
+ int token = in.readInt();
+ if (token == PARCEL_TOKEN_KEY_EVENT) {
+ return KeyEvent.createFromParcelBody(in);
+ } else if (token == PARCEL_TOKEN_MOTION_EVENT) {
+ return MotionEvent.createFromParcelBody(in);
+ } else {
+ throw new IllegalStateException("Unexpected input event type token in parcel.");
+ }
+ }
+
+ public InputEvent[] newArray(int size) {
+ return new InputEvent[size];
+ }
+ };
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index dd0d21a3..9223e17 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1211,44 +1211,48 @@
public static final Parcelable.Creator<KeyEvent> CREATOR
= new Parcelable.Creator<KeyEvent>() {
public KeyEvent createFromParcel(Parcel in) {
- return new KeyEvent(in);
+ in.readInt(); // skip token, we already know this is a KeyEvent
+ return KeyEvent.createFromParcelBody(in);
}
public KeyEvent[] newArray(int size) {
return new KeyEvent[size];
}
};
-
- public int describeContents() {
- return 0;
+
+ /** @hide */
+ public static KeyEvent createFromParcelBody(Parcel in) {
+ return new KeyEvent(in);
+ }
+
+ private KeyEvent(Parcel in) {
+ readBaseFromParcel(in);
+
+ mAction = in.readInt();
+ mKeyCode = in.readInt();
+ mRepeatCount = in.readInt();
+ mMetaState = in.readInt();
+ mScanCode = in.readInt();
+ mFlags = in.readInt();
+ mDownTime = in.readLong();
+ mEventTime = in.readLong();
}
public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_KEY_EVENT);
+
+ writeBaseToParcel(out);
+
out.writeInt(mAction);
out.writeInt(mKeyCode);
out.writeInt(mRepeatCount);
out.writeInt(mMetaState);
- out.writeInt(mDeviceId);
- out.writeInt(mSource);
out.writeInt(mScanCode);
out.writeInt(mFlags);
out.writeLong(mDownTime);
out.writeLong(mEventTime);
}
- private KeyEvent(Parcel in) {
- mAction = in.readInt();
- mKeyCode = in.readInt();
- mRepeatCount = in.readInt();
- mMetaState = in.readInt();
- mDeviceId = in.readInt();
- mSource = in.readInt();
- mScanCode = in.readInt();
- mFlags = in.readInt();
- mDownTime = in.readLong();
- mEventTime = in.readLong();
- }
-
private native boolean native_isSystemKey(int keyCode);
private native boolean native_hasDefaultAction(int keyCode);
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index c7be751..944f555 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1453,43 +1453,8 @@
public static final Parcelable.Creator<MotionEvent> CREATOR
= new Parcelable.Creator<MotionEvent>() {
public MotionEvent createFromParcel(Parcel in) {
- final int NP = in.readInt();
- final int NS = in.readInt();
- final int NI = NP * NS * NUM_SAMPLE_DATA;
-
- MotionEvent ev = obtain(NP, NS);
- ev.mNumPointers = NP;
- ev.mNumSamples = NS;
-
- ev.mDownTimeNano = in.readLong();
- ev.mAction = in.readInt();
- ev.mXOffset = in.readFloat();
- ev.mYOffset = in.readFloat();
- ev.mXPrecision = in.readFloat();
- ev.mYPrecision = in.readFloat();
- ev.mDeviceId = in.readInt();
- ev.mSource = in.readInt();
- ev.mEdgeFlags = in.readInt();
- ev.mMetaState = in.readInt();
-
- final int[] pointerIdentifiers = ev.mPointerIdentifiers;
- for (int i = 0; i < NP; i++) {
- pointerIdentifiers[i] = in.readInt();
- }
-
- final long[] eventTimeNanoSamples = ev.mEventTimeNanoSamples;
- for (int i = 0; i < NS; i++) {
- eventTimeNanoSamples[i] = in.readLong();
- }
-
- final float[] dataSamples = ev.mDataSamples;
- for (int i = 0; i < NI; i++) {
- dataSamples[i] = in.readFloat();
- }
-
- ev.mLastEventTimeNanoSampleIndex = NS - 1;
- ev.mLastDataSampleIndex = (NS - 1) * NP * NUM_SAMPLE_DATA;
- return ev;
+ in.readInt(); // skip token, we already know this is a MotionEvent
+ return MotionEvent.createFromParcelBody(in);
}
public MotionEvent[] newArray(int size) {
@@ -1497,11 +1462,50 @@
}
};
- public int describeContents() {
- return 0;
- }
+ /** @hide */
+ public static MotionEvent createFromParcelBody(Parcel in) {
+ final int NP = in.readInt();
+ final int NS = in.readInt();
+ final int NI = NP * NS * NUM_SAMPLE_DATA;
+
+ MotionEvent ev = obtain(NP, NS);
+ ev.mNumPointers = NP;
+ ev.mNumSamples = NS;
+
+ ev.readBaseFromParcel(in);
+
+ ev.mDownTimeNano = in.readLong();
+ ev.mAction = in.readInt();
+ ev.mXOffset = in.readFloat();
+ ev.mYOffset = in.readFloat();
+ ev.mXPrecision = in.readFloat();
+ ev.mYPrecision = in.readFloat();
+ ev.mEdgeFlags = in.readInt();
+ ev.mMetaState = in.readInt();
+
+ final int[] pointerIdentifiers = ev.mPointerIdentifiers;
+ for (int i = 0; i < NP; i++) {
+ pointerIdentifiers[i] = in.readInt();
+ }
+
+ final long[] eventTimeNanoSamples = ev.mEventTimeNanoSamples;
+ for (int i = 0; i < NS; i++) {
+ eventTimeNanoSamples[i] = in.readLong();
+ }
+ final float[] dataSamples = ev.mDataSamples;
+ for (int i = 0; i < NI; i++) {
+ dataSamples[i] = in.readFloat();
+ }
+
+ ev.mLastEventTimeNanoSampleIndex = NS - 1;
+ ev.mLastDataSampleIndex = (NS - 1) * NP * NUM_SAMPLE_DATA;
+ return ev;
+ }
+
public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_MOTION_EVENT);
+
final int NP = mNumPointers;
final int NS = mNumSamples;
final int NI = NP * NS * NUM_SAMPLE_DATA;
@@ -1509,14 +1513,14 @@
out.writeInt(NP);
out.writeInt(NS);
+ writeBaseToParcel(out);
+
out.writeLong(mDownTimeNano);
out.writeInt(mAction);
out.writeFloat(mXOffset);
out.writeFloat(mYOffset);
out.writeFloat(mXPrecision);
out.writeFloat(mYPrecision);
- out.writeInt(mDeviceId);
- out.writeInt(mSource);
out.writeInt(mEdgeFlags);
out.writeInt(mMetaState);
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index 674852a..d3495fe 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -55,6 +55,22 @@
INPUT_EVENT_INJECTION_TIMED_OUT = 3
};
+/*
+ * Constants used to determine the input event injection synchronization mode.
+ */
+enum {
+ /* Injection is asynchronous and is assumed always to be successful. */
+ INPUT_EVENT_INJECTION_SYNC_NONE = 0,
+
+ /* Waits for previous events to be dispatched so that the input dispatcher can determine
+ * whether input event injection willbe permitted based on the current input focus.
+ * Does not wait for the input event to finish processing. */
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1,
+
+ /* Waits for the input event to be completely processed. */
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED = 2,
+};
+
/*
* An input target specifies how an input event is to be dispatched to a particular window
@@ -176,15 +192,14 @@
float xPrecision, float yPrecision, nsecs_t downTime) = 0;
/* Injects an input event and optionally waits for sync.
- * This method may block even if sync is false because it must wait for previous events
- * to be dispatched before it can determine whether input event injection will be
- * permitted based on the current input focus.
+ * The synchronization mode determines whether the method blocks while waiting for
+ * input injection to proceed.
* Returns one of the INPUT_EVENT_INJECTION_XXX constants.
*
* This method may be called on any thread (usually by the input manager).
*/
virtual int32_t injectInputEvent(const InputEvent* event,
- int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) = 0;
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0;
/* Preempts input dispatch in progress by making pending synchronous
* dispatches asynchronous instead. This method is generally called during a focus
@@ -241,7 +256,7 @@
float xPrecision, float yPrecision, nsecs_t downTime);
virtual int32_t injectInputEvent(const InputEvent* event,
- int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis);
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis);
virtual void preemptInputDispatch();
@@ -267,11 +282,13 @@
int32_t type;
nsecs_t eventTime;
- int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING
- int32_t injectorPid; // -1 if not injected
- int32_t injectorUid; // -1 if not injected
+ int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING
+ bool injectionIsAsync; // set to true if injection is not waiting for the result
+ int32_t injectorPid; // -1 if not injected
+ int32_t injectorUid; // -1 if not injected
bool dispatchInProgress; // initially false, set to true while dispatching
+ int32_t pendingSyncDispatches; // the number of synchronous dispatches in progress
inline bool isInjected() { return injectorPid >= 0; }
};
@@ -340,6 +357,10 @@
// headMotionSample will be initialized to tailMotionSample and tailMotionSample
// will be set to NULL.
MotionSample* tailMotionSample;
+
+ inline bool isSyncTarget() {
+ return targetFlags & InputTarget::FLAG_SYNC;
+ }
};
// A command entry captures state and behavior for an action to be performed in the
@@ -497,8 +518,7 @@
// Since there can only ever be at most one such target at a time, if there is one,
// it must be at the tail because nothing else can be enqueued after it.
inline bool hasPendingSyncTarget() {
- return ! outboundQueue.isEmpty()
- && (outboundQueue.tail.prev->targetFlags & InputTarget::FLAG_SYNC);
+ return ! outboundQueue.isEmpty() && outboundQueue.tail.prev->isSyncTarget();
}
// Gets the time since the current event was originally obtained from the input driver.
@@ -559,11 +579,12 @@
// Event injection and synchronization.
Condition mInjectionResultAvailableCondition;
- Condition mFullySynchronizedCondition;
- bool isFullySynchronizedLocked();
EventEntry* createEntryFromInputEventLocked(const InputEvent* event);
void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
+ Condition mInjectionSyncFinishedCondition;
+ void decrementPendingSyncDispatchesLocked(EventEntry* entry);
+
// Key repeat tracking.
// XXX Move this up to the input reader instead.
struct KeyRepeatState {
diff --git a/include/ui/InputManager.h b/include/ui/InputManager.h
index 7ebec10..4012c69 100644
--- a/include/ui/InputManager.h
+++ b/include/ui/InputManager.h
@@ -79,13 +79,12 @@
virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
/* Injects an input event and optionally waits for sync.
- * This method may block even if sync is false because it must wait for previous events
- * to be dispatched before it can determine whether input event injection will be
- * permitted based on the current input focus.
+ * The synchronization mode determines whether the method blocks while waiting for
+ * input injection to proceed.
* Returns one of the INPUT_EVENT_INJECTION_XXX constants.
*/
virtual int32_t injectInputEvent(const InputEvent* event,
- int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) = 0;
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0;
/* Preempts input dispatch in progress by making pending synchronous
* dispatches asynchronous instead. This method is generally called during a focus
@@ -142,7 +141,7 @@
virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
virtual int32_t injectInputEvent(const InputEvent* event,
- int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis);
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis);
virtual void preemptInputDispatch();
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index a55864b..b53f140 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -184,11 +184,6 @@
// Run any deferred commands.
skipPoll |= runCommandsLockedInterruptible();
-
- // Wake up synchronization waiters, if needed.
- if (isFullySynchronizedLocked()) {
- mFullySynchronizedCondition.broadcast();
- }
} // release lock
// If we dispatched anything, don't poll just now. Wait for the next iteration.
@@ -560,6 +555,10 @@
dispatchEntry->headMotionSample = NULL;
dispatchEntry->tailMotionSample = NULL;
+ if (dispatchEntry->isSyncTarget()) {
+ eventEntry->pendingSyncDispatches += 1;
+ }
+
// Handle the case where we could not stream a new motion sample because the consumer has
// already consumed the motion event (otherwise the corresponding dispatch entry would
// still be in the outbound queue for this connection). We set the head motion sample
@@ -789,6 +788,9 @@
}
// Finished.
connection->outboundQueue.dequeueAtHead();
+ if (dispatchEntry->isSyncTarget()) {
+ decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
+ }
mAllocator.releaseDispatchEntry(dispatchEntry);
} else {
// If the head is not in progress, then we must have already dequeued the in
@@ -854,6 +856,9 @@
if (! connection->outboundQueue.isEmpty()) {
do {
DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
+ if (dispatchEntry->isSyncTarget()) {
+ decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
+ }
mAllocator.releaseDispatchEntry(dispatchEntry);
} while (! connection->outboundQueue.isEmpty());
@@ -1097,7 +1102,7 @@
Connection* connection = mActiveConnections.itemAt(i);
if (! connection->outboundQueue.isEmpty()) {
DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev;
- if (dispatchEntry->targetFlags & InputTarget::FLAG_SYNC) {
+ if (dispatchEntry->isSyncTarget()) {
if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) {
goto NoBatchingOrStreaming;
}
@@ -1148,11 +1153,11 @@
}
int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
- int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) {
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
#if DEBUG_INBOUND_EVENT_DETAILS
LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
- "sync=%d, timeoutMillis=%d",
- event->getType(), injectorPid, injectorUid, sync, timeoutMillis);
+ "syncMode=%d, timeoutMillis=%d",
+ event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis);
#endif
nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
@@ -1167,6 +1172,10 @@
injectedEntry->injectorPid = injectorPid;
injectedEntry->injectorUid = injectorUid;
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectedEntry->injectionIsAsync = true;
+ }
+
wasEmpty = mInboundQueue.isEmpty();
mInboundQueue.enqueueAtTail(injectedEntry);
@@ -1180,37 +1189,59 @@
{ // acquire lock
AutoMutex _l(mLock);
- for (;;) {
- injectionResult = injectedEntry->injectionResult;
- if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
- break;
- }
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ } else {
+ for (;;) {
+ injectionResult = injectedEntry->injectionResult;
+ if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
+ break;
+ }
- nsecs_t remainingTimeout = endTime - now();
- if (remainingTimeout <= 0) {
- injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
- sync = false;
- break;
- }
-
- mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout);
- }
-
- if (sync) {
- while (! isFullySynchronizedLocked()) {
nsecs_t remainingTimeout = endTime - now();
if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+ LOGD("injectInputEvent - Timed out waiting for injection result "
+ "to become available.");
+#endif
injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
break;
}
- mFullySynchronizedCondition.waitRelative(mLock, remainingTimeout);
+ mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout);
+ }
+
+ if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED
+ && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) {
+ while (injectedEntry->pendingSyncDispatches != 0) {
+#if DEBUG_INJECTION
+ LOGD("injectInputEvent - Waiting for %d pending synchronous dispatches.",
+ injectedEntry->pendingSyncDispatches);
+#endif
+ nsecs_t remainingTimeout = endTime - now();
+ if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+ LOGD("injectInputEvent - Timed out waiting for pending synchronous "
+ "dispatches to finish.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+ break;
+ }
+
+ mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout);
+ }
}
}
mAllocator.releaseEventEntry(injectedEntry);
} // release lock
+#if DEBUG_INJECTION
+ LOGD("injectInputEvent - Finished with result %d. "
+ "injectorPid=%d, injectorUid=%d",
+ injectionResult, injectorPid, injectorUid);
+#endif
+
return injectionResult;
}
@@ -1222,13 +1253,35 @@
injectionResult, entry->injectorPid, entry->injectorUid);
#endif
+ if (entry->injectionIsAsync) {
+ // Log the outcome since the injector did not wait for the injection result.
+ switch (injectionResult) {
+ case INPUT_EVENT_INJECTION_SUCCEEDED:
+ LOGV("Asynchronous input event injection succeeded.");
+ break;
+ case INPUT_EVENT_INJECTION_FAILED:
+ LOGW("Asynchronous input event injection failed.");
+ break;
+ case INPUT_EVENT_INJECTION_PERMISSION_DENIED:
+ LOGW("Asynchronous input event injection permission denied.");
+ break;
+ case INPUT_EVENT_INJECTION_TIMED_OUT:
+ LOGW("Asynchronous input event injection timed out.");
+ break;
+ }
+ }
+
entry->injectionResult = injectionResult;
mInjectionResultAvailableCondition.broadcast();
}
}
-bool InputDispatcher::isFullySynchronizedLocked() {
- return mInboundQueue.isEmpty() && mActiveConnections.isEmpty();
+void InputDispatcher::decrementPendingSyncDispatchesLocked(EventEntry* entry) {
+ entry->pendingSyncDispatches -= 1;
+
+ if (entry->isInjected() && entry->pendingSyncDispatches == 0) {
+ mInjectionSyncFinishedCondition.broadcast();
+ }
}
InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked(
@@ -1498,8 +1551,10 @@
entry->dispatchInProgress = false;
entry->eventTime = eventTime;
entry->injectionResult = INPUT_EVENT_INJECTION_PENDING;
+ entry->injectionIsAsync = false;
entry->injectorPid = -1;
entry->injectorUid = -1;
+ entry->pendingSyncDispatches = 0;
}
InputDispatcher::ConfigurationChangedEntry*
diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp
index bf23479..ed4f07b 100644
--- a/libs/ui/InputManager.cpp
+++ b/libs/ui/InputManager.cpp
@@ -81,8 +81,8 @@
}
int32_t InputManager::injectInputEvent(const InputEvent* event,
- int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) {
- return mDispatcher->injectInputEvent(event, injectorPid, injectorUid, sync, timeoutMillis);
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
+ return mDispatcher->injectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis);
}
void InputManager::preemptInputDispatch() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
index 4ab91b0..b0508b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
@@ -26,6 +26,7 @@
import android.util.AttributeSet;
import android.util.Slog;
import android.view.IWindowManager;
+import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.ImageView;
@@ -71,7 +72,8 @@
mDownTime = SystemClock.uptimeMillis();
mRepeat = 0;
mSending = true;
- sendEvent(KeyEvent.ACTION_DOWN, mDownTime);
+ sendEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_SOFT_KEYBOARD, mDownTime);
setPressed(true);
break;
case MotionEvent.ACTION_MOVE:
@@ -80,7 +82,9 @@
y = (int)ev.getY();
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
mSending = false;
- sendEvent(KeyEvent.ACTION_UP);
+ sendEvent(KeyEvent.ACTION_UP,
+ KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_SOFT_KEYBOARD
+ | KeyEvent.FLAG_CANCELED);
setPressed(false);
}
}
@@ -89,7 +93,8 @@
case MotionEvent.ACTION_CANCEL:
if (mSending) {
mSending = false;
- sendEvent(KeyEvent.ACTION_UP);
+ sendEvent(KeyEvent.ACTION_UP,
+ KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_SOFT_KEYBOARD);
setPressed(false);
}
break;
@@ -98,15 +103,16 @@
return true;
}
- void sendEvent(int action) {
- sendEvent(action, SystemClock.uptimeMillis());
+ void sendEvent(int action, int flags) {
+ sendEvent(action, flags, SystemClock.uptimeMillis());
}
- void sendEvent(int action, long when) {
- final KeyEvent ev = new KeyEvent(mDownTime, mDownTime, action, mCode, mRepeat);
+ void sendEvent(int action, int flags, long when) {
+ final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, mRepeat,
+ 0, 0, 0, flags, InputDevice.SOURCE_KEYBOARD);
try {
Slog.d(StatusBarService.TAG, "injecting event " + ev);
- mWindowManager.injectKeyEvent(ev, false);
+ mWindowManager.injectInputEventNoWait(ev);
} catch (RemoteException ex) {
// System process is dead
}
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index 9a1d017..9195123 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -29,6 +29,7 @@
import android.util.Slog;
import android.util.Xml;
import android.view.InputChannel;
+import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
@@ -76,10 +77,8 @@
int[] keyCodes, boolean[] keyExists);
private static native void nativeRegisterInputChannel(InputChannel inputChannel);
private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
- private static native int nativeInjectKeyEvent(KeyEvent event,
- int injectorPid, int injectorUid, boolean sync, int timeoutMillis);
- private static native int nativeInjectMotionEvent(MotionEvent event,
- int injectorPid, int injectorUid, boolean sync, int timeoutMillis);
+ private static native int nativeInjectInputEvent(InputEvent event,
+ int injectorPid, int injectorUid, int syncMode, int timeoutMillis);
private static native void nativeSetInputWindows(InputWindow[] windows);
private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen);
private static native void nativeSetFocusedApplication(InputApplication application);
@@ -92,6 +91,11 @@
static final int INPUT_EVENT_INJECTION_FAILED = 2;
static final int INPUT_EVENT_INJECTION_TIMED_OUT = 3;
+ // Input event injection synchronization modes defined in InputDispatcher.h
+ static final int INPUT_EVENT_INJECTION_SYNC_NONE = 0;
+ static final int INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1;
+ static final int INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH = 2;
+
// Key states (may be returned by queries about the current state of a
// particular key code, scan code or switch).
@@ -107,7 +111,6 @@
/** The key is down but is a virtual key press that is being emulated by the system. */
public static final int KEY_STATE_VIRTUAL = 2;
-
public InputManager(Context context, WindowManagerService windowManagerService) {
this.mContext = context;
this.mWindowManagerService = windowManagerService;
@@ -239,19 +242,30 @@
}
/**
- * Injects a key event into the event system on behalf of an application.
- * This method may block even if sync is false because it must wait for previous events
- * to be dispatched before it can determine whether input event injection will be
- * permitted based on the current input focus.
+ * Injects an input event into the event system on behalf of an application.
+ * The synchronization mode determines whether the method blocks while waiting for
+ * input injection to proceed.
+ *
+ * {@link #INPUT_EVENT_INJECTION_SYNC_NONE} never blocks. Injection is asynchronous and
+ * is assumed always to be successful.
+ *
+ * {@link #INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT} waits for previous events to be
+ * dispatched so that the input dispatcher can determine whether input event injection will
+ * be permitted based on the current input focus. Does not wait for the input event to
+ * finish processing.
+ *
+ * {@link #INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH} waits for the input event to
+ * be completely processed.
+ *
* @param event The event to inject.
* @param injectorPid The pid of the injecting application.
* @param injectorUid The uid of the injecting application.
- * @param sync If true, waits for the event to be completed before returning.
+ * @param syncMode The synchronization mode.
* @param timeoutMillis The injection timeout in milliseconds.
* @return One of the INPUT_EVENT_INJECTION_XXX constants.
*/
- public int injectKeyEvent(KeyEvent event, int injectorPid, int injectorUid,
- boolean sync, int timeoutMillis) {
+ public int injectInputEvent(InputEvent event, int injectorPid, int injectorUid,
+ int syncMode, int timeoutMillis) {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
@@ -261,38 +275,8 @@
if (timeoutMillis <= 0) {
throw new IllegalArgumentException("timeoutMillis must be positive");
}
-
- return nativeInjectKeyEvent(event, injectorPid, injectorUid,
- sync, timeoutMillis);
- }
-
- /**
- * Injects a motion event into the event system on behalf of an application.
- * This method may block even if sync is false because it must wait for previous events
- * to be dispatched before it can determine whether input event injection will be
- * permitted based on the current input focus.
- * @param event The event to inject.
- * @param sync If true, waits for the event to be completed before returning.
- * @param injectorPid The pid of the injecting application.
- * @param injectorUid The uid of the injecting application.
- * @param sync If true, waits for the event to be completed before returning.
- * @param timeoutMillis The injection timeout in milliseconds.
- * @return One of the INPUT_EVENT_INJECTION_XXX constants.
- */
- public int injectMotionEvent(MotionEvent event, int injectorPid, int injectorUid,
- boolean sync, int timeoutMillis) {
- if (event == null) {
- throw new IllegalArgumentException("event must not be null");
- }
- if (injectorPid < 0 || injectorUid < 0) {
- throw new IllegalArgumentException("injectorPid and injectorUid must not be negative.");
- }
- if (timeoutMillis <= 0) {
- throw new IllegalArgumentException("timeoutMillis must be positive");
- }
-
- return nativeInjectMotionEvent(event, injectorPid, injectorUid,
- sync, timeoutMillis);
+
+ return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis);
}
public void setInputWindows(InputWindow[] windows) {
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 1a8efa1..1fdceef 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -103,6 +103,7 @@
import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.InputDevice;
+import android.view.InputEvent;
import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -5380,6 +5381,8 @@
/**
* Injects a keystroke event into the UI.
+ * Even when sync is false, this method may block while waiting for current
+ * input events to be dispatched.
*
* @param ev A motion event describing the keystroke action. (Be sure to use
* {@link SystemClock#uptimeMillis()} as the timebase.)
@@ -5412,8 +5415,10 @@
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
- final int result = mInputManager.injectKeyEvent(newEvent,
- pid, uid, sync, INJECTION_TIMEOUT_MILLIS);
+ final int result = mInputManager.injectInputEvent(newEvent, pid, uid,
+ sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH
+ : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT,
+ INJECTION_TIMEOUT_MILLIS);
Binder.restoreCallingIdentity(ident);
return reportInjectionResult(result);
@@ -5421,6 +5426,8 @@
/**
* Inject a pointer (touch) event into the UI.
+ * Even when sync is false, this method may block while waiting for current
+ * input events to be dispatched.
*
* @param ev A motion event describing the pointer (touch) action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
@@ -5438,8 +5445,10 @@
newEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
}
- final int result = mInputManager.injectMotionEvent(newEvent,
- pid, uid, sync, INJECTION_TIMEOUT_MILLIS);
+ final int result = mInputManager.injectInputEvent(newEvent, pid, uid,
+ sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH
+ : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT,
+ INJECTION_TIMEOUT_MILLIS);
Binder.restoreCallingIdentity(ident);
return reportInjectionResult(result);
@@ -5447,6 +5456,8 @@
/**
* Inject a trackball (navigation device) event into the UI.
+ * Even when sync is false, this method may block while waiting for current
+ * input events to be dispatched.
*
* @param ev A motion event describing the trackball action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
@@ -5464,8 +5475,31 @@
newEvent.setSource(InputDevice.SOURCE_TRACKBALL);
}
- final int result = mInputManager.injectMotionEvent(newEvent,
- pid, uid, sync, INJECTION_TIMEOUT_MILLIS);
+ final int result = mInputManager.injectInputEvent(newEvent, pid, uid,
+ sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH
+ : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT,
+ INJECTION_TIMEOUT_MILLIS);
+
+ Binder.restoreCallingIdentity(ident);
+ return reportInjectionResult(result);
+ }
+
+ /**
+ * Inject an input event into the UI without waiting for dispatch to commence.
+ * This variant is useful for fire-and-forget input event injection. It does not
+ * block any longer than it takes to enqueue the input event.
+ *
+ * @param ev An input event. (Be sure to set the input source correctly.)
+ * @return Returns true if event was dispatched, false if it was dropped for any reason
+ */
+ public boolean injectInputEventNoWait(InputEvent ev) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+
+ final int result = mInputManager.injectInputEvent(ev, pid, uid,
+ InputManager.INPUT_EVENT_INJECTION_SYNC_NONE,
+ INJECTION_TIMEOUT_MILLIS);
Binder.restoreCallingIdentity(ident);
return reportInjectionResult(result);
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index a332376..0982b32 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -180,6 +180,14 @@
jfieldID token;
} gInputApplicationClassInfo;
+static struct {
+ jclass clazz;
+} gKeyEventClassInfo;
+
+static struct {
+ jclass clazz;
+} gMotionEventClassInfo;
+
// ----------------------------------------------------------------------------
static inline nsecs_t now() {
@@ -2051,32 +2059,29 @@
}
}
-static jint android_server_InputManager_nativeInjectKeyEvent(JNIEnv* env, jclass clazz,
- jobject keyEventObj, jint injectorPid, jint injectorUid,
- jboolean sync, jint timeoutMillis) {
+static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jclass clazz,
+ jobject inputEventObj, jint injectorPid, jint injectorUid,
+ jint syncMode, jint timeoutMillis) {
if (checkInputManagerUnitialized(env)) {
return INPUT_EVENT_INJECTION_FAILED;
}
- KeyEvent keyEvent;
- android_view_KeyEvent_toNative(env, keyEventObj, & keyEvent);
+ if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
+ KeyEvent keyEvent;
+ android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
- return gNativeInputManager->getInputManager()->injectInputEvent(& keyEvent,
- injectorPid, injectorUid, sync, timeoutMillis);
-}
+ return gNativeInputManager->getInputManager()->injectInputEvent(& keyEvent,
+ injectorPid, injectorUid, syncMode, timeoutMillis);
+ } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {
+ MotionEvent motionEvent;
+ android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent);
-static jint android_server_InputManager_nativeInjectMotionEvent(JNIEnv* env, jclass clazz,
- jobject motionEventObj, jint injectorPid, jint injectorUid,
- jboolean sync, jint timeoutMillis) {
- if (checkInputManagerUnitialized(env)) {
+ return gNativeInputManager->getInputManager()->injectInputEvent(& motionEvent,
+ injectorPid, injectorUid, syncMode, timeoutMillis);
+ } else {
+ jniThrowRuntimeException(env, "Invalid input event type.");
return INPUT_EVENT_INJECTION_FAILED;
}
-
- MotionEvent motionEvent;
- android_view_MotionEvent_toNative(env, motionEventObj, & motionEvent);
-
- return gNativeInputManager->getInputManager()->injectInputEvent(& motionEvent,
- injectorPid, injectorUid, sync, timeoutMillis);
}
static void android_server_InputManager_nativeSetInputWindows(JNIEnv* env, jclass clazz,
@@ -2148,10 +2153,8 @@
(void*) android_server_InputManager_nativeRegisterInputChannel },
{ "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V",
(void*) android_server_InputManager_nativeUnregisterInputChannel },
- { "nativeInjectKeyEvent", "(Landroid/view/KeyEvent;IIZI)I",
- (void*) android_server_InputManager_nativeInjectKeyEvent },
- { "nativeInjectMotionEvent", "(Landroid/view/MotionEvent;IIZI)I",
- (void*) android_server_InputManager_nativeInjectMotionEvent },
+ { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIII)I",
+ (void*) android_server_InputManager_nativeInjectInputEvent },
{ "nativeSetInputWindows", "([Lcom/android/server/InputWindow;)V",
(void*) android_server_InputManager_nativeSetInputWindows },
{ "nativeSetFocusedApplication", "(Lcom/android/server/InputApplication;)V",
@@ -2318,6 +2321,14 @@
GET_FIELD_ID(gInputApplicationClassInfo.token, gInputApplicationClassInfo.clazz,
"token", "Ljava/lang/Object;");
+ // KeyEvent
+
+ FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent");
+
+ // MotionEVent
+
+ FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent");
+
return 0;
}