Native input dispatch rewrite work in progress.
The old dispatch mechanism has been left in place and continues to
be used by default for now. To enable native input dispatch,
edit the ENABLE_NATIVE_DISPATCH constant in WindowManagerPolicy.
Includes part of the new input event NDK API. Some details TBD.
To wire up input dispatch, as the ViewRoot adds a window to the
window session it receives an InputChannel object as an output
argument. The InputChannel encapsulates the file descriptors for a
shared memory region and two pipe end-points. The ViewRoot then
provides the InputChannel to the InputQueue. Behind the
scenes, InputQueue simply attaches handlers to the native PollLoop object
that underlies the MessageQueue. This way MessageQueue doesn't need
to know anything about input dispatch per-se, it just exposes (in native
code) a PollLoop that other components can use to monitor file descriptor
state changes.
There can be zero or more targets for any given input event. Each
input target is specified by its input channel and some parameters
including flags, an X/Y coordinate offset, and the dispatch timeout.
An input target can request either synchronous dispatch (for foreground apps)
or asynchronous dispatch (fire-and-forget for wallpapers and "outside"
targets). Currently, finding the appropriate input targets for an event
requires a call back into the WindowManagerServer from native code.
In the future this will be refactored to avoid most of these callbacks
except as required to handle pending focus transitions.
End-to-end event dispatch mostly works!
To do: event injection, rate limiting, ANRs, testing, optimization, etc.
Change-Id: I8c36b2b9e0a2d27392040ecda0f51b636456de25
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index d394a46..df30c76 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -16,13 +16,11 @@
package android.os;
-import java.util.ArrayList;
-
import android.util.AndroidRuntimeException;
import android.util.Config;
import android.util.Log;
-import com.android.internal.os.RuntimeInit;
+import java.util.ArrayList;
/**
* Low-level class holding the list of messages to be dispatched by a
@@ -34,11 +32,18 @@
*/
public class MessageQueue {
Message mMessages;
- private final ArrayList mIdleHandlers = new ArrayList();
+ private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private boolean mQuiting = false;
- private int mObject = 0; // used by native code
boolean mQuitAllowed = true;
+ @SuppressWarnings("unused")
+ private int mPtr; // used by native code
+
+ private native void nativeInit();
+ private native void nativeDestroy();
+ private native boolean nativePollOnce(int timeoutMillis);
+ private native void nativeWake();
+
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
@@ -85,55 +90,39 @@
mIdleHandlers.remove(handler);
}
}
-
- // Add an input pipe to the set being selected over. If token is
- // negative, remove 'handler's entry from the current set and forget
- // about it.
- void setInputToken(int token, int region, Handler handler) {
- if (token >= 0) nativeRegisterInputStream(token, region, handler);
- else nativeUnregisterInputStream(token);
- }
-
+
MessageQueue() {
nativeInit();
}
- private native void nativeInit();
-
- /**
- * @param token fd of the readable end of the input stream
- * @param region fd of the ashmem region used for data transport alongside the 'token' fd
- * @param handler Handler from which to make input messages based on data read from the fd
- */
- private native void nativeRegisterInputStream(int token, int region, Handler handler);
- private native void nativeUnregisterInputStream(int token);
- private native void nativeSignal();
-
- /**
- * Wait until the designated time for new messages to arrive.
- *
- * @param when Timestamp in SystemClock.uptimeMillis() base of the next message in the queue.
- * If 'when' is zero, the method will check for incoming messages without blocking. If
- * 'when' is negative, the method will block forever waiting for the next message.
- * @return
- */
- private native int nativeWaitForNext(long when);
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nativeDestroy();
+ } finally {
+ super.finalize();
+ }
+ }
final Message next() {
boolean tryIdle = true;
// when we start out, we'll just touch the input pipes and then go from there
- long timeToNextEventMillis = 0;
+ int timeToNextEventMillis = 0;
while (true) {
long now;
Object[] idlers = null;
- nativeWaitForNext(timeToNextEventMillis);
+ boolean dispatched = nativePollOnce(timeToNextEventMillis);
// Try to retrieve the next message, returning if found.
synchronized (this) {
now = SystemClock.uptimeMillis();
Message msg = pullNextLocked(now);
- if (msg != null) return msg;
+ if (msg != null) {
+ return msg;
+ }
+
if (tryIdle && mIdleHandlers.size() > 0) {
idlers = mIdleHandlers.toArray();
}
@@ -170,9 +159,14 @@
synchronized (this) {
// No messages, nobody to tell about it... time to wait!
if (mMessages != null) {
- if (mMessages.when - now > 0) {
+ long longTimeToNextEventMillis = mMessages.when - now;
+
+ if (longTimeToNextEventMillis > 0) {
Binder.flushPendingCommands();
- timeToNextEventMillis = mMessages.when - now;
+ timeToNextEventMillis = (int) Math.min(longTimeToNextEventMillis,
+ Integer.MAX_VALUE);
+ } else {
+ timeToNextEventMillis = 0;
}
} else {
Binder.flushPendingCommands();
@@ -230,7 +224,7 @@
msg.next = prev.next;
prev.next = msg;
}
- nativeSignal();
+ nativeWake();
}
return true;
}
@@ -351,7 +345,7 @@
void poke()
{
synchronized (this) {
- nativeSignal();
+ nativeWake();
}
}
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 2ade44e..3f5d6ca 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -18,6 +18,7 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.view.BaseIWindow;
+import com.android.internal.view.BaseInputHandler;
import com.android.internal.view.BaseSurfaceHolder;
import android.annotation.SdkConstant;
@@ -39,6 +40,10 @@
import android.util.LogPrinter;
import android.view.Gravity;
import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.InputHandler;
+import android.view.InputQueue;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
@@ -46,6 +51,7 @@
import android.view.ViewRoot;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
+import android.view.WindowManagerPolicy;
import java.util.ArrayList;
@@ -146,6 +152,7 @@
final WindowManager.LayoutParams mLayout
= new WindowManager.LayoutParams();
IWindowSession mSession;
+ InputChannel mInputChannel;
final Object mLock = new Object();
boolean mOffsetMessageEnqueued;
@@ -205,6 +212,30 @@
};
+ final InputHandler mInputHandler = new BaseInputHandler() {
+ @Override
+ public void handleTouch(MotionEvent event, Runnable finishedCallback) {
+ try {
+ synchronized (mLock) {
+ if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ if (mPendingMove != null) {
+ mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove);
+ mPendingMove.recycle();
+ }
+ mPendingMove = event;
+ } else {
+ mPendingMove = null;
+ }
+ Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT,
+ event);
+ mCaller.sendMessage(msg);
+ }
+ } finally {
+ finishedCallback.run();
+ }
+ }
+ };
+
final BaseIWindow mWindow = new BaseIWindow() {
@Override
public boolean onDispatchPointer(MotionEvent event, long eventTime,
@@ -487,8 +518,15 @@
mLayout.setTitle(WallpaperService.this.getClass().getName());
mLayout.windowAnimations =
com.android.internal.R.style.Animation_Wallpaper;
- mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets);
+ mInputChannel = new InputChannel();
+ mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets,
+ mInputChannel);
mCreated = true;
+
+ if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+ InputQueue.registerInputChannel(mInputChannel, mInputHandler,
+ Looper.myQueue());
+ }
}
mSurfaceHolder.mSurfaceLock.lock();
@@ -587,6 +625,7 @@
mSurfaceHolder.setSizeFromLayout();
mInitializing = true;
mSession = ViewRoot.getWindowSession(getMainLooper());
+
mWindow.setSession(mSession);
IntentFilter filter = new IntentFilter();
@@ -730,6 +769,15 @@
try {
if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
+ mSurfaceHolder.getSurface() + " of: " + this);
+
+ if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+ if (mInputChannel != null) {
+ InputQueue.unregisterInputChannel(mInputChannel);
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
+ }
+
mSession.remove(mWindow);
} catch (RemoteException e) {
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 01f07d6..4647fb4 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
+import android.view.InputChannel;
import android.view.IWindow;
import android.view.MotionEvent;
import android.view.WindowManager;
@@ -33,6 +34,9 @@
*/
interface IWindowSession {
int add(IWindow window, in WindowManager.LayoutParams attrs,
+ in int viewVisibility, out Rect outContentInsets,
+ out InputChannel outInputChannel);
+ int addWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, out Rect outContentInsets);
void remove(IWindow window);
diff --git a/core/java/android/view/InputChannel.aidl b/core/java/android/view/InputChannel.aidl
new file mode 100644
index 0000000..74c0aa4
--- /dev/null
+++ b/core/java/android/view/InputChannel.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/InputChannel.aidl
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+parcelable InputChannel;
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
new file mode 100644
index 0000000..66a83b8
--- /dev/null
+++ b/core/java/android/view/InputChannel.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+
+/**
+ * An input channel specifies the file descriptors used to send input events to
+ * a window in another process. It is Parcelable so that it can be transmitted
+ * to the ViewRoot through a Binder transaction as part of registering the Window.
+ * @hide
+ */
+public class InputChannel implements Parcelable {
+ private static final String TAG = "InputChannel";
+
+ public static final Parcelable.Creator<InputChannel> CREATOR
+ = new Parcelable.Creator<InputChannel>() {
+ public InputChannel createFromParcel(Parcel source) {
+ InputChannel result = new InputChannel();
+ result.readFromParcel(source);
+ return result;
+ }
+
+ public InputChannel[] newArray(int size) {
+ return new InputChannel[size];
+ }
+ };
+
+ @SuppressWarnings("unused")
+ private int mPtr; // used by native code
+
+ private boolean mDisposeAfterWriteToParcel;
+
+ private static native InputChannel[] nativeOpenInputChannelPair(String name);
+
+ private native void nativeDispose(boolean finalized);
+ private native void nativeTransferTo(InputChannel other);
+ private native void nativeReadFromParcel(Parcel parcel);
+ private native void nativeWriteToParcel(Parcel parcel);
+
+ private native String nativeGetName();
+
+ /**
+ * Creates an uninitialized input channel.
+ * It can be initialized by reading from a Parcel or by transferring the state of
+ * another input channel into this one.
+ */
+ public InputChannel() {
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nativeDispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Creates a new input channel pair. One channel should be provided to the input
+ * dispatcher and the other to the application's input queue.
+ * @param name The descriptive (non-unique) name of the channel pair.
+ * @return A pair of input channels. They are symmetric and indistinguishable.
+ */
+ public static InputChannel[] openInputChannelPair(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+
+ Slog.d(TAG, "Opening input channel pair '" + name + "'");
+ return nativeOpenInputChannelPair(name);
+ }
+
+ /**
+ * Gets the name of the input channel.
+ * @return The input channel name.
+ */
+ public String getName() {
+ String name = nativeGetName();
+ return name != null ? name : "uninitialized";
+ }
+
+ /**
+ * Disposes the input channel.
+ * Explicitly releases the reference this object is holding on the input channel.
+ * When all references are released, the input channel will be closed.
+ */
+ public void dispose() {
+ nativeDispose(false);
+ }
+
+ /**
+ * Transfers ownership of the internal state of the input channel to another
+ * instance and invalidates this instance. This is used to pass an input channel
+ * as an out parameter in a binder call.
+ * @param other The other input channel instance.
+ */
+ public void transferToBinderOutParameter(InputChannel outParameter) {
+ if (outParameter == null) {
+ throw new IllegalArgumentException("outParameter must not be null");
+ }
+
+ nativeTransferTo(outParameter);
+ outParameter.mDisposeAfterWriteToParcel = true;
+ }
+
+ public int describeContents() {
+ return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ public void readFromParcel(Parcel in) {
+ if (in == null) {
+ throw new IllegalArgumentException("in must not be null");
+ }
+
+ nativeReadFromParcel(in);
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ if (out == null) {
+ throw new IllegalArgumentException("out must not be null");
+ }
+
+ nativeWriteToParcel(out);
+
+ if (mDisposeAfterWriteToParcel) {
+ dispose();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+}
diff --git a/core/java/android/view/InputHandler.java b/core/java/android/view/InputHandler.java
new file mode 100644
index 0000000..816f622
--- /dev/null
+++ b/core/java/android/view/InputHandler.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Handles input messages that arrive on an input channel.
+ * @hide
+ */
+public interface InputHandler {
+ /**
+ * Handle a key event.
+ * It is the responsibility of the callee to ensure that the finished callback is
+ * eventually invoked when the event processing is finished and the input system
+ * can send the next event.
+ * @param event The key event data.
+ * @param finishedCallback The callback to invoke when event processing is finished.
+ */
+ public void handleKey(KeyEvent event, Runnable finishedCallback);
+
+ /**
+ * Handle a touch event.
+ * It is the responsibility of the callee to ensure that the finished callback is
+ * eventually invoked when the event processing is finished and the input system
+ * can send the next event.
+ * @param event The motion event data.
+ * @param finishedCallback The callback to invoke when event processing is finished.
+ */
+ public void handleTouch(MotionEvent event, Runnable finishedCallback);
+
+ /**
+ * Handle a trackball event.
+ * It is the responsibility of the callee to ensure that the finished callback is
+ * eventually invoked when the event processing is finished and the input system
+ * can send the next event.
+ * @param event The motion event data.
+ * @param finishedCallback The callback to invoke when event processing is finished.
+ */
+ public void handleTrackball(MotionEvent event, Runnable finishedCallback);
+}
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
new file mode 100644
index 0000000..b38f7d5
--- /dev/null
+++ b/core/java/android/view/InputQueue.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.MessageQueue;
+import android.util.Slog;
+
+/**
+ * An input queue provides a mechanism for an application to receive incoming
+ * input events sent over an input channel. Signalling is implemented by MessageQueue.
+ * @hide
+ */
+public final class InputQueue {
+ private static final String TAG = "InputQueue";
+
+ // Describes the interpretation of an event.
+ // XXX This concept is tentative. See comments in android/input.h.
+ public static final int INPUT_EVENT_NATURE_KEY = 1;
+ public static final int INPUT_EVENT_NATURE_TOUCH = 2;
+ public static final int INPUT_EVENT_NATURE_TRACKBALL = 3;
+
+ private static Object sLock = new Object();
+
+ private static native void nativeRegisterInputChannel(InputChannel inputChannel,
+ InputHandler inputHandler, MessageQueue messageQueue);
+ private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
+ private static native void nativeFinished(long finishedToken);
+
+ private InputQueue() {
+ }
+
+ /**
+ * Registers an input channel and handler.
+ * @param inputChannel The input channel to register.
+ * @param inputHandler The input handler to input events send to the target.
+ * @param messageQueue The message queue on whose thread the handler should be invoked.
+ */
+ public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler,
+ MessageQueue messageQueue) {
+ if (inputChannel == null) {
+ throw new IllegalArgumentException("inputChannel must not be null");
+ }
+ if (inputHandler == null) {
+ throw new IllegalArgumentException("inputHandler must not be null");
+ }
+ if (messageQueue == null) {
+ throw new IllegalArgumentException("messageQueue must not be null");
+ }
+
+ synchronized (sLock) {
+ Slog.d(TAG, "Registering input channel '" + inputChannel + "'");
+ nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue);
+ }
+ }
+
+ /**
+ * Unregisters an input channel.
+ * Does nothing if the channel is not currently registered.
+ * @param inputChannel The input channel to unregister.
+ */
+ public static void unregisterInputChannel(InputChannel inputChannel) {
+ if (inputChannel == null) {
+ throw new IllegalArgumentException("inputChannel must not be null");
+ }
+
+ synchronized (sLock) {
+ Slog.d(TAG, "Unregistering input channel '" + inputChannel + "'");
+ nativeUnregisterInputChannel(inputChannel);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static void dispatchKeyEvent(InputHandler inputHandler,
+ KeyEvent event, int nature, long finishedToken) {
+ Runnable finishedCallback = new FinishedCallback(finishedToken);
+
+ if (nature == INPUT_EVENT_NATURE_KEY) {
+ inputHandler.handleKey(event, finishedCallback);
+ } else {
+ Slog.d(TAG, "Unsupported nature for key event: " + nature);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static void dispatchMotionEvent(InputHandler inputHandler,
+ MotionEvent event, int nature, long finishedToken) {
+ Runnable finishedCallback = new FinishedCallback(finishedToken);
+
+ if (nature == INPUT_EVENT_NATURE_TOUCH) {
+ inputHandler.handleTouch(event, finishedCallback);
+ } else if (nature == INPUT_EVENT_NATURE_TRACKBALL) {
+ inputHandler.handleTrackball(event, finishedCallback);
+ } else {
+ Slog.d(TAG, "Unsupported nature for motion event: " + nature);
+ }
+ }
+
+ // TODO consider recycling finished callbacks when done
+ private static class FinishedCallback implements Runnable {
+ private long mFinishedToken;
+
+ public FinishedCallback(long finishedToken) {
+ mFinishedToken = finishedToken;
+ }
+
+ public void run() {
+ synchronized (sLock) {
+ nativeFinished(mFinishedToken);
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/InputTarget.java b/core/java/android/view/InputTarget.java
new file mode 100644
index 0000000..e56e03c
--- /dev/null
+++ b/core/java/android/view/InputTarget.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * An input target specifies how an input event is to be dispatched to a particular window
+ * including the window's input channel, control flags, a timeout, and an X / Y offset to
+ * be added to input event coordinates to compensate for the absolute position of the
+ * window area.
+ *
+ * These parameters are used by the native input dispatching code.
+ * @hide
+ */
+public class InputTarget {
+ public InputChannel mInputChannel;
+ public int mFlags;
+ public long mTimeoutNanos;
+ public float mXOffset;
+ public float mYOffset;
+
+ /**
+ * This flag indicates that subsequent event delivery should be held until the
+ * current event is delivered to this target or a timeout occurs.
+ */
+ public static int FLAG_SYNC = 0x01;
+
+ /**
+ * This flag indicates that a MotionEvent with ACTION_DOWN falls outside of the area of
+ * this target and so should instead be delivered as an ACTION_OUTSIDE to this target.
+ */
+ public static int FLAG_OUTSIDE = 0x02;
+
+ /*
+ * This flag indicates that a KeyEvent or MotionEvent is being canceled.
+ * In the case of a key event, it should be delivered with KeyEvent.FLAG_CANCELED set.
+ * In the case of a motion event, it should be delivered as MotionEvent.ACTION_CANCEL.
+ */
+ public static int FLAG_CANCEL = 0x04;
+
+ public void recycle() {
+ mInputChannel = null;
+ }
+}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 9aa16b5..ae9746e 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -127,6 +127,7 @@
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
+ // native/include/android/keycodes.h
// frameworks/base/include/ui/KeycodeLabels.h
// tools/puppet_master/PuppetMaster/nav_keys.py
// frameworks/base/core/res/res/values/attrs.xml
@@ -162,7 +163,7 @@
* key code is not {#link {@link #KEYCODE_UNKNOWN} then the
* {#link {@link #getRepeatCount()} method returns the number of times
* the given key code should be executed.
- * Otherwise, if the key code {@link #KEYCODE_UNKNOWN}, then
+ * Otherwise, if the key code is {@link #KEYCODE_UNKNOWN}, then
* this is a sequence of characters as returned by {@link #getCharacters}.
*/
public static final int ACTION_MULTIPLE = 2;
@@ -330,7 +331,7 @@
private int mMetaState;
private int mAction;
private int mKeyCode;
- private int mScancode;
+ private int mScanCode;
private int mRepeatCount;
private int mDeviceId;
private int mFlags;
@@ -480,7 +481,7 @@
mRepeatCount = repeat;
mMetaState = metaState;
mDeviceId = device;
- mScancode = scancode;
+ mScanCode = scancode;
}
/**
@@ -510,7 +511,7 @@
mRepeatCount = repeat;
mMetaState = metaState;
mDeviceId = device;
- mScancode = scancode;
+ mScanCode = scancode;
mFlags = flags;
}
@@ -547,7 +548,7 @@
mRepeatCount = origEvent.mRepeatCount;
mMetaState = origEvent.mMetaState;
mDeviceId = origEvent.mDeviceId;
- mScancode = origEvent.mScancode;
+ mScanCode = origEvent.mScanCode;
mFlags = origEvent.mFlags;
mCharacters = origEvent.mCharacters;
}
@@ -572,7 +573,7 @@
mRepeatCount = newRepeat;
mMetaState = origEvent.mMetaState;
mDeviceId = origEvent.mDeviceId;
- mScancode = origEvent.mScancode;
+ mScanCode = origEvent.mScanCode;
mFlags = origEvent.mFlags;
mCharacters = origEvent.mCharacters;
}
@@ -625,7 +626,7 @@
mRepeatCount = origEvent.mRepeatCount;
mMetaState = origEvent.mMetaState;
mDeviceId = origEvent.mDeviceId;
- mScancode = origEvent.mScancode;
+ mScanCode = origEvent.mScanCode;
mFlags = origEvent.mFlags;
// Don't copy mCharacters, since one way or the other we'll lose it
// when changing the action.
@@ -859,7 +860,7 @@
* Mostly this is here for debugging purposes.
*/
public final int getScanCode() {
- return mScancode;
+ return mScanCode;
}
/**
@@ -1183,7 +1184,7 @@
public String toString() {
return "KeyEvent{action=" + mAction + " code=" + mKeyCode
+ " repeat=" + mRepeatCount
- + " meta=" + mMetaState + " scancode=" + mScancode
+ + " meta=" + mMetaState + " scancode=" + mScanCode
+ " mFlags=" + mFlags + "}";
}
@@ -1208,7 +1209,7 @@
out.writeInt(mRepeatCount);
out.writeInt(mMetaState);
out.writeInt(mDeviceId);
- out.writeInt(mScancode);
+ out.writeInt(mScanCode);
out.writeInt(mFlags);
out.writeLong(mDownTime);
out.writeLong(mEventTime);
@@ -1220,7 +1221,7 @@
mRepeatCount = in.readInt();
mMetaState = in.readInt();
mDeviceId = in.readInt();
- mScancode = in.readInt();
+ mScanCode = in.readInt();
mFlags = in.readInt();
mDownTime = in.readLong();
mEventTime = in.readLong();
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index eefbf7a..1f06191 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -248,17 +248,17 @@
private RuntimeException mRecycledLocation;
private boolean mRecycled;
- private MotionEvent() {
- mPointerIdentifiers = new int[BASE_AVAIL_POINTERS];
- mDataSamples = new float[BASE_AVAIL_POINTERS*BASE_AVAIL_SAMPLES*NUM_SAMPLE_DATA];
- mTimeSamples = new long[BASE_AVAIL_SAMPLES];
+ private MotionEvent(int pointerCount, int sampleCount) {
+ mPointerIdentifiers = new int[pointerCount];
+ mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA];
+ mTimeSamples = new long[sampleCount];
}
static private MotionEvent obtain() {
final MotionEvent ev;
synchronized (gRecyclerLock) {
if (gRecyclerTop == null) {
- return new MotionEvent();
+ return new MotionEvent(BASE_AVAIL_POINTERS, BASE_AVAIL_SAMPLES);
}
ev = gRecyclerTop;
gRecyclerTop = ev.mNext;
@@ -269,6 +269,45 @@
ev.mNext = null;
return ev;
}
+
+ @SuppressWarnings("unused") // used by native code
+ static private MotionEvent obtain(int pointerCount, int sampleCount) {
+ final MotionEvent ev;
+ synchronized (gRecyclerLock) {
+ if (gRecyclerTop == null) {
+ if (pointerCount < BASE_AVAIL_POINTERS) {
+ pointerCount = BASE_AVAIL_POINTERS;
+ }
+ if (sampleCount < BASE_AVAIL_SAMPLES) {
+ sampleCount = BASE_AVAIL_SAMPLES;
+ }
+ return new MotionEvent(pointerCount, sampleCount);
+ }
+ ev = gRecyclerTop;
+ gRecyclerTop = ev.mNext;
+ gRecyclerUsed--;
+ }
+ ev.mRecycledLocation = null;
+ ev.mRecycled = false;
+ ev.mNext = null;
+
+ if (ev.mPointerIdentifiers.length < pointerCount) {
+ ev.mPointerIdentifiers = new int[pointerCount];
+ }
+
+ final int timeSamplesLength = ev.mTimeSamples.length;
+ if (timeSamplesLength < sampleCount) {
+ ev.mTimeSamples = new long[sampleCount];
+ }
+
+ final int dataSamplesLength = ev.mDataSamples.length;
+ final int neededDataSamplesLength = pointerCount * sampleCount * NUM_SAMPLE_DATA;
+ if (dataSamplesLength < neededDataSamplesLength) {
+ ev.mDataSamples = new float[neededDataSamplesLength];
+ }
+
+ return ev;
+ }
/**
* Create a new MotionEvent, filling in all of the basic values that
@@ -1022,7 +1061,7 @@
}
/**
- * Returns a bitfield indicating which edges, if any, where touched by this
+ * Returns a bitfield indicating which edges, if any, were touched by this
* MotionEvent. For touch events, clients can use this to determine if the
* user's finger was touching the edge of the display.
*
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 0f0cf60..8beceec 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -471,7 +471,7 @@
mWindow = new MyWindow(this);
mLayout.type = mWindowType;
mLayout.gravity = Gravity.LEFT|Gravity.TOP;
- mSession.add(mWindow, mLayout,
+ mSession.addWithoutInputChannel(mWindow, mLayout,
mVisible ? VISIBLE : GONE, mContentInsets);
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index aa124e6..a41c690 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -153,6 +153,7 @@
CompatibilityInfo.Translator mTranslator;
final View.AttachInfo mAttachInfo;
+ InputChannel mInputChannel;
final Rect mTempRect; // used in the transaction to not thrash the heap.
final Rect mVisRect; // used to retrieve visible rect of focused view.
@@ -219,7 +220,7 @@
AudioManager mAudioManager;
private final int mDensity;
-
+
public static IWindowSession getWindowSession(Looper mainLooper) {
synchronized (mStaticInit) {
if (!mInitialized) {
@@ -434,9 +435,6 @@
}
}
- // fd [0] is the receiver, [1] is the sender
- private native int[] makeInputChannel();
-
/**
* We have one child
*/
@@ -488,25 +486,20 @@
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
- // Set up the input event channel
- if (false) {
- int[] fds = makeInputChannel();
- if (DEBUG_INPUT) {
- Log.v(TAG, "makeInputChannel() returned " + fds);
- }
- }
-
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
+ mInputChannel = new InputChannel();
try {
res = sWindowSession.add(mWindow, mWindowAttributes,
- getHostVisibility(), mAttachInfo.mContentInsets);
+ getHostVisibility(), mAttachInfo.mContentInsets,
+ mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
+ mInputChannel = null;
unscheduleTraversals();
throw new RuntimeException("Adding window failed", e);
} finally {
@@ -514,7 +507,7 @@
attrs.restore();
}
}
-
+
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
}
@@ -560,6 +553,12 @@
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
+
+ if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+ InputQueue.registerInputChannel(mInputChannel, mInputHandler,
+ Looper.myQueue());
+ }
+
view.assignParent(this);
mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0;
@@ -1735,6 +1734,14 @@
}
mSurface.release();
+ if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+ if (mInputChannel != null) {
+ InputQueue.unregisterInputChannel(mInputChannel);
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
+ }
+
try {
sWindowSession.remove(mWindow);
} catch (RemoteException e) {
@@ -1841,19 +1848,16 @@
boolean callWhenDone = msg.arg1 != 0;
if (event == null) {
- try {
- long timeBeforeGettingEvents;
- if (MEASURE_LATENCY) {
- timeBeforeGettingEvents = System.nanoTime();
- }
+ long timeBeforeGettingEvents;
+ if (MEASURE_LATENCY) {
+ timeBeforeGettingEvents = System.nanoTime();
+ }
- event = sWindowSession.getPendingPointerMove(mWindow);
+ event = getPendingPointerMotionEvent();
- if (MEASURE_LATENCY && event != null) {
- lt.sample("9 Client got events ", System.nanoTime() - event.getEventTimeNano());
- lt.sample("8 Client getting events ", timeBeforeGettingEvents - event.getEventTimeNano());
- }
- } catch (RemoteException e) {
+ if (MEASURE_LATENCY && event != null) {
+ lt.sample("9 Client got events ", System.nanoTime() - event.getEventTimeNano());
+ lt.sample("8 Client getting events ", timeBeforeGettingEvents - event.getEventTimeNano());
}
callWhenDone = false;
}
@@ -1928,14 +1932,9 @@
}
} finally {
if (callWhenDone) {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishMotionEvent();
}
- if (event != null) {
- event.recycle();
- }
+ recycleMotionEvent(event);
if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");
// Let the exception fall through -- the looper will catch
// it and take care of the bad app for us.
@@ -2075,7 +2074,63 @@
} break;
}
}
+
+ private void finishKeyEvent(KeyEvent event) {
+ if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+ if (mFinishedCallback != null) {
+ mFinishedCallback.run();
+ mFinishedCallback = null;
+ }
+ } else {
+ try {
+ sWindowSession.finishKey(mWindow);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ private void finishMotionEvent() {
+ if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+ throw new IllegalStateException("Should not be reachable with native input dispatch.");
+ }
+
+ try {
+ sWindowSession.finishKey(mWindow);
+ } catch (RemoteException e) {
+ }
+ }
+ private void recycleMotionEvent(MotionEvent event) {
+ if (event != null) {
+ event.recycle();
+ }
+ }
+
+ private MotionEvent getPendingPointerMotionEvent() {
+ if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+ throw new IllegalStateException("Should not be reachable with native input dispatch.");
+ }
+
+ try {
+ return sWindowSession.getPendingPointerMove(mWindow);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ private MotionEvent getPendingTrackballMotionEvent() {
+ if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+ throw new IllegalStateException("Should not be reachable with native input dispatch.");
+ }
+
+ try {
+ return sWindowSession.getPendingTrackballMove(mWindow);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+
/**
* Something in the current window tells us we need to change the touch mode. For
* example, we are not in touch mode, and the user touches the screen.
@@ -2200,10 +2255,7 @@
private void deliverTrackballEvent(MotionEvent event, boolean callWhenDone) {
if (event == null) {
- try {
- event = sWindowSession.getPendingTrackballMove(mWindow);
- } catch (RemoteException e) {
- }
+ event = getPendingTrackballMotionEvent();
callWhenDone = false;
}
@@ -2223,14 +2275,9 @@
} finally {
if (handled) {
if (callWhenDone) {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishMotionEvent();
}
- if (event != null) {
- event.recycle();
- }
+ recycleMotionEvent(event);
// If we reach this, we delivered a trackball event to mView and
// mView consumed it. Because we will not translate the trackball
// event into a key event, touch mode will not exit, so we exit
@@ -2339,13 +2386,8 @@
}
} finally {
if (callWhenDone) {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
- if (event != null) {
- event.recycle();
- }
+ finishMotionEvent();
+ recycleMotionEvent(event);
}
// Let the exception fall through -- the looper will catch
// it and take care of the bad app for us.
@@ -2503,10 +2545,7 @@
if (sendDone) {
if (LOCAL_LOGV) Log.v(
"ViewRoot", "Telling window manager key is finished");
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishKeyEvent(event);
}
return;
}
@@ -2539,10 +2578,7 @@
} else if (sendDone) {
if (LOCAL_LOGV) Log.v(
"ViewRoot", "Telling window manager key is finished");
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishKeyEvent(event);
} else {
Log.w("ViewRoot", "handleFinishedEvent(seq=" + seq
+ " handled=" + handled + " ev=" + event
@@ -2617,10 +2653,7 @@
if (sendDone) {
if (LOCAL_LOGV) Log.v(
"ViewRoot", "Telling window manager key is finished");
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishKeyEvent(event);
}
// Let the exception fall through -- the looper will catch
// it and take care of the bad app for us.
@@ -2798,6 +2831,53 @@
msg.obj = ri;
sendMessage(msg);
}
+
+ private Runnable mFinishedCallback;
+
+ private final InputHandler mInputHandler = new InputHandler() {
+ public void handleKey(KeyEvent event, Runnable finishedCallback) {
+ mFinishedCallback = finishedCallback;
+
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ //noinspection ConstantConditions
+ if (false && event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) {
+ if (Config.LOGD) Log.d("keydisp",
+ "===================================================");
+ if (Config.LOGD) Log.d("keydisp", "Focused view Hierarchy is:");
+ debug();
+
+ if (Config.LOGD) Log.d("keydisp",
+ "===================================================");
+ }
+ }
+
+ Message msg = obtainMessage(DISPATCH_KEY);
+ msg.obj = event;
+
+ if (LOCAL_LOGV) Log.v(
+ "ViewRoot", "sending key " + event + " to " + mView);
+
+ sendMessageAtTime(msg, event.getEventTime());
+ }
+
+ public void handleTouch(MotionEvent event, Runnable finishedCallback) {
+ finishedCallback.run();
+
+ Message msg = obtainMessage(DISPATCH_POINTER);
+ msg.obj = event;
+ msg.arg1 = 0;
+ sendMessageAtTime(msg, event.getEventTime());
+ }
+
+ public void handleTrackball(MotionEvent event, Runnable finishedCallback) {
+ finishedCallback.run();
+
+ Message msg = obtainMessage(DISPATCH_TRACKBALL);
+ msg.obj = event;
+ msg.arg1 = 0;
+ sendMessageAtTime(msg, event.getEventTime());
+ }
+ };
public void dispatchKey(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -2968,7 +3048,7 @@
}
}
- static class EventCompletion extends Handler {
+ class EventCompletion extends Handler {
final IWindow mWindow;
final KeyEvent mKeyEvent;
final boolean mIsPointer;
@@ -2987,40 +3067,25 @@
@Override
public void handleMessage(Message msg) {
if (mKeyEvent != null) {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishKeyEvent(mKeyEvent);
} else if (mIsPointer) {
boolean didFinish;
MotionEvent event = mMotionEvent;
if (event == null) {
- try {
- event = sWindowSession.getPendingPointerMove(mWindow);
- } catch (RemoteException e) {
- }
+ event = getPendingPointerMotionEvent();
didFinish = true;
} else {
didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE;
}
if (!didFinish) {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishMotionEvent();
}
} else {
MotionEvent event = mMotionEvent;
if (event == null) {
- try {
- event = sWindowSession.getPendingTrackballMove(mWindow);
- } catch (RemoteException e) {
- }
+ event = getPendingTrackballMotionEvent();
} else {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishMotionEvent();
}
}
}
@@ -3050,7 +3115,7 @@
viewRoot.dispatchKey(event);
} else {
Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!");
- new EventCompletion(mMainLooper, this, event, false, null);
+ viewRoot.new EventCompletion(mMainLooper, this, event, false, null);
}
}
@@ -3064,7 +3129,7 @@
}
viewRoot.dispatchPointer(event, eventTime, callWhenDone);
} else {
- new EventCompletion(mMainLooper, this, null, true, event);
+ viewRoot.new EventCompletion(mMainLooper, this, null, true, event);
}
}
@@ -3074,7 +3139,7 @@
if (viewRoot != null) {
viewRoot.dispatchTrackball(event, eventTime, callWhenDone);
} else {
- new EventCompletion(mMainLooper, this, null, false, event);
+ viewRoot.new EventCompletion(mMainLooper, this, null, false, event);
}
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index b39cb9d..431b786 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -78,6 +78,12 @@
public final static int FLAG_BRIGHT_HERE = 0x20000000;
public final static boolean WATCH_POINTER = false;
+
+ /**
+ * Temporary flag added during the transition to the new native input dispatcher.
+ * This will be removed when the old input dispatch code is deleted.
+ */
+ public final static boolean ENABLE_NATIVE_INPUT_DISPATCH = false;
// flags for interceptKeyTq
/**
@@ -708,6 +714,8 @@
*/
public boolean preprocessInputEventTq(RawInputEvent event);
+ public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
+
/**
* Determine whether a given key code is used to cause an app switch
* to occur (most often the HOME key, also often ENDCALL). If you return