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/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
new file mode 100644
index 0000000..72c4166
--- /dev/null
+++ b/services/java/com/android/server/InputManager.java
@@ -0,0 +1,460 @@
+/*
+ * 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 com.android.server;
+
+import com.android.internal.util.XmlUtils;
+import com.android.server.KeyInputQueue.VirtualKey;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Environment;
+import android.os.LocalPowerManager;
+import android.os.PowerManager;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+import android.view.InputChannel;
+import android.view.InputTarget;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.RawInputEvent;
+import android.view.Surface;
+import android.view.WindowManagerPolicy;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/*
+ * Wraps the C++ InputManager and provides its callbacks.
+ * 
+ * XXX Tempted to promote this to a first-class service, ie. InputManagerService, to
+ *     improve separation of concerns with respect to the window manager.
+ */
+public class InputManager {
+    static final String TAG = "InputManager";
+    
+    private final Callbacks mCallbacks;
+    private final Context mContext;
+    private final WindowManagerService mWindowManagerService;
+    private final WindowManagerPolicy mWindowManagerPolicy;
+    private final PowerManager mPowerManager;
+    private final PowerManagerService mPowerManagerService;
+    
+    private int mTouchScreenConfig;
+    private int mKeyboardConfig;
+    private int mNavigationConfig;
+    
+    private static native void nativeInit(Callbacks callbacks);
+    private static native void nativeStart();
+    private static native void nativeSetDisplaySize(int displayId, int width, int height);
+    private static native void nativeSetDisplayOrientation(int displayId, int rotation);
+    
+    private static native int nativeGetScanCodeState(int deviceId, int deviceClasses,
+            int scanCode);
+    private static native int nativeGetKeyCodeState(int deviceId, int deviceClasses,
+            int keyCode);
+    private static native int nativeGetSwitchState(int deviceId, int deviceClasses,
+            int sw);
+    private static native boolean nativeHasKeys(int[] keyCodes, boolean[] keyExists);
+    private static native void nativeRegisterInputChannel(InputChannel inputChannel);
+    private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
+    
+    // Device class as defined by EventHub.
+    private static final int CLASS_KEYBOARD = 0x00000001;
+    private static final int CLASS_ALPHAKEY = 0x00000002;
+    private static final int CLASS_TOUCHSCREEN = 0x00000004;
+    private static final int CLASS_TRACKBALL = 0x00000008;
+    private static final int CLASS_TOUCHSCREEN_MT = 0x00000010;
+    private static final int CLASS_DPAD = 0x00000020;
+    
+    public InputManager(Context context,
+            WindowManagerService windowManagerService,
+            WindowManagerPolicy windowManagerPolicy,
+            PowerManager powerManager,
+            PowerManagerService powerManagerService) {
+        this.mContext = context;
+        this.mWindowManagerService = windowManagerService;
+        this.mWindowManagerPolicy = windowManagerPolicy;
+        this.mPowerManager = powerManager;
+        this.mPowerManagerService = powerManagerService;
+        
+        this.mCallbacks = new Callbacks();
+        
+        mTouchScreenConfig = Configuration.TOUCHSCREEN_NOTOUCH;
+        mKeyboardConfig = Configuration.KEYBOARD_NOKEYS;
+        mNavigationConfig = Configuration.NAVIGATION_NONAV;
+        
+        init();
+    }
+    
+    private void init() {
+        Slog.i(TAG, "Initializing input manager");
+        nativeInit(mCallbacks);
+    }
+    
+    public void start() {
+        Slog.i(TAG, "Starting input manager");
+        nativeStart();
+    }
+    
+    public void setDisplaySize(int displayId, int width, int height) {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("Invalid display id or dimensions.");
+        }
+        
+        Slog.i(TAG, "Setting display #" + displayId + " size to " + width + "x" + height);
+        nativeSetDisplaySize(displayId, width, height);
+    }
+    
+    public void setDisplayOrientation(int displayId, int rotation) {
+        if (rotation < Surface.ROTATION_0 || rotation > Surface.ROTATION_270) {
+            throw new IllegalArgumentException("Invalid rotation.");
+        }
+        
+        Slog.i(TAG, "Setting display #" + displayId + " orientation to " + rotation);
+        nativeSetDisplayOrientation(displayId, rotation);
+    }
+    
+    public void getInputConfiguration(Configuration config) {
+        if (config == null) {
+            throw new IllegalArgumentException("config must not be null.");
+        }
+        
+        config.touchscreen = mTouchScreenConfig;
+        config.keyboard = mKeyboardConfig;
+        config.navigation = mNavigationConfig;
+    }
+    
+    public int getScancodeState(int code) {
+        return nativeGetScanCodeState(0, -1, code);
+    }
+    
+    public int getScancodeState(int deviceId, int code) {
+        return nativeGetScanCodeState(deviceId, -1, code);
+    }
+    
+    public int getTrackballScancodeState(int code) {
+        return nativeGetScanCodeState(-1, CLASS_TRACKBALL, code);
+    }
+    
+    public int getDPadScancodeState(int code) {
+        return nativeGetScanCodeState(-1, CLASS_DPAD, code);
+    }
+    
+    public int getKeycodeState(int code) {
+        return nativeGetKeyCodeState(0, -1, code);
+    }
+    
+    public int getKeycodeState(int deviceId, int code) {
+        return nativeGetKeyCodeState(deviceId, -1, code);
+    }
+    
+    public int getTrackballKeycodeState(int code) {
+        return nativeGetKeyCodeState(-1, CLASS_TRACKBALL, code);
+    }
+    
+    public int getDPadKeycodeState(int code) {
+        return nativeGetKeyCodeState(-1, CLASS_DPAD, code);
+    }
+
+    public int getSwitchState(int sw) {
+        return nativeGetSwitchState(-1, -1, sw);
+    }
+    
+    public int getSwitchState(int deviceId, int sw) {
+        return nativeGetSwitchState(deviceId, -1, sw);
+    }
+
+    public boolean hasKeys(int[] keyCodes, boolean[] keyExists) {
+        if (keyCodes == null) {
+            throw new IllegalArgumentException("keyCodes must not be null.");
+        }
+        if (keyExists == null) {
+            throw new IllegalArgumentException("keyExists must not be null.");
+        }
+        
+        return nativeHasKeys(keyCodes, keyExists);
+    }
+    
+    public void registerInputChannel(InputChannel inputChannel) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null.");
+        }
+        
+        nativeRegisterInputChannel(inputChannel);
+    }
+    
+    public void unregisterInputChannel(InputChannel inputChannel) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null.");
+        }
+        
+        nativeUnregisterInputChannel(inputChannel);
+    }
+    
+    // TBD where this really belongs, duplicate copy in WindowManagerService
+    static final int INJECT_FAILED = 0;
+    static final int INJECT_SUCCEEDED = 1;
+    static final int INJECT_NO_PERMISSION = -1;
+    
+    /**
+     * Injects a key event into the event system on behalf of an application.
+     * @param event The event to inject.
+     * @param nature The nature of the event.
+     * @param sync If true, waits for the event to be completed before returning.
+     * @param pid The pid of the injecting application.
+     * @param uid The uid of the injecting application.
+     * @return INJECT_SUCCEEDED, INJECT_FAILED or INJECT_NO_PERMISSION
+     */
+    public int injectKeyEvent(KeyEvent event, int nature, boolean sync, int pid, int uid) {
+        // TODO
+        return INJECT_FAILED;
+    }
+    
+    /**
+     * Injects a motion event into the event system on behalf of an application.
+     * @param event The event to inject.
+     * @param nature The nature of the event.
+     * @param sync If true, waits for the event to be completed before returning.
+     * @param pid The pid of the injecting application.
+     * @param uid The uid of the injecting application.
+     * @return INJECT_SUCCEEDED, INJECT_FAILED or INJECT_NO_PERMISSION
+     */
+    public int injectMotionEvent(MotionEvent event, int nature, boolean sync, int pid, int uid) {
+        // TODO
+        return INJECT_FAILED;
+    }
+    
+    public void dump(PrintWriter pw) {
+        // TODO
+    }
+    
+    private static final class VirtualKeyDefinition {
+        public int scanCode;
+        
+        // configured position data, specified in display coords
+        public int centerX;
+        public int centerY;
+        public int width;
+        public int height;
+    }
+    
+    /*
+     * Callbacks from native.
+     */
+    private class Callbacks {
+        static final String TAG = "InputManager-Callbacks";
+        
+        private static final boolean DEBUG_VIRTUAL_KEYS = false;
+        private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
+        
+        private final InputTargetList mReusableInputTargetList = new InputTargetList();
+        
+        @SuppressWarnings("unused")
+        public boolean isScreenOn() {
+            return mPowerManagerService.isScreenOn();
+        }
+        
+        @SuppressWarnings("unused")
+        public boolean isScreenBright() {
+            return mPowerManagerService.isScreenBright();
+        }
+        
+        @SuppressWarnings("unused")
+        public void virtualKeyFeedback(long whenNanos, int deviceId, int action, int flags,
+                int keyCode, int scanCode, int metaState, long downTimeNanos) {
+            KeyEvent keyEvent = new KeyEvent(downTimeNanos / 1000000,
+                    whenNanos / 1000000, action, keyCode, 0, metaState, scanCode, deviceId,
+                    flags);
+            
+            mWindowManagerService.virtualKeyFeedback(keyEvent);
+        }
+        
+        @SuppressWarnings("unused")
+        public void notifyConfigurationChanged(long whenNanos,
+                int touchScreenConfig, int keyboardConfig, int navigationConfig) {
+            mTouchScreenConfig = touchScreenConfig;
+            mKeyboardConfig = keyboardConfig;
+            mNavigationConfig = navigationConfig;
+            
+            mWindowManagerService.sendNewConfiguration();
+        }
+        
+        @SuppressWarnings("unused")
+        public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+            mWindowManagerPolicy.notifyLidSwitchChanged(whenNanos, lidOpen);
+        }
+        
+        @SuppressWarnings("unused")
+        public int hackInterceptKey(int deviceId, int type, int scanCode,
+                int keyCode, int policyFlags, int value, long whenNanos, boolean isScreenOn) {
+            RawInputEvent event = new RawInputEvent();
+            event.deviceId = deviceId;
+            event.type = type;
+            event.scancode = scanCode;
+            event.keycode = keyCode;
+            event.flags = policyFlags;
+            event.value = value;
+            event.when = whenNanos / 1000000;
+            
+            return mWindowManagerPolicy.interceptKeyTq(event, isScreenOn);
+        }
+        
+        @SuppressWarnings("unused")
+        public void goToSleep(long whenNanos) {
+            long when = whenNanos / 1000000;
+            mPowerManager.goToSleep(when);
+        }
+        
+        @SuppressWarnings("unused")
+        public void pokeUserActivityForKey(long whenNanos) {
+            long when = whenNanos / 1000000;
+            mPowerManagerService.userActivity(when, false,
+                    LocalPowerManager.BUTTON_EVENT, false);
+        }
+        
+        @SuppressWarnings("unused")
+        public void notifyAppSwitchComing() {
+            mWindowManagerService.mKeyWaiter.appSwitchComing();
+        }
+        
+        @SuppressWarnings("unused")
+        public boolean filterTouchEvents() {
+            return mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_filterTouchEvents);
+        }
+        
+        @SuppressWarnings("unused")
+        public boolean filterJumpyTouchEvents() {
+            return mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_filterJumpyTouchEvents);
+        }
+        
+        @SuppressWarnings("unused")
+        public VirtualKeyDefinition[] getVirtualKeyDefinitions(String deviceName) {
+            ArrayList<VirtualKeyDefinition> keys = new ArrayList<VirtualKeyDefinition>();
+            
+            try {
+                FileInputStream fis = new FileInputStream(
+                        "/sys/board_properties/virtualkeys." + deviceName);
+                InputStreamReader isr = new InputStreamReader(fis);
+                BufferedReader br = new BufferedReader(isr, 2048);
+                String str = br.readLine();
+                if (str != null) {
+                    String[] it = str.split(":");
+                    if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "***** VIRTUAL KEYS: " + it);
+                    final int N = it.length-6;
+                    for (int i=0; i<=N; i+=6) {
+                        if (!"0x01".equals(it[i])) {
+                            Slog.w(TAG, "Unknown virtual key type at elem #" + i
+                                    + ": " + it[i]);
+                            continue;
+                        }
+                        try {
+                            VirtualKeyDefinition key = new VirtualKeyDefinition();
+                            key.scanCode = Integer.parseInt(it[i+1]);
+                            key.centerX = Integer.parseInt(it[i+2]);
+                            key.centerY = Integer.parseInt(it[i+3]);
+                            key.width = Integer.parseInt(it[i+4]);
+                            key.height = Integer.parseInt(it[i+5]);
+                            if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Virtual key "
+                                    + key.scanCode + ": center=" + key.centerX + ","
+                                    + key.centerY + " size=" + key.width + "x"
+                                    + key.height);
+                            keys.add(key);
+                        } catch (NumberFormatException e) {
+                            Slog.w(TAG, "Bad number at region " + i + " in: "
+                                    + str, e);
+                        }
+                    }
+                }
+                br.close();
+            } catch (FileNotFoundException e) {
+                Slog.i(TAG, "No virtual keys found");
+            } catch (IOException e) {
+                Slog.w(TAG, "Error reading virtual keys", e);
+            }
+            
+            return keys.toArray(new VirtualKeyDefinition[keys.size()]);
+        }
+        
+        @SuppressWarnings("unused")
+        public String[] getExcludedDeviceNames() {
+            ArrayList<String> names = new ArrayList<String>();
+            
+            // Read partner-provided list of excluded input devices
+            XmlPullParser parser = null;
+            // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
+            File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH);
+            FileReader confreader = null;
+            try {
+                confreader = new FileReader(confFile);
+                parser = Xml.newPullParser();
+                parser.setInput(confreader);
+                XmlUtils.beginDocument(parser, "devices");
+
+                while (true) {
+                    XmlUtils.nextElement(parser);
+                    if (!"device".equals(parser.getName())) {
+                        break;
+                    }
+                    String name = parser.getAttributeValue(null, "name");
+                    if (name != null) {
+                        names.add(name);
+                    }
+                }
+            } catch (FileNotFoundException e) {
+                // It's ok if the file does not exist.
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e);
+            } finally {
+                try { if (confreader != null) confreader.close(); } catch (IOException e) { }
+            }
+            
+            return names.toArray(new String[names.size()]);
+        }
+        
+        @SuppressWarnings("unused")
+        public InputTarget[] getKeyEventTargets(KeyEvent event, int nature, int policyFlags) {
+            mReusableInputTargetList.clear();
+            
+            mWindowManagerService.getKeyEventTargets(mReusableInputTargetList,
+                    event, nature, policyFlags);
+            
+            return mReusableInputTargetList.toNullTerminatedArray();
+        }
+        
+        @SuppressWarnings("unused")
+        public InputTarget[] getMotionEventTargets(MotionEvent event, int nature, int policyFlags) {
+            mReusableInputTargetList.clear();
+            
+            mWindowManagerService.getMotionEventTargets(mReusableInputTargetList,
+                    event, nature, policyFlags);
+            
+            return mReusableInputTargetList.toNullTerminatedArray();
+        }
+    }
+}
diff --git a/services/java/com/android/server/InputTargetList.java b/services/java/com/android/server/InputTargetList.java
new file mode 100644
index 0000000..1575612
--- /dev/null
+++ b/services/java/com/android/server/InputTargetList.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.android.server;
+
+import android.view.InputChannel;
+import android.view.InputTarget;
+
+/**
+ * A specialized list of input targets backed by an array.
+ * 
+ * This class is part of an InputManager optimization to avoid allocating and copying
+ * input target arrays unnecessarily on return from JNI callbacks.  Internally, it keeps
+ * an array full of demand-allocated InputTarget objects that it recycles each time the
+ * list is cleared.  The used portion of the array is padded with a null.
+ * 
+ * @hide
+ */
+public class InputTargetList {
+    private InputTarget[] mArray;
+    private int mCount;
+    
+    /**
+     * Creates an empty input target list.
+     */
+    public InputTargetList() {
+        mArray = new InputTarget[8];
+    }
+    
+    /**
+     * Clears the input target list.
+     */
+    public void clear() {
+        if (mCount == 0) {
+            return;
+        }
+        
+        int count = mCount;
+        mCount = 0;
+        mArray[count] = mArray[0];
+        while (count > 0) {
+            count -= 1;
+            mArray[count].recycle();
+        }
+        // mArray[0] could be set to null here but we do it in toNullTerminatedArray()
+    }
+    
+    /**
+     * Adds a new input target to the input target list.
+     * @param inputChannel The input channel of the target window.
+     * @param flags Input target flags.
+     * @param timeoutNanos The input dispatch timeout (before ANR) in nanoseconds or -1 if none.
+     * @param xOffset An offset to add to motion X coordinates during delivery.
+     * @param yOffset An offset to add to motion Y coordinates during delivery.
+     */
+    public void add(InputChannel inputChannel, int flags, long timeoutNanos,
+            float xOffset, float yOffset) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null");
+        }
+        
+        if (mCount + 1 == mArray.length) {
+            InputTarget[] oldArray = mArray;
+            mArray = new InputTarget[oldArray.length * 2];
+            System.arraycopy(oldArray, 0, mArray, 0, mCount);
+        }
+        
+        // Grab InputTarget from tail (after used section) if available.
+        InputTarget inputTarget = mArray[mCount + 1];
+        if (inputTarget == null) {
+            inputTarget = new InputTarget();
+        }
+        inputTarget.mInputChannel = inputChannel;
+        inputTarget.mFlags = flags;
+        inputTarget.mTimeoutNanos = timeoutNanos;
+        inputTarget.mXOffset = xOffset;
+        inputTarget.mYOffset = yOffset;
+        
+        mArray[mCount] = inputTarget;
+        mCount += 1;
+        // mArray[mCount] could be set to null here but we do it in toNullTerminatedArray()
+    }
+    
+    /**
+     * Gets the input targets as a null-terminated array.
+     * @return The input target array.
+     */
+    public InputTarget[] toNullTerminatedArray() {
+        mArray[mCount] = null;
+        return mArray;
+    }
+}
\ No newline at end of file
diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java
index f30346b..f62c7ee 100644
--- a/services/java/com/android/server/KeyInputQueue.java
+++ b/services/java/com/android/server/KeyInputQueue.java
@@ -298,7 +298,9 @@
         
         mHapticFeedbackCallback = hapticFeedbackCallback;
         
-        readExcludedDevices();
+        if (! WindowManagerService.ENABLE_NATIVE_INPUT_DISPATCH) {
+            readExcludedDevices();
+        }
         
         PowerManager pm = (PowerManager)context.getSystemService(
                                                         Context.POWER_SERVICE);
@@ -311,7 +313,9 @@
         mFirst.next = mLast;
         mLast.prev = mFirst;
 
-        mThread.start();
+        if (! WindowManagerService.ENABLE_NATIVE_INPUT_DISPATCH) {
+            mThread.start();
+        }
     }
 
     public void setDisplay(Display display) {
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index ac5e3f1..9bc3931 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -101,6 +101,9 @@
 import android.view.IWindow;
 import android.view.IWindowManager;
 import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.InputQueue;
+import android.view.InputTarget;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.RawInputEvent;
@@ -157,6 +160,8 @@
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean HIDE_STACK_CRAWLS = true;
     static final boolean MEASURE_LATENCY = false;
+    static final boolean ENABLE_NATIVE_INPUT_DISPATCH =
+        WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH;
     static private LatencyTimer lt;
 
     static final boolean PROFILE_ORIENTATION = false;
@@ -497,10 +502,12 @@
 
     final KeyWaiter mKeyWaiter = new KeyWaiter();
     final KeyQ mQueue;
+    final InputManager mInputManager;
     final InputDispatcherThread mInputThread;
 
     // Who is holding the screen on.
     Session mHoldingScreenOn;
+    PowerManager.WakeLock mHoldingScreenWakeLock;
 
     boolean mTurnOnScreen;
 
@@ -650,8 +657,16 @@
         }
         mMinWaitTimeBetweenTouchEvents = 1000 / max_events_per_sec;
 
-        mQueue = new KeyQ();
+        mHoldingScreenWakeLock = pmc.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
+                "KEEP_SCREEN_ON_FLAG");
+        mHoldingScreenWakeLock.setReferenceCounted(false);
 
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputManager = new InputManager(context, this, mPolicy, pmc, mPowerManager);
+        } else {
+            mInputManager = null;
+        }
+        mQueue = new KeyQ();
         mInputThread = new InputDispatcherThread();
 
         PolicyThread thr = new PolicyThread(mPolicy, this, context, pm);
@@ -666,7 +681,11 @@
             }
         }
 
-        mInputThread.start();
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputManager.start();
+        } else {
+            mInputThread.start();
+        }
 
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
@@ -1859,7 +1878,7 @@
     
     public int addWindow(Session session, IWindow client,
             WindowManager.LayoutParams attrs, int viewVisibility,
-            Rect outContentInsets) {
+            Rect outContentInsets, InputChannel outInputChannel) {
         int res = mPolicy.checkAddPermission(attrs);
         if (res != WindowManagerImpl.ADD_OKAY) {
             return res;
@@ -1878,7 +1897,12 @@
                 mDisplay = wm.getDefaultDisplay();
                 mInitialDisplayWidth = mDisplay.getWidth();
                 mInitialDisplayHeight = mDisplay.getHeight();
-                mQueue.setDisplay(mDisplay);
+                if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                    mInputManager.setDisplaySize(0,
+                            mInitialDisplayWidth, mInitialDisplayHeight);
+                } else {
+                    mQueue.setDisplay(mDisplay);
+                }
                 reportNewConfig = true;
             }
 
@@ -1971,6 +1995,17 @@
             if (res != WindowManagerImpl.ADD_OKAY) {
                 return res;
             }
+            
+            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                if (outInputChannel != null) {
+                    String name = win.makeInputChannelName();
+                    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
+                    win.mInputChannel = inputChannels[0];
+                    inputChannels[1].transferToBinderOutParameter(outInputChannel);
+                    
+                    mInputManager.registerInputChannel(win.mInputChannel);
+                }
+            }
 
             // From now on, no exceptions or errors allowed!
 
@@ -4354,7 +4389,11 @@
                 "getSwitchState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return KeyInputQueue.getSwitchState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getSwitchState(sw);
+        } else {
+            return KeyInputQueue.getSwitchState(sw);
+        }
     }
 
     public int getSwitchStateForDevice(int devid, int sw) {
@@ -4362,7 +4401,11 @@
                 "getSwitchStateForDevice()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return KeyInputQueue.getSwitchState(devid, sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getSwitchState(devid, sw);
+        } else {
+            return KeyInputQueue.getSwitchState(devid, sw);
+        }
     }
 
     public int getScancodeState(int sw) {
@@ -4370,7 +4413,11 @@
                 "getScancodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getScancodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getScancodeState(sw);
+        } else {
+            return mQueue.getScancodeState(sw);
+        }
     }
 
     public int getScancodeStateForDevice(int devid, int sw) {
@@ -4378,7 +4425,11 @@
                 "getScancodeStateForDevice()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getScancodeState(devid, sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getScancodeState(devid, sw);
+        } else {
+            return mQueue.getScancodeState(devid, sw);
+        }
     }
 
     public int getTrackballScancodeState(int sw) {
@@ -4386,7 +4437,11 @@
                 "getTrackballScancodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getTrackballScancodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getTrackballScancodeState(sw);
+        } else {
+            return mQueue.getTrackballScancodeState(sw);
+        }
     }
 
     public int getDPadScancodeState(int sw) {
@@ -4394,7 +4449,11 @@
                 "getDPadScancodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getDPadScancodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getDPadScancodeState(sw);
+        } else {
+            return mQueue.getDPadScancodeState(sw);
+        }
     }
 
     public int getKeycodeState(int sw) {
@@ -4402,7 +4461,11 @@
                 "getKeycodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getKeycodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getKeycodeState(sw);
+        } else {
+            return mQueue.getKeycodeState(sw);
+        }
     }
 
     public int getKeycodeStateForDevice(int devid, int sw) {
@@ -4410,7 +4473,11 @@
                 "getKeycodeStateForDevice()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getKeycodeState(devid, sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getKeycodeState(devid, sw);
+        } else {
+            return mQueue.getKeycodeState(devid, sw);
+        }
     }
 
     public int getTrackballKeycodeState(int sw) {
@@ -4418,7 +4485,11 @@
                 "getTrackballKeycodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getTrackballKeycodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getTrackballKeycodeState(sw);
+        } else {
+            return mQueue.getTrackballKeycodeState(sw);
+        }
     }
 
     public int getDPadKeycodeState(int sw) {
@@ -4426,11 +4497,19 @@
                 "getDPadKeycodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getDPadKeycodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getDPadKeycodeState(sw);
+        } else {
+            return mQueue.getDPadKeycodeState(sw);
+        }
     }
 
     public boolean hasKeys(int[] keycodes, boolean[] keyExists) {
-        return KeyInputQueue.hasKeys(keycodes, keyExists);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.hasKeys(keycodes, keyExists);
+        } else {
+            return KeyInputQueue.hasKeys(keycodes, keyExists);
+        }
     }
 
     public void enableScreenAfterBoot() {
@@ -4575,7 +4654,11 @@
             mLayoutNeeded = true;
             startFreezingDisplayLocked();
             Slog.i(TAG, "Setting rotation to " + rotation + ", animFlags=" + animFlags);
-            mQueue.setOrientation(rotation);
+            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                mInputManager.setDisplayOrientation(0, rotation);
+            } else {
+                mQueue.setOrientation(rotation);
+            }
             if (mDisplayEnabled) {
                 Surface.setOrientation(0, rotation, animFlags);
             }
@@ -4906,7 +4989,11 @@
         if (mDisplay == null) {
             return false;
         }
-        mQueue.getInputConfiguration(config);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputManager.getInputConfiguration(config);
+        } else {
+            mQueue.getInputConfiguration(config);
+        }
 
         // Use the effective "visual" dimensions based on current rotation
         final boolean rotated = (mRotation == Surface.ROTATION_90
@@ -4989,6 +5076,291 @@
     // -------------------------------------------------------------
     // Input Events and Focus Management
     // -------------------------------------------------------------
+    
+    public void getKeyEventTargets(InputTargetList inputTargets,
+            KeyEvent event, int nature, int policyFlags) {
+        if (DEBUG_INPUT) Slog.v(TAG, "Dispatch key: " + event);
+
+        // TODO what do we do with mDisplayFrozen?
+        // TODO what do we do with focus.mToken.paused?
+        
+        WindowState focus = getFocusedWindow();
+        wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT);
+        
+        addInputTarget(inputTargets, focus, InputTarget.FLAG_SYNC);
+    }
+    
+    // Target of Motion events
+    WindowState mTouchFocus;
+
+    // Windows above the target who would like to receive an "outside"
+    // touch event for any down events outside of them.
+    // (This is a linked list by way of WindowState.mNextOutsideTouch.)
+    WindowState mOutsideTouchTargets;
+    
+    private void clearTouchFocus() {
+        mTouchFocus = null;
+        mOutsideTouchTargets = null;
+    }
+    
+    public void getMotionEventTargets(InputTargetList inputTargets,
+            MotionEvent event, int nature, int policyFlags) {
+        if (nature == InputQueue.INPUT_EVENT_NATURE_TRACKBALL) {
+            // More or less the same as for keys...
+            WindowState focus = getFocusedWindow();
+            wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT);
+            
+            addInputTarget(inputTargets, focus, InputTarget.FLAG_SYNC);
+            return;
+        }
+        
+        int action = event.getAction();
+
+        // TODO detect cheek presses somewhere... either here or in native code
+        
+        final boolean screenWasOff = (policyFlags & WindowManagerPolicy.FLAG_BRIGHT_HERE) != 0;
+        
+        WindowState target = mTouchFocus;
+        
+        if (action == MotionEvent.ACTION_UP) {
+            // let go of our target
+            mPowerManager.logPointerUpEvent();
+            clearTouchFocus();
+        } else if (action == MotionEvent.ACTION_DOWN) {
+            // acquire a new target
+            mPowerManager.logPointerDownEvent();
+        
+            synchronized (mWindowMap) {
+                if (mTouchFocus != null) {
+                    // this is weird, we got a pen down, but we thought it was
+                    // already down!
+                    // XXX: We should probably send an ACTION_UP to the current
+                    // target.
+                    Slog.w(TAG, "Pointer down received while already down in: "
+                            + mTouchFocus);
+                    clearTouchFocus();
+                }
+
+                // ACTION_DOWN is special, because we need to lock next events to
+                // the window we'll land onto.
+                final int x = (int) event.getX();
+                final int y = (int) event.getY();
+
+                final ArrayList windows = mWindows;
+                final int N = windows.size();
+                WindowState topErrWindow = null;
+                final Rect tmpRect = mTempRect;
+                for (int i=N-1; i>=0; i--) {
+                    WindowState child = (WindowState)windows.get(i);
+                    //Slog.i(TAG, "Checking dispatch to: " + child);
+                    final int flags = child.mAttrs.flags;
+                    if ((flags & WindowManager.LayoutParams.FLAG_SYSTEM_ERROR) != 0) {
+                        if (topErrWindow == null) {
+                            topErrWindow = child;
+                        }
+                    }
+                    if (!child.isVisibleLw()) {
+                        //Slog.i(TAG, "Not visible!");
+                        continue;
+                    }
+                    if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+                        //Slog.i(TAG, "Not touchable!");
+                        if ((flags & WindowManager.LayoutParams
+                                .FLAG_WATCH_OUTSIDE_TOUCH) != 0) {
+                            child.mNextOutsideTouch = mOutsideTouchTargets;
+                            mOutsideTouchTargets = child;
+                        }
+                        continue;
+                    }
+                    tmpRect.set(child.mFrame);
+                    if (child.mTouchableInsets == ViewTreeObserver
+                                .InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) {
+                        // The touch is inside of the window if it is
+                        // inside the frame, AND the content part of that
+                        // frame that was given by the application.
+                        tmpRect.left += child.mGivenContentInsets.left;
+                        tmpRect.top += child.mGivenContentInsets.top;
+                        tmpRect.right -= child.mGivenContentInsets.right;
+                        tmpRect.bottom -= child.mGivenContentInsets.bottom;
+                    } else if (child.mTouchableInsets == ViewTreeObserver
+                                .InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE) {
+                        // The touch is inside of the window if it is
+                        // inside the frame, AND the visible part of that
+                        // frame that was given by the application.
+                        tmpRect.left += child.mGivenVisibleInsets.left;
+                        tmpRect.top += child.mGivenVisibleInsets.top;
+                        tmpRect.right -= child.mGivenVisibleInsets.right;
+                        tmpRect.bottom -= child.mGivenVisibleInsets.bottom;
+                    }
+                    final int touchFlags = flags &
+                        (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+                    if (tmpRect.contains(x, y) || touchFlags == 0) {
+                        //Slog.i(TAG, "Using this target!");
+                        if (!screenWasOff || (flags &
+                                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING) != 0) {
+                            mTouchFocus = child;
+                        } else {
+                            //Slog.i(TAG, "Waking, skip!");
+                            mTouchFocus = null;
+                        }
+                        break;
+                    }
+
+                    if ((flags & WindowManager.LayoutParams
+                            .FLAG_WATCH_OUTSIDE_TOUCH) != 0) {
+                        child.mNextOutsideTouch = mOutsideTouchTargets;
+                        mOutsideTouchTargets = child;
+                        //Slog.i(TAG, "Adding to outside target list: " + child);
+                    }
+                }
+
+                // if there's an error window but it's not accepting
+                // focus (typically because it is not yet visible) just
+                // wait for it -- any other focused window may in fact
+                // be in ANR state.
+                if (topErrWindow != null && mTouchFocus != topErrWindow) {
+                    mTouchFocus = null;
+                }
+            }
+            
+            target = mTouchFocus;
+        }
+        
+        if (target != null) {
+            wakeupIfNeeded(target, eventType(event));
+        }
+        
+        int targetFlags = 0;
+        if (target == null) {
+            // In this case we are either dropping the event, or have received
+            // a move or up without a down.  It is common to receive move
+            // events in such a way, since this means the user is moving the
+            // pointer without actually pressing down.  All other cases should
+            // be atypical, so let's log them.
+            if (action != MotionEvent.ACTION_MOVE) {
+                Slog.w(TAG, "No window to dispatch pointer action " + action);
+            }
+        } else {
+            if ((target.mAttrs.flags &
+                    WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) != 0) {
+                //target wants to ignore fat touch events
+                boolean cheekPress = mPolicy.isCheekPressedAgainstScreen(event);
+                //explicit flag to return without processing event further
+                boolean returnFlag = false;
+                if((action == MotionEvent.ACTION_DOWN)) {
+                    mFatTouch = false;
+                    if(cheekPress) {
+                        mFatTouch = true;
+                        returnFlag = true;
+                    }
+                } else {
+                    if(action == MotionEvent.ACTION_UP) {
+                        if(mFatTouch) {
+                            //earlier even was invalid doesnt matter if current up is cheekpress or not
+                            mFatTouch = false;
+                            returnFlag = true;
+                        } else if(cheekPress) {
+                            //cancel the earlier event
+                            targetFlags |= InputTarget.FLAG_CANCEL;
+                            action = MotionEvent.ACTION_CANCEL;
+                        }
+                    } else if(action == MotionEvent.ACTION_MOVE) {
+                        if(mFatTouch) {
+                            //two cases here
+                            //an invalid down followed by 0 or moves(valid or invalid)
+                            //a valid down,  invalid move, more moves. want to ignore till up
+                            returnFlag = true;
+                        } else if(cheekPress) {
+                            //valid down followed by invalid moves
+                            //an invalid move have to cancel earlier action
+                            targetFlags |= InputTarget.FLAG_CANCEL;
+                            action = MotionEvent.ACTION_CANCEL;
+                            if (DEBUG_INPUT) Slog.v(TAG, "Sending cancel for invalid ACTION_MOVE");
+                            //note that the subsequent invalid moves will not get here
+                            mFatTouch = true;
+                        }
+                    }
+                } //else if action
+                if(returnFlag) {
+                    return;
+                }
+            } //end if target
+        }        
+        
+        synchronized (mWindowMap) {
+            if (target != null && ! target.isVisibleLw()) {
+                target = null;
+            }
+            
+            if (action == MotionEvent.ACTION_DOWN) {
+                while (mOutsideTouchTargets != null) {
+                    addInputTarget(inputTargets, mOutsideTouchTargets,
+                            InputTarget.FLAG_OUTSIDE | targetFlags);
+                    mOutsideTouchTargets = mOutsideTouchTargets.mNextOutsideTouch;
+                }
+            }
+            
+            // If we sent an initial down to the wallpaper, then continue
+            // sending events until the final up.
+            // Alternately if we are on top of the wallpaper, then the wallpaper also
+            // gets to see this movement.
+            if (mSendingPointersToWallpaper ||
+                    (target != null && action == MotionEvent.ACTION_DOWN
+                            && mWallpaperTarget == target
+                            && target.mAttrs.type != WindowManager.LayoutParams.TYPE_KEYGUARD)) {
+                int curTokenIndex = mWallpaperTokens.size();
+                while (curTokenIndex > 0) {
+                    curTokenIndex--;
+                    WindowToken token = mWallpaperTokens.get(curTokenIndex);
+                    int curWallpaperIndex = token.windows.size();
+                    while (curWallpaperIndex > 0) {
+                        curWallpaperIndex--;
+                        WindowState wallpaper = token.windows.get(curWallpaperIndex);
+                        if ((wallpaper.mAttrs.flags &
+                                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+                            continue;
+                        }
+                        
+                        switch (action) {
+                            case MotionEvent.ACTION_DOWN:
+                                mSendingPointersToWallpaper = true;
+                                break;
+                            case MotionEvent.ACTION_UP:
+                                mSendingPointersToWallpaper = false;
+                                break;
+                        }
+                        
+                        addInputTarget(inputTargets, wallpaper, targetFlags);
+                    }
+                }
+            }
+            
+            if (target != null) {
+                addInputTarget(inputTargets, target, InputTarget.FLAG_SYNC | targetFlags);
+            }
+        }
+    }
+    
+    private void addInputTarget(InputTargetList inputTargets, WindowState window, int flags) {
+        if (window.mInputChannel == null) {
+            return;
+        }
+        
+        long timeoutNanos = -1;
+        IApplicationToken appToken = window.getAppToken();
+
+        if (appToken != null) {
+            try {
+                timeoutNanos = appToken.getKeyDispatchingTimeout() * 1000000;
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Could not get key dispatching timeout.", ex);
+            }
+        }
+        
+        inputTargets.add(window.mInputChannel, flags, timeoutNanos,
+                - window.mFrame.left, - window.mFrame.top);
+    }
 
     private final void wakeupIfNeeded(WindowState targetWin, int eventType) {
         long curTime = SystemClock.uptimeMillis();
@@ -5499,10 +5871,18 @@
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         final long ident = Binder.clearCallingIdentity();
-        final int result = dispatchKey(newEvent, pid, uid);
-        if (sync) {
-            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+        
+        final int result;
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            result = mInputManager.injectKeyEvent(newEvent,
+                    InputQueue.INPUT_EVENT_NATURE_KEY, sync, pid, uid);
+        } else {
+            result = dispatchKey(newEvent, pid, uid);
+            if (sync) {
+                mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+            }
         }
+        
         Binder.restoreCallingIdentity(ident);
         switch (result) {
             case INJECT_NO_PERMISSION:
@@ -5527,10 +5907,18 @@
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         final long ident = Binder.clearCallingIdentity();
-        final int result = dispatchPointer(null, ev, pid, uid);
-        if (sync) {
-            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+        
+        final int result;
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            result = mInputManager.injectMotionEvent(ev,
+                    InputQueue.INPUT_EVENT_NATURE_TOUCH, sync, pid, uid);
+        } else {
+            result = dispatchPointer(null, ev, pid, uid);
+            if (sync) {
+                mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+            }
         }
+        
         Binder.restoreCallingIdentity(ident);
         switch (result) {
             case INJECT_NO_PERMISSION:
@@ -5555,10 +5943,18 @@
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         final long ident = Binder.clearCallingIdentity();
-        final int result = dispatchTrackball(null, ev, pid, uid);
-        if (sync) {
-            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+        
+        final int result;
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            result = mInputManager.injectMotionEvent(ev,
+                    InputQueue.INPUT_EVENT_NATURE_TRACKBALL, sync, pid, uid);
+        } else {
+            result = dispatchTrackball(null, ev, pid, uid);
+            if (sync) {
+                mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+            }
         }
+        
         Binder.restoreCallingIdentity(ident);
         switch (result) {
             case INJECT_NO_PERMISSION:
@@ -6326,14 +6722,8 @@
 
     private class KeyQ extends KeyInputQueue
             implements KeyInputQueue.FilterCallback {
-        PowerManager.WakeLock mHoldingScreen;
-
         KeyQ() {
             super(mContext, WindowManagerService.this);
-            PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
-            mHoldingScreen = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
-                    "KEEP_SCREEN_ON_FLAG");
-            mHoldingScreen.setReferenceCounted(false);
         }
 
         @Override
@@ -6445,21 +6835,6 @@
                     return FILTER_KEEP;
             }
         }
-
-        /**
-         * Must be called with the main window manager lock held.
-         */
-        void setHoldScreenLocked(boolean holding) {
-            boolean state = mHoldingScreen.isHeld();
-            if (holding != state) {
-                if (holding) {
-                    mHoldingScreen.acquire();
-                } else {
-                    mPolicy.screenOnStoppedLw();
-                    mHoldingScreen.release();
-                }
-            }
-        }
     }
 
     public boolean detectSafeMode() {
@@ -6788,8 +7163,14 @@
         }
 
         public int add(IWindow window, WindowManager.LayoutParams attrs,
+                int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) {
+            return addWindow(this, window, attrs, viewVisibility, outContentInsets,
+                    outInputChannel);
+        }
+        
+        public int addWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs,
                 int viewVisibility, Rect outContentInsets) {
-            return addWindow(this, window, attrs, viewVisibility, outContentInsets);
+            return addWindow(this, window, attrs, viewVisibility, outContentInsets, null);
         }
 
         public void remove(IWindow window) {
@@ -7158,6 +7539,9 @@
         int mSurfaceLayer;
         float mSurfaceAlpha;
         
+        // Input channel
+        InputChannel mInputChannel;
+        
         WindowState(Session s, IWindow c, WindowToken token,
                WindowState attachedWindow, WindowManager.LayoutParams a,
                int viewVisibility) {
@@ -8182,6 +8566,15 @@
                 // Ignore if it has already been removed (usually because
                 // we are doing this as part of processing a death note.)
             }
+            
+            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                if (mInputChannel != null) {
+                    mInputManager.unregisterInputChannel(mInputChannel);
+                    
+                    mInputChannel.dispose();
+                    mInputChannel = null;
+                }
+            }
         }
 
         private class DeathRecipient implements IBinder.DeathRecipient {
@@ -8424,6 +8817,11 @@
                         pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep);
             }
         }
+        
+        String makeInputChannelName() {
+            return Integer.toHexString(System.identityHashCode(this))
+                + " " + mAttrs.getTitle();
+        }
 
         @Override
         public String toString() {
@@ -9275,7 +9673,8 @@
             IInputContext inputContext) {
         if (client == null) throw new IllegalArgumentException("null client");
         if (inputContext == null) throw new IllegalArgumentException("null inputContext");
-        return new Session(client, inputContext);
+        Session session = new Session(client, inputContext);
+        return session;
     }
 
     public boolean inputMethodClientHasFocus(IInputMethodClient client) {
@@ -10773,7 +11172,7 @@
         if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen
                 + " holdScreen=" + holdScreen);
         if (!mDisplayFrozen) {
-            mQueue.setHoldScreenLocked(holdScreen != null);
+            setHoldScreenLocked(holdScreen != null);
             if (screenBrightness < 0 || screenBrightness > 1.0f) {
                 mPowerManager.setScreenBrightnessOverride(-1);
             } else {
@@ -10804,6 +11203,21 @@
         // be enabled, because the window obscured flags have changed.
         enableScreenIfNeededLocked();
     }
+    
+    /**
+     * Must be called with the main window manager lock held.
+     */
+    void setHoldScreenLocked(boolean holding) {
+        boolean state = mHoldingScreenWakeLock.isHeld();
+        if (holding != state) {
+            if (holding) {
+                mHoldingScreenWakeLock.acquire();
+            } else {
+                mPolicy.screenOnStoppedLw();
+                mHoldingScreenWakeLock.release();
+            }
+        }
+    }
 
     void requestAnimationLocked(long delay) {
         if (!mAnimationPending) {
@@ -11138,8 +11552,13 @@
             return;
         }
 
-        pw.println("Input State:");
-        mQueue.dump(pw, "  ");
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            pw.println("Input Dispatcher State:");
+            mInputManager.dump(pw);
+        } else {
+            pw.println("Input State:");
+            mQueue.dump(pw, "  ");
+        }
         pw.println(" ");
         
         synchronized(mWindowMap) {
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index b90e327..499ca86 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -5,6 +5,7 @@
     com_android_server_AlarmManagerService.cpp \
     com_android_server_BatteryService.cpp \
     com_android_server_KeyInputQueue.cpp \
+    com_android_server_InputManager.cpp \
     com_android_server_LightsService.cpp \
     com_android_server_SensorService.cpp \
     com_android_server_SystemServer.cpp \
@@ -16,6 +17,7 @@
 	$(JNI_H_INCLUDE)
 
 LOCAL_SHARED_LIBRARIES := \
+    libandroid_runtime \
 	libcutils \
 	libhardware \
 	libhardware_legacy \
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
new file mode 100644
index 0000000..53262ae
--- /dev/null
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -0,0 +1,746 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "InputManager-JNI"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <ui/InputManager.h>
+#include <ui/InputTransport.h>
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include "../../core/jni/android_view_KeyEvent.h"
+#include "../../core/jni/android_view_MotionEvent.h"
+#include "../../core/jni/android_view_InputChannel.h"
+#include "../../core/jni/android_view_InputTarget.h"
+
+namespace android {
+
+class InputDispatchPolicy : public InputDispatchPolicyInterface {
+public:
+    InputDispatchPolicy(JNIEnv* env, jobject callbacks);
+    virtual ~InputDispatchPolicy();
+
+    void setDisplaySize(int32_t displayId, int32_t width, int32_t height);
+    void setDisplayOrientation(int32_t displayId, int32_t orientation);
+
+    virtual bool getDisplayInfo(int32_t displayId,
+            int32_t* width, int32_t* height, int32_t* orientation);
+
+    virtual void notifyConfigurationChanged(nsecs_t when,
+            int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig);
+
+    virtual void notifyLidSwitchChanged(nsecs_t when, bool lidOpen);
+
+    virtual void virtualKeyFeedback(nsecs_t when, int32_t deviceId,
+            int32_t action, int32_t flags, int32_t keyCode,
+            int32_t scanCode, int32_t metaState, nsecs_t downTime);
+
+    virtual int32_t interceptKey(nsecs_t when, int32_t deviceId,
+            bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags);
+    virtual int32_t interceptTrackball(nsecs_t when, bool buttonChanged, bool buttonDown,
+            bool rolled);
+    virtual int32_t interceptTouch(nsecs_t when);
+
+    virtual bool filterTouchEvents();
+    virtual bool filterJumpyTouchEvents();
+    virtual void getVirtualKeyDefinitions(const String8& deviceName,
+            Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions);
+    virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
+
+    virtual bool allowKeyRepeat();
+    virtual nsecs_t getKeyRepeatTimeout();
+
+    virtual void getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
+            Vector<InputTarget>& outTargets);
+    virtual void getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
+            Vector<InputTarget>& outTargets);
+
+private:
+    bool isScreenOn();
+    bool isScreenBright();
+
+private:
+    jobject mCallbacks;
+
+    int32_t mFilterTouchEvents;
+    int32_t mFilterJumpyTouchEvents;
+
+    Mutex mDisplayLock;
+    int32_t mDisplayWidth, mDisplayHeight;
+    int32_t mDisplayOrientation;
+
+    inline JNIEnv* threadEnv() const {
+        return AndroidRuntime::getJNIEnv();
+    }
+};
+
+
+// globals
+
+static sp<EventHub> gEventHub;
+static sp<InputDispatchPolicy> gInputDispatchPolicy;
+static sp<InputManager> gInputManager;
+
+// JNI
+
+static struct {
+    jclass clazz;
+
+    jmethodID isScreenOn;
+    jmethodID isScreenBright;
+    jmethodID notifyConfigurationChanged;
+    jmethodID notifyLidSwitchChanged;
+    jmethodID virtualKeyFeedback;
+    jmethodID hackInterceptKey;
+    jmethodID goToSleep;
+    jmethodID pokeUserActivityForKey;
+    jmethodID notifyAppSwitchComing;
+    jmethodID filterTouchEvents;
+    jmethodID filterJumpyTouchEvents;
+    jmethodID getVirtualKeyDefinitions;
+    jmethodID getExcludedDeviceNames;
+    jmethodID getKeyEventTargets;
+    jmethodID getMotionEventTargets;
+} gCallbacksClassInfo;
+
+static struct {
+    jclass clazz;
+
+    jfieldID scanCode;
+    jfieldID centerX;
+    jfieldID centerY;
+    jfieldID width;
+    jfieldID height;
+} gVirtualKeyDefinitionClassInfo;
+
+static bool checkInputManagerUnitialized(JNIEnv* env) {
+    if (gInputManager == NULL) {
+        LOGE("Input manager not initialized.");
+        jniThrowRuntimeException(env, "Input manager not initialized.");
+        return true;
+    }
+    return false;
+}
+
+static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
+        jobject callbacks) {
+    if (gEventHub == NULL) {
+        gEventHub = new EventHub();
+    }
+
+    if (gInputDispatchPolicy == NULL) {
+        gInputDispatchPolicy = new InputDispatchPolicy(env, callbacks);
+    }
+
+    if (gInputManager == NULL) {
+        gInputManager = new InputManager(gEventHub, gInputDispatchPolicy);
+    }
+}
+
+static void android_server_InputManager_nativeStart(JNIEnv* env, jclass clazz) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    status_t result = gInputManager->start();
+    if (result) {
+        jniThrowRuntimeException(env, "Input manager could not be started.");
+    }
+}
+
+static void android_server_InputManager_nativeSetDisplaySize(JNIEnv* env, jclass clazz,
+        jint displayId, jint width, jint height) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    // XXX we could get this from the SurfaceFlinger directly instead of requiring it
+    // to be passed in like this, not sure which is better but leaving it like this
+    // keeps the window manager in direct control of when display transitions propagate down
+    // to the input dispatcher
+    gInputDispatchPolicy->setDisplaySize(displayId, width, height);
+}
+
+static void android_server_InputManager_nativeSetDisplayOrientation(JNIEnv* env, jclass clazz,
+        jint displayId, jint orientation) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    gInputDispatchPolicy->setDisplayOrientation(displayId, orientation);
+}
+
+static jint android_server_InputManager_nativeGetScanCodeState(JNIEnv* env, jclass clazz,
+        jint deviceId, jint deviceClasses, jint scanCode) {
+    if (checkInputManagerUnitialized(env)) {
+        return KEY_STATE_UNKNOWN;
+    }
+
+    return gInputManager->getScanCodeState(deviceId, deviceClasses, scanCode);
+}
+
+static jint android_server_InputManager_nativeGetKeyCodeState(JNIEnv* env, jclass clazz,
+        jint deviceId, jint deviceClasses, jint keyCode) {
+    if (checkInputManagerUnitialized(env)) {
+        return KEY_STATE_UNKNOWN;
+    }
+
+    return gInputManager->getKeyCodeState(deviceId, deviceClasses, keyCode);
+}
+
+static jint android_server_InputManager_nativeGetSwitchState(JNIEnv* env, jclass clazz,
+        jint deviceId, jint deviceClasses, jint sw) {
+    if (checkInputManagerUnitialized(env)) {
+        return KEY_STATE_UNKNOWN;
+    }
+
+    return gInputManager->getSwitchState(deviceId, deviceClasses, sw);
+}
+
+static jboolean android_server_InputManager_nativeHasKeys(JNIEnv* env, jclass clazz,
+        jintArray keyCodes, jbooleanArray outFlags) {
+    if (checkInputManagerUnitialized(env)) {
+        return JNI_FALSE;
+    }
+
+    int32_t* codes = env->GetIntArrayElements(keyCodes, NULL);
+    uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL);
+    jsize numCodes = env->GetArrayLength(keyCodes);
+    jboolean result;
+    if (numCodes == env->GetArrayLength(outFlags)) {
+        result = gInputManager->hasKeys(numCodes, codes, flags);
+    } else {
+        result = JNI_FALSE;
+    }
+
+    env->ReleaseBooleanArrayElements(outFlags, flags, 0);
+    env->ReleaseIntArrayElements(keyCodes, codes, 0);
+    return result;
+}
+
+static void throwInputChannelNotInitialized(JNIEnv* env) {
+    jniThrowException(env, "java/lang/IllegalStateException",
+             "inputChannel is not initialized");
+}
+
+static void android_server_InputManager_handleInputChannelDisposed(JNIEnv* env,
+        jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) {
+    LOGW("Input channel object '%s' was disposed without first being unregistered with "
+            "the input manager!", inputChannel->getName().string());
+
+    gInputManager->unregisterInputChannel(inputChannel);
+}
+
+static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
+        jobject inputChannelObj) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+            inputChannelObj);
+    if (inputChannel == NULL) {
+        throwInputChannelNotInitialized(env);
+        return;
+    }
+
+    status_t status = gInputManager->registerInputChannel(inputChannel);
+    if (status) {
+        jniThrowRuntimeException(env, "Failed to register input channel.  "
+                "Check logs for details.");
+        return;
+    }
+
+    android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
+            android_server_InputManager_handleInputChannelDisposed, NULL);
+}
+
+static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
+        jobject inputChannelObj) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+            inputChannelObj);
+    if (inputChannel == NULL) {
+        throwInputChannelNotInitialized(env);
+        return;
+    }
+
+    android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL);
+
+    status_t status = gInputManager->unregisterInputChannel(inputChannel);
+    if (status) {
+        jniThrowRuntimeException(env, "Failed to unregister input channel.  "
+                "Check logs for details.");
+    }
+}
+
+static JNINativeMethod gInputManagerMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeInit", "(Lcom/android/server/InputManager$Callbacks;)V",
+            (void*) android_server_InputManager_nativeInit },
+    { "nativeStart", "()V",
+            (void*) android_server_InputManager_nativeStart },
+    { "nativeSetDisplaySize", "(III)V",
+            (void*) android_server_InputManager_nativeSetDisplaySize },
+    { "nativeSetDisplayOrientation", "(II)V",
+            (void*) android_server_InputManager_nativeSetDisplayOrientation },
+    { "nativeGetScanCodeState", "(III)I",
+            (void*) android_server_InputManager_nativeGetScanCodeState },
+    { "nativeGetKeyCodeState", "(III)I",
+            (void*) android_server_InputManager_nativeGetKeyCodeState },
+    { "nativeGetSwitchState", "(III)I",
+            (void*) android_server_InputManager_nativeGetSwitchState },
+    { "nativeHasKeys", "([I[Z)Z",
+            (void*) android_server_InputManager_nativeHasKeys },
+    { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;)V",
+            (void*) android_server_InputManager_nativeRegisterInputChannel },
+    { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V",
+            (void*) android_server_InputManager_nativeUnregisterInputChannel }
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_server_InputManager(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "com/android/server/InputManager",
+            gInputManagerMethods, NELEM(gInputManagerMethods));
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    // Policy
+
+    FIND_CLASS(gCallbacksClassInfo.clazz, "com/android/server/InputManager$Callbacks");
+
+    GET_METHOD_ID(gCallbacksClassInfo.isScreenOn, gCallbacksClassInfo.clazz,
+            "isScreenOn", "()Z");
+
+    GET_METHOD_ID(gCallbacksClassInfo.isScreenBright, gCallbacksClassInfo.clazz,
+            "isScreenBright", "()Z");
+
+    GET_METHOD_ID(gCallbacksClassInfo.notifyConfigurationChanged, gCallbacksClassInfo.clazz,
+            "notifyConfigurationChanged", "(JIII)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.notifyLidSwitchChanged, gCallbacksClassInfo.clazz,
+            "notifyLidSwitchChanged", "(JZ)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.virtualKeyFeedback, gCallbacksClassInfo.clazz,
+            "virtualKeyFeedback", "(JIIIIIIJ)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.hackInterceptKey, gCallbacksClassInfo.clazz,
+            "hackInterceptKey", "(IIIIIIJZ)I");
+
+    GET_METHOD_ID(gCallbacksClassInfo.goToSleep, gCallbacksClassInfo.clazz,
+            "goToSleep", "(J)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.pokeUserActivityForKey, gCallbacksClassInfo.clazz,
+            "pokeUserActivityForKey", "(J)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.notifyAppSwitchComing, gCallbacksClassInfo.clazz,
+            "notifyAppSwitchComing", "()V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.filterTouchEvents, gCallbacksClassInfo.clazz,
+            "filterTouchEvents", "()Z");
+
+    GET_METHOD_ID(gCallbacksClassInfo.filterJumpyTouchEvents, gCallbacksClassInfo.clazz,
+            "filterJumpyTouchEvents", "()Z");
+
+    GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyDefinitions, gCallbacksClassInfo.clazz,
+            "getVirtualKeyDefinitions",
+            "(Ljava/lang/String;)[Lcom/android/server/InputManager$VirtualKeyDefinition;");
+
+    GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz,
+            "getExcludedDeviceNames", "()[Ljava/lang/String;");
+
+    GET_METHOD_ID(gCallbacksClassInfo.getKeyEventTargets, gCallbacksClassInfo.clazz,
+            "getKeyEventTargets", "(Landroid/view/KeyEvent;II)[Landroid/view/InputTarget;");
+
+    GET_METHOD_ID(gCallbacksClassInfo.getMotionEventTargets, gCallbacksClassInfo.clazz,
+            "getMotionEventTargets", "(Landroid/view/MotionEvent;II)[Landroid/view/InputTarget;");
+
+    // VirtualKeyDefinition
+
+    FIND_CLASS(gVirtualKeyDefinitionClassInfo.clazz,
+            "com/android/server/InputManager$VirtualKeyDefinition");
+
+    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.scanCode, gVirtualKeyDefinitionClassInfo.clazz,
+            "scanCode", "I");
+
+    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerX, gVirtualKeyDefinitionClassInfo.clazz,
+            "centerX", "I");
+
+    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerY, gVirtualKeyDefinitionClassInfo.clazz,
+            "centerY", "I");
+
+    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.width, gVirtualKeyDefinitionClassInfo.clazz,
+            "width", "I");
+
+    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.height, gVirtualKeyDefinitionClassInfo.clazz,
+            "height", "I");
+
+    return 0;
+}
+
+// static functions
+
+static bool isAppSwitchKey(int32_t keyCode) {
+    return keyCode == KEYCODE_HOME || keyCode == KEYCODE_ENDCALL;
+}
+
+static bool checkException(JNIEnv* env, const char* methodName) {
+    if (env->ExceptionCheck()) {
+        LOGE("An exception was thrown by an InputDispatchPolicy callback '%s'.", methodName);
+        LOGE_EX(env);
+        env->ExceptionClear();
+        return true;
+    }
+    return false;
+}
+
+
+// InputDispatchPolicy implementation
+
+InputDispatchPolicy::InputDispatchPolicy(JNIEnv* env, jobject callbacks) :
+        mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1),
+        mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(-1) {
+    mCallbacks = env->NewGlobalRef(callbacks);
+}
+
+InputDispatchPolicy::~InputDispatchPolicy() {
+    JNIEnv* env = threadEnv();
+
+    env->DeleteGlobalRef(mCallbacks);
+}
+
+void InputDispatchPolicy::setDisplaySize(int32_t displayId, int32_t width, int32_t height) {
+    if (displayId == 0) {
+        AutoMutex _l(mDisplayLock);
+
+        mDisplayWidth = width;
+        mDisplayHeight = height;
+    }
+}
+
+void InputDispatchPolicy::setDisplayOrientation(int32_t displayId, int32_t orientation) {
+    if (displayId == 0) {
+        AutoMutex _l(mDisplayLock);
+
+        mDisplayOrientation = orientation;
+    }
+}
+
+bool InputDispatchPolicy::getDisplayInfo(int32_t displayId,
+        int32_t* width, int32_t* height, int32_t* orientation) {
+    bool result = false;
+    if (displayId == 0) {
+        AutoMutex _l(mDisplayLock);
+
+        if (mDisplayWidth > 0) {
+            *width = mDisplayWidth;
+            *height = mDisplayHeight;
+            *orientation = mDisplayOrientation;
+            result = true;
+        }
+    }
+    return result;
+}
+
+bool InputDispatchPolicy::isScreenOn() {
+    JNIEnv* env = threadEnv();
+
+    jboolean result = env->CallBooleanMethod(mCallbacks, gCallbacksClassInfo.isScreenOn);
+    if (checkException(env, "isScreenOn")) {
+        return true;
+    }
+    return result;
+}
+
+bool InputDispatchPolicy::isScreenBright() {
+    JNIEnv* env = threadEnv();
+
+    jboolean result = env->CallBooleanMethod(mCallbacks, gCallbacksClassInfo.isScreenBright);
+    if (checkException(env, "isScreenBright")) {
+        return true;
+    }
+    return result;
+}
+
+void InputDispatchPolicy::notifyConfigurationChanged(nsecs_t when,
+        int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig) {
+    JNIEnv* env = threadEnv();
+
+    env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.notifyConfigurationChanged,
+            when, touchScreenConfig, keyboardConfig, navigationConfig);
+    checkException(env, "notifyConfigurationChanged");
+}
+
+void InputDispatchPolicy::notifyLidSwitchChanged(nsecs_t when, bool lidOpen) {
+    JNIEnv* env = threadEnv();
+    env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.notifyLidSwitchChanged,
+            when, lidOpen);
+    checkException(env, "notifyLidSwitchChanged");
+}
+
+void InputDispatchPolicy::virtualKeyFeedback(nsecs_t when, int32_t deviceId,
+        int32_t action, int32_t flags, int32_t keyCode,
+        int32_t scanCode, int32_t metaState, nsecs_t downTime) {
+    JNIEnv* env = threadEnv();
+
+    env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.virtualKeyFeedback,
+            when, deviceId, action, flags, keyCode, scanCode, metaState, downTime);
+    checkException(env, "virtualKeyFeedback");
+}
+
+int32_t InputDispatchPolicy::interceptKey(nsecs_t when,
+        int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) {
+    const int32_t WM_ACTION_PASS_TO_USER = 1;
+    const int32_t WM_ACTION_POKE_USER_ACTIVITY = 2;
+    const int32_t WM_ACTION_GO_TO_SLEEP = 4;
+
+    JNIEnv* env = threadEnv();
+
+    bool isScreenOn = this->isScreenOn();
+    bool isScreenBright = this->isScreenBright();
+
+    jint wmActions = env->CallIntMethod(mCallbacks, gCallbacksClassInfo.hackInterceptKey,
+            deviceId, EV_KEY, scanCode, keyCode, policyFlags, down ? 1 : 0, when, isScreenOn);
+    if (checkException(env, "hackInterceptKey")) {
+        wmActions = 0;
+    }
+
+    int32_t actions = ACTION_NONE;
+    if (! isScreenOn) {
+        // Key presses and releases wake the device.
+        actions |= ACTION_WOKE_HERE;
+    }
+
+    if (! isScreenBright) {
+        // Key presses and releases brighten the screen if dimmed.
+        actions |= ACTION_BRIGHT_HERE;
+    }
+
+    if (wmActions & WM_ACTION_GO_TO_SLEEP) {
+        env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.goToSleep, when);
+        checkException(env, "goToSleep");
+    }
+
+    if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) {
+        env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.pokeUserActivityForKey, when);
+        checkException(env, "pokeUserActivityForKey");
+    }
+
+    if (wmActions & WM_ACTION_PASS_TO_USER) {
+        actions |= ACTION_DISPATCH;
+    }
+
+    if (! (wmActions & WM_ACTION_PASS_TO_USER)) {
+        if (down && isAppSwitchKey(keyCode)) {
+            env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.notifyAppSwitchComing);
+            checkException(env, "notifyAppSwitchComing");
+
+            actions |= ACTION_APP_SWITCH_COMING;
+        }
+    }
+    return actions;
+}
+
+int32_t InputDispatchPolicy::interceptTouch(nsecs_t when) {
+    if (! isScreenOn()) {
+        // Touch events do not wake the device.
+        return ACTION_NONE;
+    }
+
+    return ACTION_DISPATCH;
+}
+
+int32_t InputDispatchPolicy::interceptTrackball(nsecs_t when,
+        bool buttonChanged, bool buttonDown, bool rolled) {
+    if (! isScreenOn()) {
+        // Trackball motions and button presses do not wake the device.
+        return ACTION_NONE;
+    }
+
+    return ACTION_DISPATCH;
+}
+
+bool InputDispatchPolicy::filterTouchEvents() {
+    if (mFilterTouchEvents < 0) {
+        JNIEnv* env = threadEnv();
+
+        jboolean result = env->CallBooleanMethod(mCallbacks,
+                gCallbacksClassInfo.filterTouchEvents);
+        if (checkException(env, "filterTouchEvents")) {
+            result = false;
+        }
+
+        mFilterTouchEvents = result ? 1 : 0;
+    }
+    return mFilterTouchEvents;
+}
+
+bool InputDispatchPolicy::filterJumpyTouchEvents() {
+    if (mFilterJumpyTouchEvents < 0) {
+        JNIEnv* env = threadEnv();
+
+        jboolean result = env->CallBooleanMethod(mCallbacks,
+                gCallbacksClassInfo.filterJumpyTouchEvents);
+        if (checkException(env, "filterJumpyTouchEvents")) {
+            result = false;
+        }
+
+        mFilterJumpyTouchEvents = result ? 1 : 0;
+    }
+    return mFilterJumpyTouchEvents;
+}
+
+void InputDispatchPolicy::getVirtualKeyDefinitions(const String8& deviceName,
+        Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) {
+    JNIEnv* env = threadEnv();
+
+    jstring deviceNameStr = env->NewStringUTF(deviceName.string());
+    if (! checkException(env, "getVirtualKeyDefinitions")) {
+        jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks,
+                gCallbacksClassInfo.getVirtualKeyDefinitions, deviceNameStr));
+        if (! checkException(env, "getVirtualKeyDefinitions") && result) {
+            jsize length = env->GetArrayLength(result);
+            for (jsize i = 0; i < length; i++) {
+                jobject item = env->GetObjectArrayElement(result, i);
+
+                outVirtualKeyDefinitions.add();
+                outVirtualKeyDefinitions.editTop().scanCode =
+                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.scanCode));
+                outVirtualKeyDefinitions.editTop().centerX =
+                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerX));
+                outVirtualKeyDefinitions.editTop().centerY =
+                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerY));
+                outVirtualKeyDefinitions.editTop().width =
+                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.width));
+                outVirtualKeyDefinitions.editTop().height =
+                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.height));
+
+                env->DeleteLocalRef(item);
+            }
+            env->DeleteLocalRef(result);
+        }
+        env->DeleteLocalRef(deviceNameStr);
+    }
+}
+
+void InputDispatchPolicy::getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) {
+    JNIEnv* env = threadEnv();
+
+    jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks,
+            gCallbacksClassInfo.getExcludedDeviceNames));
+    if (! checkException(env, "getExcludedDeviceNames") && result) {
+        jsize length = env->GetArrayLength(result);
+        for (jsize i = 0; i < length; i++) {
+            jstring item = jstring(env->GetObjectArrayElement(result, i));
+
+            const char* deviceNameChars = env->GetStringUTFChars(item, NULL);
+            outExcludedDeviceNames.add(String8(deviceNameChars));
+            env->ReleaseStringUTFChars(item, deviceNameChars);
+
+            env->DeleteLocalRef(item);
+        }
+        env->DeleteLocalRef(result);
+    }
+}
+
+bool InputDispatchPolicy::allowKeyRepeat() {
+    // Disable key repeat when the screen is off.
+    return isScreenOn();
+}
+
+nsecs_t InputDispatchPolicy::getKeyRepeatTimeout() {
+    // TODO use ViewConfiguration.getLongPressTimeout()
+    return milliseconds_to_nanoseconds(500);
+}
+
+void InputDispatchPolicy::getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
+        Vector<InputTarget>& outTargets) {
+    JNIEnv* env = threadEnv();
+
+    jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
+    if (! keyEventObj) {
+        LOGE("Could not obtain DVM KeyEvent object to get key event targets.");
+    } else {
+        jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks,
+                gCallbacksClassInfo.getKeyEventTargets,
+                keyEventObj, jint(keyEvent->getNature()), jint(policyFlags)));
+        if (! checkException(env, "getKeyEventTargets") && result) {
+            jsize length = env->GetArrayLength(result);
+            for (jsize i = 0; i < length; i++) {
+                jobject item = env->GetObjectArrayElement(result, i);
+                if (! item) {
+                    break; // found null element indicating end of used portion of the array
+                }
+
+                outTargets.add();
+                android_view_InputTarget_toNative(env, item, & outTargets.editTop());
+
+                env->DeleteLocalRef(item);
+            }
+            env->DeleteLocalRef(result);
+        }
+        env->DeleteLocalRef(keyEventObj);
+    }
+}
+
+void InputDispatchPolicy::getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
+        Vector<InputTarget>& outTargets) {
+    JNIEnv* env = threadEnv();
+
+    jobject motionEventObj = android_view_MotionEvent_fromNative(env, motionEvent);
+    if (! motionEventObj) {
+        LOGE("Could not obtain DVM MotionEvent object to get key event targets.");
+    } else {
+        jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks,
+                gCallbacksClassInfo.getMotionEventTargets,
+                motionEventObj, jint(motionEvent->getNature()), jint(policyFlags)));
+        if (! checkException(env, "getMotionEventTargets") && result) {
+            jsize length = env->GetArrayLength(result);
+            for (jsize i = 0; i < length; i++) {
+                jobject item = env->GetObjectArrayElement(result, i);
+                if (! item) {
+                    break; // found null element indicating end of used portion of the array
+                }
+
+                outTargets.add();
+                android_view_InputTarget_toNative(env, item, & outTargets.editTop());
+
+                env->DeleteLocalRef(item);
+            }
+            env->DeleteLocalRef(result);
+        }
+        android_view_MotionEvent_recycle(env, motionEventObj);
+        env->DeleteLocalRef(motionEventObj);
+    }
+}
+
+} /* namespace android */
diff --git a/services/jni/com_android_server_KeyInputQueue.cpp b/services/jni/com_android_server_KeyInputQueue.cpp
index c92f8df..f9e3585 100644
--- a/services/jni/com_android_server_KeyInputQueue.cpp
+++ b/services/jni/com_android_server_KeyInputQueue.cpp
@@ -156,7 +156,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getSwitchState(sw);
+    if (gHub != NULL) st = gHub->getSwitchState(-1, -1, sw);
     gLock.unlock();
     
     return st;
@@ -168,7 +168,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getSwitchState(deviceId, sw);
+    if (gHub != NULL) st = gHub->getSwitchState(deviceId, -1, sw);
     gLock.unlock();
     
     return st;
@@ -180,7 +180,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getScancodeState(sw);
+    if (gHub != NULL) st = gHub->getScanCodeState(0, -1, sw);
     gLock.unlock();
     
     return st;
@@ -192,7 +192,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getScancodeState(deviceId, sw);
+    if (gHub != NULL) st = gHub->getScanCodeState(deviceId, -1, sw);
     gLock.unlock();
     
     return st;
@@ -204,7 +204,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getKeycodeState(sw);
+    if (gHub != NULL) st = gHub->getKeyCodeState(0, -1, sw);
     gLock.unlock();
     
     return st;
@@ -216,7 +216,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getKeycodeState(deviceId, sw);
+    if (gHub != NULL) st = gHub->getKeyCodeState(deviceId,-1, sw);
     gLock.unlock();
     
     return st;
@@ -247,7 +247,7 @@
 
     int32_t* codes = env->GetIntArrayElements(keyCodes, NULL);
     uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL);
-    size_t numCodes = env->GetArrayLength(keyCodes);
+    jsize numCodes = env->GetArrayLength(keyCodes);
     if (numCodes == env->GetArrayLength(outFlags)) {
         gLock.lock();
         if (gHub != NULL) ret = gHub->hasKeys(numCodes, codes, flags);
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index d11e7e1..a1a6838 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -7,6 +7,7 @@
 int register_android_server_AlarmManagerService(JNIEnv* env);
 int register_android_server_BatteryService(JNIEnv* env);
 int register_android_server_KeyInputQueue(JNIEnv* env);
+int register_android_server_InputManager(JNIEnv* env);
 int register_android_server_LightsService(JNIEnv* env);
 int register_android_server_SensorService(JNIEnv* env);
 int register_android_server_VibratorService(JNIEnv* env);
@@ -28,6 +29,7 @@
     LOG_ASSERT(env, "Could not retrieve the env!");
 
     register_android_server_KeyInputQueue(env);
+    register_android_server_InputManager(env);
     register_android_server_LightsService(env);
     register_android_server_AlarmManagerService(env);
     register_android_server_BatteryService(env);