Rewrite input handling for native applications
Bug: 8473020 Change-Id: Ic4353d8924ab877bec21aff8c2dba9fe725bf906
This commit is contained in:
@@ -27,7 +27,6 @@ import android.os.Environment;
|
||||
import android.os.Looper;
|
||||
import android.os.MessageQueue;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.InputChannel;
|
||||
import android.view.InputQueue;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Surface;
|
||||
@@ -111,11 +110,9 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
|
||||
int format, int width, int height);
|
||||
private native void onSurfaceRedrawNeededNative(int handle, Surface surface);
|
||||
private native void onSurfaceDestroyedNative(int handle);
|
||||
private native void onInputChannelCreatedNative(int handle, InputChannel channel);
|
||||
private native void onInputChannelDestroyedNative(int handle, InputChannel channel);
|
||||
private native void onInputQueueCreatedNative(int handle, int queuePtr);
|
||||
private native void onInputQueueDestroyedNative(int handle, int queuePtr);
|
||||
private native void onContentRectChangedNative(int handle, int x, int y, int w, int h);
|
||||
private native void dispatchKeyEventNative(int handle, KeyEvent event);
|
||||
private native void finishPreDispatchKeyEventNative(int handle, int seq, boolean handled);
|
||||
|
||||
static class NativeContentView extends View {
|
||||
NativeActivity mActivity;
|
||||
@@ -197,7 +194,7 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
|
||||
mCurSurfaceHolder = null;
|
||||
}
|
||||
if (mCurInputQueue != null) {
|
||||
onInputChannelDestroyedNative(mNativeHandle, mCurInputQueue.getInputChannel());
|
||||
onInputQueueDestroyedNative(mNativeHandle, mCurInputQueue.getNativePtr());
|
||||
mCurInputQueue = null;
|
||||
}
|
||||
unloadNativeCode(mNativeHandle);
|
||||
@@ -261,18 +258,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (mDispatchingUnhandledKey) {
|
||||
return super.dispatchKeyEvent(event);
|
||||
} else {
|
||||
// Key events from the IME do not go through the input channel;
|
||||
// we need to intercept them here to hand to the application.
|
||||
dispatchKeyEventNative(mNativeHandle, event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
if (!mDestroyed) {
|
||||
mCurSurfaceHolder = holder;
|
||||
@@ -304,14 +289,14 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
|
||||
public void onInputQueueCreated(InputQueue queue) {
|
||||
if (!mDestroyed) {
|
||||
mCurInputQueue = queue;
|
||||
onInputChannelCreatedNative(mNativeHandle, queue.getInputChannel());
|
||||
onInputQueueCreatedNative(mNativeHandle, queue.getNativePtr());
|
||||
}
|
||||
}
|
||||
|
||||
public void onInputQueueDestroyed(InputQueue queue) {
|
||||
mCurInputQueue = null;
|
||||
if (!mDestroyed) {
|
||||
onInputChannelDestroyedNative(mNativeHandle, queue.getInputChannel());
|
||||
onInputQueueDestroyedNative(mNativeHandle, queue.getNativePtr());
|
||||
mCurInputQueue = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,25 +317,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
|
||||
}
|
||||
}
|
||||
|
||||
boolean dispatchUnhandledKeyEvent(KeyEvent event) {
|
||||
try {
|
||||
mDispatchingUnhandledKey = true;
|
||||
View decor = getWindow().getDecorView();
|
||||
if (decor != null) {
|
||||
return decor.dispatchKeyEvent(event);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
mDispatchingUnhandledKey = false;
|
||||
}
|
||||
}
|
||||
|
||||
void preDispatchKeyEvent(KeyEvent event, int seq) {
|
||||
// FIXME: Input dispatch should be redirected back through ViewRootImpl again.
|
||||
finishPreDispatchKeyEventNative(mNativeHandle, seq, false);
|
||||
}
|
||||
|
||||
void setWindowFlags(int flags, int mask) {
|
||||
getWindow().setFlags(flags, mask);
|
||||
}
|
||||
|
||||
@@ -16,11 +16,127 @@
|
||||
|
||||
package android.view;
|
||||
|
||||
import dalvik.system.CloseGuard;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.MessageQueue;
|
||||
import android.util.Pools.Pool;
|
||||
import android.util.Pools.SimplePool;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* An input queue provides a mechanism for an application to receive incoming
|
||||
* input events. Currently only usable from native code.
|
||||
*/
|
||||
public final class InputQueue {
|
||||
private final SparseArray<ActiveInputEvent> mActiveEventArray =
|
||||
new SparseArray<ActiveInputEvent>(20);
|
||||
private final Pool<ActiveInputEvent> mActiveInputEventPool =
|
||||
new SimplePool<ActiveInputEvent>(20);
|
||||
|
||||
private final CloseGuard mCloseGuard = CloseGuard.get();
|
||||
|
||||
private int mPtr;
|
||||
|
||||
private static native int nativeInit(WeakReference<InputQueue> weakQueue,
|
||||
MessageQueue messageQueue);
|
||||
private static native int nativeSendKeyEvent(int ptr, KeyEvent e, boolean preDispatch);
|
||||
private static native int nativeSendMotionEvent(int ptr, MotionEvent e);
|
||||
private static native void nativeDispose(int ptr);
|
||||
|
||||
/** @hide */
|
||||
public InputQueue() {
|
||||
mPtr = nativeInit(new WeakReference<InputQueue>(this), Looper.myQueue());
|
||||
|
||||
mCloseGuard.open("dispose");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
dispose(true);
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void dispose() {
|
||||
dispose(false);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void dispose(boolean finalized) {
|
||||
if (mCloseGuard != null) {
|
||||
if (finalized) {
|
||||
mCloseGuard.warnIfOpen();
|
||||
}
|
||||
mCloseGuard.close();
|
||||
}
|
||||
|
||||
if (mPtr != 0) {
|
||||
nativeDispose(mPtr);
|
||||
mPtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int getNativePtr() {
|
||||
return mPtr;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void sendInputEvent(InputEvent e, Object token, boolean predispatch,
|
||||
FinishedInputEventCallback callback) {
|
||||
ActiveInputEvent event = obtainActiveInputEvent(token, callback);
|
||||
int id;
|
||||
if (e instanceof KeyEvent) {
|
||||
id = nativeSendKeyEvent(mPtr, (KeyEvent) e, predispatch);
|
||||
} else {
|
||||
id = nativeSendMotionEvent(mPtr, (MotionEvent) e);
|
||||
}
|
||||
mActiveEventArray.put(id, event);
|
||||
}
|
||||
|
||||
private void finishInputEvent(int id, boolean handled) {
|
||||
int index = mActiveEventArray.indexOfKey(id);
|
||||
if (index >= 0) {
|
||||
ActiveInputEvent e = mActiveEventArray.valueAt(index);
|
||||
mActiveEventArray.removeAt(index);
|
||||
e.mCallback.onFinishedInputEvent(e.mToken, handled);
|
||||
recycleActiveInputEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
private ActiveInputEvent obtainActiveInputEvent(Object token,
|
||||
FinishedInputEventCallback callback) {
|
||||
ActiveInputEvent e = mActiveInputEventPool.acquire();
|
||||
if (e == null) {
|
||||
e = new ActiveInputEvent();
|
||||
}
|
||||
e.mToken = token;
|
||||
e.mCallback = callback;
|
||||
return e;
|
||||
}
|
||||
|
||||
private void recycleActiveInputEvent(ActiveInputEvent e) {
|
||||
e.recycle();
|
||||
mActiveInputEventPool.release(e);
|
||||
}
|
||||
|
||||
private final class ActiveInputEvent {
|
||||
public Object mToken;
|
||||
public FinishedInputEventCallback mCallback;
|
||||
|
||||
public void recycle() {
|
||||
mToken = null;
|
||||
mCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to receive notification of when an InputQueue is associated
|
||||
* and dissociated with a thread.
|
||||
@@ -31,7 +147,7 @@ public final class InputQueue {
|
||||
* thread making this call, so it can start receiving events from it.
|
||||
*/
|
||||
void onInputQueueCreated(InputQueue queue);
|
||||
|
||||
|
||||
/**
|
||||
* Called when the given InputQueue is no longer associated with
|
||||
* the thread and thus not dispatching events.
|
||||
@@ -39,15 +155,9 @@ public final class InputQueue {
|
||||
void onInputQueueDestroyed(InputQueue queue);
|
||||
}
|
||||
|
||||
final InputChannel mChannel;
|
||||
|
||||
/** @hide */
|
||||
public InputQueue(InputChannel channel) {
|
||||
mChannel = channel;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public InputChannel getInputChannel() {
|
||||
return mChannel;
|
||||
public static interface FinishedInputEventCallback {
|
||||
void onFinishedInputEvent(Object token, boolean handled);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1232,6 +1232,12 @@ public class KeyEvent extends InputEvent implements Parcelable {
|
||||
*/
|
||||
public static final int FLAG_FALLBACK = 0x400;
|
||||
|
||||
/**
|
||||
* Signifies that the key is being predispatched.
|
||||
* @hide
|
||||
*/
|
||||
public static final int FLAG_PREDISPATCH = 0x20000000;
|
||||
|
||||
/**
|
||||
* Private control to determine when an app is tracking a key sequence.
|
||||
* @hide
|
||||
|
||||
@@ -597,12 +597,11 @@ public final class ViewRootImpl implements ViewParent,
|
||||
}
|
||||
if (mInputChannel != null) {
|
||||
if (mInputQueueCallback != null) {
|
||||
mInputQueue = new InputQueue(mInputChannel);
|
||||
mInputQueue = new InputQueue();
|
||||
mInputQueueCallback.onInputQueueCreated(mInputQueue);
|
||||
} else {
|
||||
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
|
||||
Looper.myLooper());
|
||||
}
|
||||
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
|
||||
Looper.myLooper());
|
||||
}
|
||||
|
||||
view.assignParent(this);
|
||||
@@ -2822,9 +2821,11 @@ public final class ViewRootImpl implements ViewParent,
|
||||
|
||||
if (mInputQueueCallback != null && mInputQueue != null) {
|
||||
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
|
||||
mInputQueue.dispose();
|
||||
mInputQueueCallback = null;
|
||||
mInputQueue = null;
|
||||
} else if (mInputEventReceiver != null) {
|
||||
}
|
||||
if (mInputEventReceiver != null) {
|
||||
mInputEventReceiver.dispose();
|
||||
mInputEventReceiver = null;
|
||||
}
|
||||
@@ -3347,6 +3348,15 @@ public final class ViewRootImpl implements ViewParent,
|
||||
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
|
||||
forward(q);
|
||||
} else if (mView == null || !mAdded) {
|
||||
Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent);
|
||||
finish(q, false);
|
||||
} else if (!mAttachInfo.mHasWindowFocus &&
|
||||
!q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) &&
|
||||
!isTerminalInputEvent(q.mEvent)) {
|
||||
// If this is a focused event and the window doesn't currently have input focus,
|
||||
// then drop this event. This could be an event that came back from the previous
|
||||
// stage but the window has lost focus in the meantime.
|
||||
Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent);
|
||||
finish(q, false);
|
||||
} else {
|
||||
apply(q, onProcess(q));
|
||||
@@ -3547,15 +3557,30 @@ public final class ViewRootImpl implements ViewParent,
|
||||
* Delivers pre-ime input events to a native activity.
|
||||
* Does not support pointer events.
|
||||
*/
|
||||
final class NativePreImeInputStage extends AsyncInputStage {
|
||||
final class NativePreImeInputStage extends AsyncInputStage
|
||||
implements InputQueue.FinishedInputEventCallback {
|
||||
public NativePreImeInputStage(InputStage next, String traceCounter) {
|
||||
super(next, traceCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int onProcess(QueuedInputEvent q) {
|
||||
if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
|
||||
mInputQueue.sendInputEvent(q.mEvent, q, true, this);
|
||||
return DEFER;
|
||||
}
|
||||
return FORWARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishedInputEvent(Object token, boolean handled) {
|
||||
QueuedInputEvent q = (QueuedInputEvent)token;
|
||||
if (handled) {
|
||||
finish(q, true);
|
||||
return;
|
||||
}
|
||||
forward(q);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3621,16 +3646,6 @@ public final class ViewRootImpl implements ViewParent,
|
||||
finish(q, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -3702,15 +3717,30 @@ public final class ViewRootImpl implements ViewParent,
|
||||
/**
|
||||
* Delivers post-ime input events to a native activity.
|
||||
*/
|
||||
final class NativePostImeInputStage extends AsyncInputStage {
|
||||
final class NativePostImeInputStage extends AsyncInputStage
|
||||
implements InputQueue.FinishedInputEventCallback {
|
||||
public NativePostImeInputStage(InputStage next, String traceCounter) {
|
||||
super(next, traceCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int onProcess(QueuedInputEvent q) {
|
||||
if (mInputQueue != null) {
|
||||
mInputQueue.sendInputEvent(q.mEvent, q, false, this);
|
||||
return DEFER;
|
||||
}
|
||||
return FORWARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishedInputEvent(Object token, boolean handled) {
|
||||
QueuedInputEvent q = (QueuedInputEvent)token;
|
||||
if (handled) {
|
||||
finish(q, true);
|
||||
return;
|
||||
}
|
||||
forward(q);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,7 @@ LOCAL_SRC_FILES:= \
|
||||
android_view_InputDevice.cpp \
|
||||
android_view_InputEventReceiver.cpp \
|
||||
android_view_InputEventSender.cpp \
|
||||
android_view_InputQueue.cpp \
|
||||
android_view_KeyEvent.cpp \
|
||||
android_view_KeyCharacterMap.cpp \
|
||||
android_view_HardwareRenderer.cpp \
|
||||
|
||||
@@ -164,6 +164,7 @@ extern int register_android_view_InputChannel(JNIEnv* env);
|
||||
extern int register_android_view_InputDevice(JNIEnv* env);
|
||||
extern int register_android_view_InputEventReceiver(JNIEnv* env);
|
||||
extern int register_android_view_InputEventSender(JNIEnv* env);
|
||||
extern int register_android_view_InputQueue(JNIEnv* env);
|
||||
extern int register_android_view_KeyCharacterMap(JNIEnv *env);
|
||||
extern int register_android_view_KeyEvent(JNIEnv* env);
|
||||
extern int register_android_view_MotionEvent(JNIEnv* env);
|
||||
@@ -1197,6 +1198,7 @@ static const RegJNIRec gRegJNI[] = {
|
||||
REG_JNI(register_android_view_InputChannel),
|
||||
REG_JNI(register_android_view_InputEventReceiver),
|
||||
REG_JNI(register_android_view_InputEventSender),
|
||||
REG_JNI(register_android_view_InputQueue),
|
||||
REG_JNI(register_android_view_KeyEvent),
|
||||
REG_JNI(register_android_view_MotionEvent),
|
||||
REG_JNI(register_android_view_PointerIcon),
|
||||
|
||||
@@ -45,8 +45,6 @@ namespace android
|
||||
{
|
||||
|
||||
static struct {
|
||||
jmethodID dispatchUnhandledKeyEvent;
|
||||
jmethodID preDispatchKeyEvent;
|
||||
jmethodID finish;
|
||||
jmethodID setWindowFlags;
|
||||
jmethodID setWindowFormat;
|
||||
@@ -63,8 +61,7 @@ struct ActivityWork {
|
||||
};
|
||||
|
||||
enum {
|
||||
CMD_DEF_KEY = 1,
|
||||
CMD_FINISH,
|
||||
CMD_FINISH = 1,
|
||||
CMD_SET_WINDOW_FORMAT,
|
||||
CMD_SET_WINDOW_FLAGS,
|
||||
CMD_SHOW_SOFT_INPUT,
|
||||
@@ -101,299 +98,6 @@ static bool read_work(int fd, ActivityWork* outWork) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
} // namespace android
|
||||
|
||||
using namespace android;
|
||||
|
||||
AInputQueue::AInputQueue(const sp<InputChannel>& channel, int workWrite) :
|
||||
mWorkWrite(workWrite), mConsumer(channel), mSeq(0) {
|
||||
int msgpipe[2];
|
||||
if (pipe(msgpipe)) {
|
||||
ALOGW("could not create pipe: %s", strerror(errno));
|
||||
mDispatchKeyRead = mDispatchKeyWrite = -1;
|
||||
} else {
|
||||
mDispatchKeyRead = msgpipe[0];
|
||||
mDispatchKeyWrite = msgpipe[1];
|
||||
int result = fcntl(mDispatchKeyRead, F_SETFL, O_NONBLOCK);
|
||||
SLOGW_IF(result != 0, "Could not make AInputQueue read pipe "
|
||||
"non-blocking: %s", strerror(errno));
|
||||
result = fcntl(mDispatchKeyWrite, F_SETFL, O_NONBLOCK);
|
||||
SLOGW_IF(result != 0, "Could not make AInputQueue write pipe "
|
||||
"non-blocking: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
AInputQueue::~AInputQueue() {
|
||||
close(mDispatchKeyRead);
|
||||
close(mDispatchKeyWrite);
|
||||
}
|
||||
|
||||
void AInputQueue::attachLooper(ALooper* looper, int ident,
|
||||
ALooper_callbackFunc callback, void* data) {
|
||||
mLooper = static_cast<android::Looper*>(looper);
|
||||
mLooper->addFd(mConsumer.getChannel()->getFd(),
|
||||
ident, ALOOPER_EVENT_INPUT, callback, data);
|
||||
mLooper->addFd(mDispatchKeyRead,
|
||||
ident, ALOOPER_EVENT_INPUT, callback, data);
|
||||
}
|
||||
|
||||
void AInputQueue::detachLooper() {
|
||||
mLooper->removeFd(mConsumer.getChannel()->getFd());
|
||||
mLooper->removeFd(mDispatchKeyRead);
|
||||
}
|
||||
|
||||
int32_t AInputQueue::hasEvents() {
|
||||
struct pollfd pfd[2];
|
||||
|
||||
pfd[0].fd = mConsumer.getChannel()->getFd();
|
||||
pfd[0].events = POLLIN;
|
||||
pfd[0].revents = 0;
|
||||
pfd[1].fd = mDispatchKeyRead;
|
||||
pfd[1].events = POLLIN;
|
||||
pfd[1].revents = 0;
|
||||
|
||||
int nfd = poll(pfd, 2, 0);
|
||||
if (nfd <= 0) return 0;
|
||||
return ((pfd[0].revents & POLLIN) || (pfd[1].revents & POLLIN)) ? 1 : -1;
|
||||
}
|
||||
|
||||
int32_t AInputQueue::getEvent(AInputEvent** outEvent) {
|
||||
*outEvent = NULL;
|
||||
|
||||
char byteread;
|
||||
ssize_t nRead = read(mDispatchKeyRead, &byteread, 1);
|
||||
|
||||
Mutex::Autolock _l(mLock);
|
||||
|
||||
if (nRead == 1) {
|
||||
if (mDispatchingKeys.size() > 0) {
|
||||
KeyEvent* kevent = mDispatchingKeys[0];
|
||||
*outEvent = kevent;
|
||||
mDispatchingKeys.removeAt(0);
|
||||
in_flight_event inflight;
|
||||
inflight.event = kevent;
|
||||
inflight.seq = -1;
|
||||
inflight.finishSeq = 0;
|
||||
mInFlightEvents.push(inflight);
|
||||
}
|
||||
|
||||
bool finishNow = false;
|
||||
if (mFinishPreDispatches.size() > 0) {
|
||||
finish_pre_dispatch finish(mFinishPreDispatches[0]);
|
||||
mFinishPreDispatches.removeAt(0);
|
||||
const size_t N = mInFlightEvents.size();
|
||||
for (size_t i=0; i<N; i++) {
|
||||
const in_flight_event& inflight(mInFlightEvents[i]);
|
||||
if (inflight.seq == finish.seq) {
|
||||
*outEvent = inflight.event;
|
||||
finishNow = finish.handled;
|
||||
}
|
||||
}
|
||||
if (*outEvent == NULL) {
|
||||
ALOGW("getEvent couldn't find inflight for seq %d", finish.seq);
|
||||
}
|
||||
}
|
||||
|
||||
if (finishNow) {
|
||||
finishEvent(*outEvent, true, false);
|
||||
*outEvent = NULL;
|
||||
return -1;
|
||||
} else if (*outEvent != NULL) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t consumerSeq;
|
||||
InputEvent* myEvent = NULL;
|
||||
status_t res = mConsumer.consume(&mPooledInputEventFactory, true /*consumeBatches*/, -1,
|
||||
&consumerSeq, &myEvent);
|
||||
if (res != android::OK) {
|
||||
if (res != android::WOULD_BLOCK) {
|
||||
ALOGW("channel '%s' ~ Failed to consume input event. status=%d",
|
||||
mConsumer.getChannel()->getName().string(), res);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mConsumer.hasDeferredEvent()) {
|
||||
wakeupDispatchLocked();
|
||||
}
|
||||
|
||||
in_flight_event inflight;
|
||||
inflight.event = myEvent;
|
||||
inflight.seq = -1;
|
||||
inflight.finishSeq = consumerSeq;
|
||||
mInFlightEvents.push(inflight);
|
||||
|
||||
*outEvent = myEvent;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AInputQueue::preDispatchEvent(AInputEvent* event) {
|
||||
if (((InputEvent*)event)->getType() != AINPUT_EVENT_TYPE_KEY) {
|
||||
// The IME only cares about key events.
|
||||
return false;
|
||||
}
|
||||
|
||||
// For now we only send system keys to the IME... this avoids having
|
||||
// critical keys like DPAD go through this path. We really need to have
|
||||
// the IME report which keys it wants.
|
||||
if (!((KeyEvent*)event)->isSystemKey()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return preDispatchKey((KeyEvent*)event);
|
||||
}
|
||||
|
||||
void AInputQueue::finishEvent(AInputEvent* event, bool handled, bool didDefaultHandling) {
|
||||
LOG_TRACE("finishEvent: %p handled=%d, didDefaultHandling=%d", event,
|
||||
handled ? 1 : 0, didDefaultHandling ? 1 : 0);
|
||||
|
||||
if (!handled && !didDefaultHandling
|
||||
&& ((InputEvent*)event)->getType() == AINPUT_EVENT_TYPE_KEY
|
||||
&& ((KeyEvent*)event)->hasDefaultAction()) {
|
||||
// The app didn't handle this, but it may have a default action
|
||||
// associated with it. We need to hand this back to Java to be
|
||||
// executed.
|
||||
doUnhandledKey((KeyEvent*)event);
|
||||
return;
|
||||
}
|
||||
|
||||
Mutex::Autolock _l(mLock);
|
||||
|
||||
const size_t N = mInFlightEvents.size();
|
||||
for (size_t i=0; i<N; i++) {
|
||||
const in_flight_event& inflight(mInFlightEvents[i]);
|
||||
if (inflight.event == event) {
|
||||
if (inflight.finishSeq) {
|
||||
status_t res = mConsumer.sendFinishedSignal(inflight.finishSeq, handled);
|
||||
if (res != android::OK) {
|
||||
ALOGW("Failed to send finished signal on channel '%s'. status=%d",
|
||||
mConsumer.getChannel()->getName().string(), res);
|
||||
}
|
||||
}
|
||||
mPooledInputEventFactory.recycle(static_cast<InputEvent*>(event));
|
||||
mInFlightEvents.removeAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ALOGW("finishEvent called for unknown event: %p", event);
|
||||
}
|
||||
|
||||
void AInputQueue::dispatchEvent(android::KeyEvent* event) {
|
||||
Mutex::Autolock _l(mLock);
|
||||
|
||||
LOG_TRACE("dispatchEvent: dispatching=%d write=%d\n", mDispatchingKeys.size(),
|
||||
mDispatchKeyWrite);
|
||||
mDispatchingKeys.add(event);
|
||||
wakeupDispatchLocked();
|
||||
}
|
||||
|
||||
void AInputQueue::finishPreDispatch(int seq, bool handled) {
|
||||
Mutex::Autolock _l(mLock);
|
||||
|
||||
LOG_TRACE("finishPreDispatch: seq=%d handled=%d\n", seq, handled ? 1 : 0);
|
||||
finish_pre_dispatch finish;
|
||||
finish.seq = seq;
|
||||
finish.handled = handled;
|
||||
mFinishPreDispatches.add(finish);
|
||||
wakeupDispatchLocked();
|
||||
}
|
||||
|
||||
KeyEvent* AInputQueue::consumeUnhandledEvent() {
|
||||
Mutex::Autolock _l(mLock);
|
||||
|
||||
KeyEvent* event = NULL;
|
||||
if (mUnhandledKeys.size() > 0) {
|
||||
event = mUnhandledKeys[0];
|
||||
mUnhandledKeys.removeAt(0);
|
||||
}
|
||||
|
||||
LOG_TRACE("consumeUnhandledEvent: KeyEvent=%p", event);
|
||||
return event;
|
||||
}
|
||||
|
||||
KeyEvent* AInputQueue::consumePreDispatchingEvent(int* outSeq) {
|
||||
Mutex::Autolock _l(mLock);
|
||||
|
||||
KeyEvent* event = NULL;
|
||||
if (mPreDispatchingKeys.size() > 0) {
|
||||
const in_flight_event& inflight(mPreDispatchingKeys[0]);
|
||||
event = static_cast<KeyEvent*>(inflight.event);
|
||||
*outSeq = inflight.seq;
|
||||
mPreDispatchingKeys.removeAt(0);
|
||||
}
|
||||
|
||||
LOG_TRACE("consumePreDispatchingEvent: KeyEvent=%p", event);
|
||||
return event;
|
||||
}
|
||||
|
||||
KeyEvent* AInputQueue::createKeyEvent() {
|
||||
Mutex::Autolock _l(mLock);
|
||||
|
||||
return mPooledInputEventFactory.createKeyEvent();
|
||||
}
|
||||
|
||||
void AInputQueue::doUnhandledKey(KeyEvent* keyEvent) {
|
||||
Mutex::Autolock _l(mLock);
|
||||
|
||||
LOG_TRACE("Unhandled key: pending=%d write=%d\n", mUnhandledKeys.size(), mWorkWrite);
|
||||
if (mUnhandledKeys.size() <= 0 && mWorkWrite >= 0) {
|
||||
write_work(mWorkWrite, CMD_DEF_KEY);
|
||||
}
|
||||
mUnhandledKeys.add(keyEvent);
|
||||
}
|
||||
|
||||
bool AInputQueue::preDispatchKey(KeyEvent* keyEvent) {
|
||||
Mutex::Autolock _l(mLock);
|
||||
|
||||
LOG_TRACE("preDispatch key: pending=%d write=%d\n", mPreDispatchingKeys.size(), mWorkWrite);
|
||||
const size_t N = mInFlightEvents.size();
|
||||
for (size_t i=0; i<N; i++) {
|
||||
in_flight_event& inflight(mInFlightEvents.editItemAt(i));
|
||||
if (inflight.event == keyEvent) {
|
||||
if (inflight.seq >= 0) {
|
||||
// This event has already been pre-dispatched!
|
||||
LOG_TRACE("Event already pre-dispatched!");
|
||||
return false;
|
||||
}
|
||||
mSeq++;
|
||||
if (mSeq < 0) mSeq = 1;
|
||||
inflight.seq = mSeq;
|
||||
|
||||
if (mPreDispatchingKeys.size() <= 0 && mWorkWrite >= 0) {
|
||||
write_work(mWorkWrite, CMD_DEF_KEY);
|
||||
}
|
||||
mPreDispatchingKeys.add(inflight);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ALOGW("preDispatchKey called for unknown event: %p", keyEvent);
|
||||
return false;
|
||||
}
|
||||
|
||||
void AInputQueue::wakeupDispatchLocked() {
|
||||
restart:
|
||||
char dummy = 0;
|
||||
int res = write(mDispatchKeyWrite, &dummy, sizeof(dummy));
|
||||
if (res < 0 && errno == EINTR) {
|
||||
goto restart;
|
||||
}
|
||||
|
||||
if (res == sizeof(dummy)) return;
|
||||
|
||||
if (res < 0) ALOGW("Failed writing to dispatch fd: %s", strerror(errno));
|
||||
else ALOGW("Truncated writing to dispatch fd: %d", res);
|
||||
}
|
||||
|
||||
namespace android {
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Native state for interacting with the NativeActivity class.
|
||||
*/
|
||||
@@ -404,8 +108,6 @@ struct NativeCode : public ANativeActivity {
|
||||
dlhandle = _dlhandle;
|
||||
createActivityFunc = _createFunc;
|
||||
nativeWindow = NULL;
|
||||
inputChannel = NULL;
|
||||
nativeInputQueue = NULL;
|
||||
mainWorkRead = mainWorkWrite = -1;
|
||||
}
|
||||
|
||||
@@ -419,11 +121,7 @@ struct NativeCode : public ANativeActivity {
|
||||
if (messageQueue != NULL && mainWorkRead >= 0) {
|
||||
messageQueue->getLooper()->removeFd(mainWorkRead);
|
||||
}
|
||||
if (nativeInputQueue != NULL) {
|
||||
nativeInputQueue->mWorkWrite = -1;
|
||||
}
|
||||
setSurface(NULL);
|
||||
setInputChannel(NULL);
|
||||
if (mainWorkRead >= 0) close(mainWorkRead);
|
||||
if (mainWorkWrite >= 0) close(mainWorkWrite);
|
||||
if (dlhandle != NULL) {
|
||||
@@ -442,26 +140,6 @@ struct NativeCode : public ANativeActivity {
|
||||
}
|
||||
}
|
||||
|
||||
status_t setInputChannel(jobject _channel) {
|
||||
if (inputChannel != NULL) {
|
||||
delete nativeInputQueue;
|
||||
env->DeleteGlobalRef(inputChannel);
|
||||
}
|
||||
inputChannel = NULL;
|
||||
nativeInputQueue = NULL;
|
||||
if (_channel != NULL) {
|
||||
inputChannel = env->NewGlobalRef(_channel);
|
||||
sp<InputChannel> ic =
|
||||
android_view_InputChannel_getInputChannel(env, _channel);
|
||||
if (ic != NULL) {
|
||||
nativeInputQueue = new AInputQueue(ic, mainWorkWrite);
|
||||
} else {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
ANativeActivityCallbacks callbacks;
|
||||
|
||||
void* dlhandle;
|
||||
@@ -475,9 +153,6 @@ struct NativeCode : public ANativeActivity {
|
||||
int32_t lastWindowWidth;
|
||||
int32_t lastWindowHeight;
|
||||
|
||||
jobject inputChannel;
|
||||
struct AInputQueue* nativeInputQueue;
|
||||
|
||||
// These are used to wake up the main thread to process work.
|
||||
int mainWorkRead;
|
||||
int mainWorkWrite;
|
||||
@@ -532,38 +207,6 @@ static int mainWorkCallback(int fd, int events, void* data) {
|
||||
LOG_TRACE("mainWorkCallback: cmd=%d", work.cmd);
|
||||
|
||||
switch (work.cmd) {
|
||||
case CMD_DEF_KEY: {
|
||||
KeyEvent* keyEvent;
|
||||
while ((keyEvent=code->nativeInputQueue->consumeUnhandledEvent()) != NULL) {
|
||||
jobject inputEventObj = android_view_KeyEvent_fromNative(
|
||||
code->env, keyEvent);
|
||||
jboolean handled;
|
||||
if (inputEventObj) {
|
||||
handled = code->env->CallBooleanMethod(code->clazz,
|
||||
gNativeActivityClassInfo.dispatchUnhandledKeyEvent, inputEventObj);
|
||||
code->messageQueue->raiseAndClearException(
|
||||
code->env, "dispatchUnhandledKeyEvent");
|
||||
code->env->DeleteLocalRef(inputEventObj);
|
||||
} else {
|
||||
ALOGE("Failed to obtain key event for dispatchUnhandledKeyEvent.");
|
||||
handled = false;
|
||||
}
|
||||
code->nativeInputQueue->finishEvent(keyEvent, handled, true);
|
||||
}
|
||||
int seq;
|
||||
while ((keyEvent=code->nativeInputQueue->consumePreDispatchingEvent(&seq)) != NULL) {
|
||||
jobject inputEventObj = android_view_KeyEvent_fromNative(
|
||||
code->env, keyEvent);
|
||||
if (inputEventObj) {
|
||||
code->env->CallVoidMethod(code->clazz,
|
||||
gNativeActivityClassInfo.preDispatchKeyEvent, inputEventObj, seq);
|
||||
code->messageQueue->raiseAndClearException(code->env, "preDispatchKeyEvent");
|
||||
code->env->DeleteLocalRef(inputEventObj);
|
||||
} else {
|
||||
ALOGE("Failed to obtain key event for preDispatchKeyEvent.");
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case CMD_FINISH: {
|
||||
code->env->CallVoidMethod(code->clazz, gNativeActivityClassInfo.finish);
|
||||
code->messageQueue->raiseAndClearException(code->env, "finish");
|
||||
@@ -903,36 +546,28 @@ onSurfaceDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jobject surfa
|
||||
}
|
||||
|
||||
static void
|
||||
onInputChannelCreated_native(JNIEnv* env, jobject clazz, jint handle, jobject channel)
|
||||
onInputQueueCreated_native(JNIEnv* env, jobject clazz, jint handle, jint queuePtr)
|
||||
{
|
||||
LOG_TRACE("onInputChannelCreated_native");
|
||||
if (handle != 0) {
|
||||
NativeCode* code = (NativeCode*)handle;
|
||||
status_t err = code->setInputChannel(channel);
|
||||
if (err != OK) {
|
||||
jniThrowException(env, "java/lang/IllegalStateException",
|
||||
"Error setting input channel");
|
||||
return;
|
||||
}
|
||||
if (code->callbacks.onInputQueueCreated != NULL) {
|
||||
code->callbacks.onInputQueueCreated(code,
|
||||
code->nativeInputQueue);
|
||||
AInputQueue* queue = reinterpret_cast<AInputQueue*>(queuePtr);
|
||||
code->callbacks.onInputQueueCreated(code, queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
onInputChannelDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jobject channel)
|
||||
onInputQueueDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jint queuePtr)
|
||||
{
|
||||
LOG_TRACE("onInputChannelDestroyed_native");
|
||||
if (handle != 0) {
|
||||
NativeCode* code = (NativeCode*)handle;
|
||||
if (code->nativeInputQueue != NULL
|
||||
&& code->callbacks.onInputQueueDestroyed != NULL) {
|
||||
code->callbacks.onInputQueueDestroyed(code,
|
||||
code->nativeInputQueue);
|
||||
if (code->callbacks.onInputQueueDestroyed != NULL) {
|
||||
AInputQueue* queue = reinterpret_cast<AInputQueue*>(queuePtr);
|
||||
code->callbacks.onInputQueueDestroyed(code, queue);
|
||||
}
|
||||
code->setInputChannel(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,38 +589,6 @@ onContentRectChanged_native(JNIEnv* env, jobject clazz, jint handle,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dispatchKeyEvent_native(JNIEnv* env, jobject clazz, jint handle, jobject eventObj)
|
||||
{
|
||||
LOG_TRACE("dispatchKeyEvent_native");
|
||||
if (handle != 0) {
|
||||
NativeCode* code = (NativeCode*)handle;
|
||||
if (code->nativeInputQueue != NULL) {
|
||||
KeyEvent* event = code->nativeInputQueue->createKeyEvent();
|
||||
status_t status = android_view_KeyEvent_toNative(env, eventObj, event);
|
||||
if (status) {
|
||||
delete event;
|
||||
jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
|
||||
return;
|
||||
}
|
||||
code->nativeInputQueue->dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
finishPreDispatchKeyEvent_native(JNIEnv* env, jobject clazz, jint handle,
|
||||
jint seq, jboolean handled)
|
||||
{
|
||||
LOG_TRACE("finishPreDispatchKeyEvent_native");
|
||||
if (handle != 0) {
|
||||
NativeCode* code = (NativeCode*)handle;
|
||||
if (code->nativeInputQueue != NULL) {
|
||||
code->nativeInputQueue->finishPreDispatch(seq, handled ? true : false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const JNINativeMethod g_methods[] = {
|
||||
{ "loadNativeCode", "(Ljava/lang/String;Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILandroid/content/res/AssetManager;[B)I",
|
||||
(void*)loadNativeCode_native },
|
||||
@@ -1002,11 +605,11 @@ static const JNINativeMethod g_methods[] = {
|
||||
{ "onSurfaceChangedNative", "(ILandroid/view/Surface;III)V", (void*)onSurfaceChanged_native },
|
||||
{ "onSurfaceRedrawNeededNative", "(ILandroid/view/Surface;)V", (void*)onSurfaceRedrawNeeded_native },
|
||||
{ "onSurfaceDestroyedNative", "(I)V", (void*)onSurfaceDestroyed_native },
|
||||
{ "onInputChannelCreatedNative", "(ILandroid/view/InputChannel;)V", (void*)onInputChannelCreated_native },
|
||||
{ "onInputChannelDestroyedNative", "(ILandroid/view/InputChannel;)V", (void*)onInputChannelDestroyed_native },
|
||||
{ "onInputQueueCreatedNative", "(II)V",
|
||||
(void*)onInputQueueCreated_native },
|
||||
{ "onInputQueueDestroyedNative", "(II)V",
|
||||
(void*)onInputQueueDestroyed_native },
|
||||
{ "onContentRectChangedNative", "(IIIII)V", (void*)onContentRectChanged_native },
|
||||
{ "dispatchKeyEventNative", "(ILandroid/view/KeyEvent;)V", (void*)dispatchKeyEvent_native },
|
||||
{ "finishPreDispatchKeyEventNative", "(IIZ)V", (void*)finishPreDispatchKeyEvent_native },
|
||||
};
|
||||
|
||||
static const char* const kNativeActivityPathName = "android/app/NativeActivity";
|
||||
@@ -1025,13 +628,6 @@ int register_android_app_NativeActivity(JNIEnv* env)
|
||||
jclass clazz;
|
||||
FIND_CLASS(clazz, kNativeActivityPathName);
|
||||
|
||||
GET_METHOD_ID(gNativeActivityClassInfo.dispatchUnhandledKeyEvent,
|
||||
clazz,
|
||||
"dispatchUnhandledKeyEvent", "(Landroid/view/KeyEvent;)Z");
|
||||
GET_METHOD_ID(gNativeActivityClassInfo.preDispatchKeyEvent,
|
||||
clazz,
|
||||
"preDispatchKeyEvent", "(Landroid/view/KeyEvent;I)V");
|
||||
|
||||
GET_METHOD_ID(gNativeActivityClassInfo.finish,
|
||||
clazz,
|
||||
"finish", "()V");
|
||||
|
||||
282
core/jni/android_view_InputQueue.cpp
Normal file
282
core/jni/android_view_InputQueue.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "InputQueue"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <android/input.h>
|
||||
#include <android_runtime/AndroidRuntime.h>
|
||||
#include <android_runtime/android_view_InputQueue.h>
|
||||
#include <androidfw/Input.h>
|
||||
#include <utils/Looper.h>
|
||||
#include <utils/TypeHelpers.h>
|
||||
#include <ScopedLocalRef.h>
|
||||
|
||||
#include "JNIHelp.h"
|
||||
#include "android_os_MessageQueue.h"
|
||||
#include "android_view_KeyEvent.h"
|
||||
#include "android_view_MotionEvent.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
static struct {
|
||||
jmethodID finishInputEvent;
|
||||
} gInputQueueClassInfo;
|
||||
|
||||
enum {
|
||||
MSG_FINISH_INPUT = 1,
|
||||
};
|
||||
|
||||
InputQueue::InputQueue(jobject inputQueueObj, const sp<Looper>& looper,
|
||||
int dispatchReadFd, int dispatchWriteFd) :
|
||||
mDispatchReadFd(dispatchReadFd), mDispatchWriteFd(dispatchWriteFd),
|
||||
mDispatchLooper(looper), mHandler(new WeakMessageHandler(this)) {
|
||||
JNIEnv* env = AndroidRuntime::getJNIEnv();
|
||||
mInputQueueWeakGlobal = env->NewGlobalRef(inputQueueObj);
|
||||
}
|
||||
|
||||
InputQueue::~InputQueue() {
|
||||
mDispatchLooper->removeMessages(mHandler);
|
||||
JNIEnv* env = AndroidRuntime::getJNIEnv();
|
||||
env->DeleteGlobalRef(mInputQueueWeakGlobal);
|
||||
close(mDispatchReadFd);
|
||||
close(mDispatchWriteFd);
|
||||
}
|
||||
|
||||
void InputQueue::attachLooper(Looper* looper, int ident,
|
||||
ALooper_callbackFunc callback, void* data) {
|
||||
Mutex::Autolock _l(mLock);
|
||||
for (size_t i = 0; i < mAppLoopers.size(); i++) {
|
||||
if (looper == mAppLoopers[i]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mAppLoopers.push(looper);
|
||||
looper->addFd(mDispatchReadFd, ident, ALOOPER_EVENT_INPUT, callback, data);
|
||||
}
|
||||
|
||||
void InputQueue::detachLooper() {
|
||||
Mutex::Autolock _l(mLock);
|
||||
detachLooperLocked();
|
||||
}
|
||||
|
||||
void InputQueue::detachLooperLocked() {
|
||||
for (size_t i = 0; i < mAppLoopers.size(); i++) {
|
||||
mAppLoopers[i]->removeFd(mDispatchReadFd);
|
||||
}
|
||||
mAppLoopers.clear();
|
||||
}
|
||||
|
||||
bool InputQueue::hasEvents() {
|
||||
Mutex::Autolock _l(mLock);
|
||||
return mPendingEvents.size() > 0;
|
||||
}
|
||||
|
||||
status_t InputQueue::getEvent(InputEvent** outEvent) {
|
||||
Mutex::Autolock _l(mLock);
|
||||
*outEvent = NULL;
|
||||
if (!mPendingEvents.isEmpty()) {
|
||||
*outEvent = mPendingEvents[0];
|
||||
mPendingEvents.removeAt(0);
|
||||
}
|
||||
|
||||
if (mPendingEvents.isEmpty()) {
|
||||
char byteread[16];
|
||||
ssize_t nRead;
|
||||
do {
|
||||
nRead = TEMP_FAILURE_RETRY(read(mDispatchReadFd, &byteread, sizeof(byteread)));
|
||||
if (nRead < 0 && errno != EAGAIN) {
|
||||
ALOGW("Failed to read from native dispatch pipe: %s", strerror(errno));
|
||||
}
|
||||
} while (nRead > 0);
|
||||
}
|
||||
|
||||
return *outEvent != NULL ? OK : WOULD_BLOCK;
|
||||
}
|
||||
|
||||
bool InputQueue::preDispatchEvent(InputEvent* e) {
|
||||
if (e->getType() == AINPUT_EVENT_TYPE_KEY) {
|
||||
KeyEvent* keyEvent = static_cast<KeyEvent*>(e);
|
||||
if (keyEvent->getFlags() & AKEY_EVENT_FLAG_PREDISPATCH) {
|
||||
finishEvent(e, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void InputQueue::finishEvent(InputEvent* event, bool handled) {
|
||||
Mutex::Autolock _l(mLock);
|
||||
mFinishedEvents.push(key_value_pair_t<InputEvent*, bool>(event, handled));
|
||||
if (mFinishedEvents.size() == 1) {
|
||||
mDispatchLooper->sendMessage(this, Message(MSG_FINISH_INPUT));
|
||||
}
|
||||
}
|
||||
|
||||
void InputQueue::handleMessage(const Message& message) {
|
||||
switch(message.what) {
|
||||
case MSG_FINISH_INPUT:
|
||||
JNIEnv* env = AndroidRuntime::getJNIEnv();
|
||||
ScopedLocalRef<jobject> inputQueueObj(env, jniGetReferent(env, mInputQueueWeakGlobal));
|
||||
if (!inputQueueObj.get()) {
|
||||
ALOGW("InputQueue was finalized without being disposed");
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
InputEvent* event;
|
||||
bool handled;
|
||||
{
|
||||
Mutex::Autolock _l(mLock);
|
||||
if (mFinishedEvents.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
event = mFinishedEvents[0].getKey();
|
||||
handled = mFinishedEvents[0].getValue();
|
||||
mFinishedEvents.removeAt(0);
|
||||
}
|
||||
env->CallVoidMethod(inputQueueObj.get(), gInputQueueClassInfo.finishInputEvent,
|
||||
reinterpret_cast<jint>(event), handled);
|
||||
recycleInputEvent(event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void InputQueue::recycleInputEvent(InputEvent* event) {
|
||||
mPooledInputEventFactory.recycle(event);
|
||||
}
|
||||
|
||||
KeyEvent* InputQueue::createKeyEvent() {
|
||||
return mPooledInputEventFactory.createKeyEvent();
|
||||
}
|
||||
|
||||
MotionEvent* InputQueue::createMotionEvent() {
|
||||
return mPooledInputEventFactory.createMotionEvent();
|
||||
}
|
||||
|
||||
void InputQueue::enqueueEvent(InputEvent* event) {
|
||||
Mutex::Autolock _l(mLock);
|
||||
mPendingEvents.push(event);
|
||||
if (mPendingEvents.size() == 1) {
|
||||
char dummy = 0;
|
||||
int res = TEMP_FAILURE_RETRY(write(mDispatchWriteFd, &dummy, sizeof(dummy)));
|
||||
if (res < 0 && errno != EAGAIN) {
|
||||
ALOGW("Failed writing to dispatch fd: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InputQueue* InputQueue::createQueue(jobject inputQueueObj, const sp<Looper>& looper) {
|
||||
int pipeFds[2];
|
||||
if (pipe(pipeFds)) {
|
||||
ALOGW("Could not create native input dispatching pipe: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
fcntl(pipeFds[0], F_SETFL, O_NONBLOCK);
|
||||
fcntl(pipeFds[1], F_SETFL, O_NONBLOCK);
|
||||
return new InputQueue(inputQueueObj, looper, pipeFds[0], pipeFds[1]);
|
||||
}
|
||||
|
||||
static jint nativeInit(JNIEnv* env, jobject clazz, jobject queueWeak, jobject jMsgQueue) {
|
||||
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, jMsgQueue);
|
||||
if (messageQueue == NULL) {
|
||||
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
|
||||
return 0;
|
||||
}
|
||||
sp<InputQueue> queue = InputQueue::createQueue(queueWeak, messageQueue->getLooper());
|
||||
if (!queue.get()) {
|
||||
jniThrowRuntimeException(env, "InputQueue failed to initialize");
|
||||
return 0;
|
||||
}
|
||||
queue->incStrong(&gInputQueueClassInfo);
|
||||
return reinterpret_cast<jint>(queue.get());
|
||||
}
|
||||
|
||||
static void nativeDispose(JNIEnv* env, jobject clazz, jint ptr) {
|
||||
sp<InputQueue> queue = reinterpret_cast<InputQueue*>(ptr);
|
||||
queue->detachLooper();
|
||||
queue->decStrong(&gInputQueueClassInfo);
|
||||
}
|
||||
|
||||
static jint nativeSendKeyEvent(JNIEnv* env, jobject clazz, jint ptr, jobject eventObj,
|
||||
jboolean predispatch) {
|
||||
InputQueue* queue = reinterpret_cast<InputQueue*>(ptr);
|
||||
KeyEvent* event = queue->createKeyEvent();
|
||||
status_t status = android_view_KeyEvent_toNative(env, eventObj, event);
|
||||
if (status) {
|
||||
queue->recycleInputEvent(event);
|
||||
jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (predispatch) {
|
||||
event->setFlags(event->getFlags() | AKEY_EVENT_FLAG_PREDISPATCH);
|
||||
}
|
||||
|
||||
queue->enqueueEvent(event);
|
||||
return reinterpret_cast<jint>(event);
|
||||
}
|
||||
|
||||
static jint nativeSendMotionEvent(JNIEnv* env, jobject clazz, jint ptr, jobject eventObj) {
|
||||
sp<InputQueue> queue = reinterpret_cast<InputQueue*>(ptr);
|
||||
MotionEvent* originalEvent = android_view_MotionEvent_getNativePtr(env, eventObj);
|
||||
if (!originalEvent) {
|
||||
jniThrowRuntimeException(env, "Could not obtain MotionEvent pointer.");
|
||||
return -1;
|
||||
}
|
||||
MotionEvent* event = queue->createMotionEvent();
|
||||
event->copyFrom(originalEvent, true /* keepHistory */);
|
||||
queue->enqueueEvent(event);
|
||||
return reinterpret_cast<jint>(event);
|
||||
}
|
||||
|
||||
static const JNINativeMethod g_methods[] = {
|
||||
{ "nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;)I",
|
||||
(void*) nativeInit },
|
||||
{ "nativeDispose", "(I)V", (void*) nativeDispose },
|
||||
{ "nativeSendKeyEvent", "(ILandroid/view/KeyEvent;Z)I", (void*) nativeSendKeyEvent },
|
||||
{ "nativeSendMotionEvent", "(ILandroid/view/MotionEvent;)I", (void*) nativeSendMotionEvent },
|
||||
};
|
||||
|
||||
static const char* const kInputQueuePathName = "android/view/InputQueue";
|
||||
|
||||
#define FIND_CLASS(var, className) \
|
||||
do { \
|
||||
var = env->FindClass(className); \
|
||||
LOG_FATAL_IF(! var, "Unable to find class %s", className); \
|
||||
} while(0)
|
||||
|
||||
#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
|
||||
do { \
|
||||
var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
|
||||
LOG_FATAL_IF(! var, "Unable to find method" methodName); \
|
||||
} while(0)
|
||||
|
||||
int register_android_view_InputQueue(JNIEnv* env)
|
||||
{
|
||||
jclass clazz;
|
||||
FIND_CLASS(clazz, kInputQueuePathName);
|
||||
GET_METHOD_ID(gInputQueueClassInfo.finishInputEvent, clazz, "finishInputEvent", "(IZ)V");
|
||||
|
||||
return AndroidRuntime::registerNativeMethods(
|
||||
env, kInputQueuePathName,
|
||||
g_methods, NELEM(g_methods));
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
@@ -17,7 +17,6 @@
|
||||
#ifndef _ANDROID_APP_NATIVEACTIVITY_H
|
||||
#define _ANDROID_APP_NATIVEACTIVITY_H
|
||||
|
||||
#include <androidfw/InputTransport.h>
|
||||
#include <utils/Looper.h>
|
||||
|
||||
#include <android/native_activity.h>
|
||||
@@ -43,106 +42,4 @@ extern void android_NativeActivity_hideSoftInput(
|
||||
|
||||
} // namespace android
|
||||
|
||||
|
||||
/*
|
||||
* NDK input queue API.
|
||||
*
|
||||
* Here is the event flow:
|
||||
* 1. Event arrives in input consumer, and is returned by getEvent().
|
||||
* 2. Application calls preDispatchEvent():
|
||||
* a. Event is assigned a sequence ID and enqueued in mPreDispatchingKeys.
|
||||
* b. Main thread picks up event, hands to input method.
|
||||
* c. Input method eventually returns sequence # and whether it was handled.
|
||||
* d. finishPreDispatch() is called to enqueue the information.
|
||||
* e. next getEvent() call will:
|
||||
* - finish any pre-dispatch events that the input method handled
|
||||
* - return the next pre-dispatched event that the input method didn't handle.
|
||||
* f. (A preDispatchEvent() call on this event will now return false).
|
||||
* 3. Application calls finishEvent() with whether it was handled.
|
||||
* - If handled is true, the event is finished.
|
||||
* - If handled is false, the event is put on mUnhandledKeys, and:
|
||||
* a. Main thread receives event from consumeUnhandledEvent().
|
||||
* b. Java sends event through default key handler.
|
||||
* c. event is finished.
|
||||
*/
|
||||
struct AInputQueue {
|
||||
public:
|
||||
/* Creates a consumer associated with an input channel. */
|
||||
explicit AInputQueue(const android::sp<android::InputChannel>& channel, int workWrite);
|
||||
|
||||
/* Destroys the consumer and releases its input channel. */
|
||||
~AInputQueue();
|
||||
|
||||
void attachLooper(ALooper* looper, int ident, ALooper_callbackFunc callback, void* data);
|
||||
|
||||
void detachLooper();
|
||||
|
||||
int32_t hasEvents();
|
||||
|
||||
int32_t getEvent(AInputEvent** outEvent);
|
||||
|
||||
bool preDispatchEvent(AInputEvent* event);
|
||||
|
||||
void finishEvent(AInputEvent* event, bool handled, bool didDefaultHandling);
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
inline android::InputConsumer& getConsumer() { return mConsumer; }
|
||||
|
||||
void dispatchEvent(android::KeyEvent* event);
|
||||
|
||||
void finishPreDispatch(int seq, bool handled);
|
||||
|
||||
android::KeyEvent* consumeUnhandledEvent();
|
||||
android::KeyEvent* consumePreDispatchingEvent(int* outSeq);
|
||||
|
||||
android::KeyEvent* createKeyEvent();
|
||||
|
||||
int mWorkWrite;
|
||||
|
||||
private:
|
||||
void doUnhandledKey(android::KeyEvent* keyEvent);
|
||||
bool preDispatchKey(android::KeyEvent* keyEvent);
|
||||
void wakeupDispatchLocked();
|
||||
|
||||
android::PooledInputEventFactory mPooledInputEventFactory;
|
||||
android::InputConsumer mConsumer;
|
||||
android::sp<android::Looper> mLooper;
|
||||
|
||||
int mDispatchKeyRead;
|
||||
int mDispatchKeyWrite;
|
||||
|
||||
struct in_flight_event {
|
||||
android::InputEvent* event;
|
||||
int seq; // internal sequence number for synthetic pre-dispatch events
|
||||
uint32_t finishSeq; // sequence number for sendFinishedSignal, or 0 if finish not required
|
||||
};
|
||||
|
||||
struct finish_pre_dispatch {
|
||||
int seq;
|
||||
bool handled;
|
||||
};
|
||||
|
||||
android::Mutex mLock;
|
||||
|
||||
int mSeq;
|
||||
|
||||
// All input events that are actively being processed.
|
||||
android::Vector<in_flight_event> mInFlightEvents;
|
||||
|
||||
// Key events that the app didn't handle, and are pending for
|
||||
// delivery to the activity's default key handling.
|
||||
android::Vector<android::KeyEvent*> mUnhandledKeys;
|
||||
|
||||
// Keys that arrived in the Java framework and need to be
|
||||
// dispatched to the app.
|
||||
android::Vector<android::KeyEvent*> mDispatchingKeys;
|
||||
|
||||
// Key events that are pending to be pre-dispatched to the IME.
|
||||
android::Vector<in_flight_event> mPreDispatchingKeys;
|
||||
|
||||
// Event sequence numbers that we have finished pre-dispatching.
|
||||
android::Vector<finish_pre_dispatch> mFinishPreDispatches;
|
||||
};
|
||||
|
||||
#endif // _ANDROID_APP_NATIVEACTIVITY_H
|
||||
|
||||
85
include/android_runtime/android_view_InputQueue.h
Normal file
85
include/android_runtime/android_view_InputQueue.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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.
|
||||
*/
|
||||
|
||||
#ifndef _ANDROID_VIEW_INPUTQUEUE_H
|
||||
#define _ANDROID_VIEW_INPUTQUEUE_H
|
||||
|
||||
#include <androidfw/Input.h>
|
||||
#include <utils/Looper.h>
|
||||
#include <utils/TypeHelpers.h>
|
||||
#include <utils/Vector.h>
|
||||
|
||||
#include "JNIHelp.h"
|
||||
|
||||
/*
|
||||
* Declare a concrete type for the NDK's AInputQueue forward declaration
|
||||
*/
|
||||
struct AInputQueue{
|
||||
};
|
||||
|
||||
namespace android {
|
||||
|
||||
class InputQueue : public AInputQueue, public MessageHandler {
|
||||
public:
|
||||
virtual ~InputQueue();
|
||||
|
||||
void attachLooper(Looper* looper, int ident, ALooper_callbackFunc callback, void* data);
|
||||
|
||||
void detachLooper();
|
||||
|
||||
bool hasEvents();
|
||||
|
||||
status_t getEvent(InputEvent** outEvent);
|
||||
|
||||
bool preDispatchEvent(InputEvent* event);
|
||||
|
||||
void finishEvent(InputEvent* event, bool handled);
|
||||
|
||||
KeyEvent* createKeyEvent();
|
||||
|
||||
MotionEvent* createMotionEvent();
|
||||
|
||||
void recycleInputEvent(InputEvent* event);
|
||||
|
||||
void enqueueEvent(InputEvent* event);
|
||||
|
||||
static InputQueue* createQueue(jobject inputQueueObj, const sp<Looper>& looper);
|
||||
|
||||
protected:
|
||||
virtual void handleMessage(const Message& message);
|
||||
|
||||
private:
|
||||
InputQueue(jobject inputQueueObj, const sp<Looper>& looper,
|
||||
int readDispatchFd, int writeDispatchFd);
|
||||
|
||||
void detachLooperLocked();
|
||||
|
||||
jobject mInputQueueWeakGlobal;
|
||||
int mDispatchReadFd;
|
||||
int mDispatchWriteFd;
|
||||
Vector<Looper*> mAppLoopers;
|
||||
sp<Looper> mDispatchLooper;
|
||||
sp<WeakMessageHandler> mHandler;
|
||||
PooledInputEventFactory mPooledInputEventFactory;
|
||||
// Guards the pending and finished event vectors
|
||||
mutable Mutex mLock;
|
||||
Vector<InputEvent*> mPendingEvents;
|
||||
Vector<key_value_pair_t<InputEvent*, bool> > mFinishedEvents;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif
|
||||
@@ -36,6 +36,9 @@ class SkMatrix;
|
||||
* Additional private constants not defined in ndk/ui/input.h.
|
||||
*/
|
||||
enum {
|
||||
/* Signifies that the key is being predispatched */
|
||||
AKEY_EVENT_FLAG_PREDISPATCH = 0x20000000,
|
||||
|
||||
/* Private control to determine when an app is tracking a key sequence. */
|
||||
AKEY_EVENT_FLAG_START_TRACKING = 0x40000000,
|
||||
|
||||
|
||||
@@ -25,12 +25,15 @@
|
||||
#include <utils/Vector.h>
|
||||
|
||||
#include <android_runtime/android_app_NativeActivity.h>
|
||||
#include <android_runtime/android_view_InputQueue.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
|
||||
using android::InputEvent;
|
||||
using android::InputQueue;
|
||||
using android::KeyEvent;
|
||||
using android::Looper;
|
||||
using android::MotionEvent;
|
||||
using android::sp;
|
||||
using android::Vector;
|
||||
@@ -269,25 +272,37 @@ float AMotionEvent_getHistoricalAxisValue(const AInputEvent* motion_event,
|
||||
|
||||
void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper,
|
||||
int ident, ALooper_callbackFunc callback, void* data) {
|
||||
queue->attachLooper(looper, ident, callback, data);
|
||||
InputQueue* iq = static_cast<InputQueue*>(queue);
|
||||
Looper* l = static_cast<Looper*>(looper);
|
||||
iq->attachLooper(l, ident, callback, data);
|
||||
}
|
||||
|
||||
void AInputQueue_detachLooper(AInputQueue* queue) {
|
||||
queue->detachLooper();
|
||||
InputQueue* iq = static_cast<InputQueue*>(queue);
|
||||
iq->detachLooper();
|
||||
}
|
||||
|
||||
int32_t AInputQueue_hasEvents(AInputQueue* queue) {
|
||||
return queue->hasEvents();
|
||||
InputQueue* iq = static_cast<InputQueue*>(queue);
|
||||
return iq->hasEvents();
|
||||
}
|
||||
|
||||
int32_t AInputQueue_getEvent(AInputQueue* queue, AInputEvent** outEvent) {
|
||||
return queue->getEvent(outEvent);
|
||||
InputQueue* iq = static_cast<InputQueue*>(queue);
|
||||
InputEvent* event;
|
||||
int32_t res = iq->getEvent(&event);
|
||||
*outEvent = event;
|
||||
return res;
|
||||
}
|
||||
|
||||
int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, AInputEvent* event) {
|
||||
return queue->preDispatchEvent(event) ? 1 : 0;
|
||||
InputQueue* iq = static_cast<InputQueue*>(queue);
|
||||
InputEvent* e = static_cast<InputEvent*>(event);
|
||||
return iq->preDispatchEvent(e) ? 1 : 0;
|
||||
}
|
||||
|
||||
void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled) {
|
||||
queue->finishEvent(event, handled != 0, false);
|
||||
InputQueue* iq = static_cast<InputQueue*>(queue);
|
||||
InputEvent* e = static_cast<InputEvent*>(event);
|
||||
iq->finishEvent(e, handled != 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user