Native input event dispatching.

Target identification is now fully native.
Fixed a couple of minor issues related to input injection.
Native input enabled by default, can be disabled by setting
WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH to false.

Change-Id: I7edf66ed3e987cc9306ad4743ac57a116af452ff
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 95ab5bc..bf86b23 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -103,7 +103,6 @@
 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;
@@ -207,6 +206,9 @@
     // Maximum number of milliseconds to wait for input event injection.
     // FIXME is this value reasonable?
     private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
+    
+    // Default input dispatching timeout in nanoseconds.
+    private static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L;
 
     static final int INJECT_FAILED = 0;
     static final int INJECT_SUCCEEDED = 1;
@@ -2061,8 +2063,8 @@
 
             boolean focusChanged = false;
             if (win.canReceiveKeys()) {
-                if ((focusChanged=updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS))
-                        == true) {
+                focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS);
+                if (focusChanged) {
                     imMayMove = false;
                 }
             }
@@ -2078,10 +2080,9 @@
             //dump();
 
             if (focusChanged) {
-                if (mCurrentFocus != null) {
-                    mKeyWaiter.handleNewWindowLocked(mCurrentFocus);
-                }
+                finishUpdateFocusedWindowAfterAssignLayersLocked();
             }
+            
             if (localLOGV) Slog.v(
                 TAG, "New client " + client.asBinder()
                 + ": window=" + win);
@@ -2183,10 +2184,14 @@
     }
 
     private void removeWindowInnerLocked(Session session, WindowState win) {
-        mKeyWaiter.finishedKey(session, win.mClient, true,
-                KeyWaiter.RETURN_NOTHING);
-        mKeyWaiter.releasePendingPointerLocked(win.mSession);
-        mKeyWaiter.releasePendingTrackballLocked(win.mSession);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputMonitor.windowIsBeingRemovedLw(win);
+        } else {
+            mKeyWaiter.finishedKey(session, win.mClient, true,
+                    KeyWaiter.RETURN_NOTHING);
+            mKeyWaiter.releasePendingPointerLocked(win.mSession);
+            mKeyWaiter.releasePendingTrackballLocked(win.mSession);
+        }
 
         win.mRemoved = true;
 
@@ -2554,8 +2559,12 @@
                               applyAnimationLocked(win, transit, false)) {
                             focusMayChange = true;
                             win.mExiting = true;
-                            mKeyWaiter.finishedKey(session, client, true,
-                                    KeyWaiter.RETURN_NOTHING);
+                            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                                mInputMonitor.windowIsBecomingInvisibleLw(win);
+                            } else {
+                                mKeyWaiter.finishedKey(session, client, true,
+                                        KeyWaiter.RETURN_NOTHING);
+                            }
                         } else if (win.isAnimating()) {
                             // Currently in a hide animation... turn this into
                             // an exit.
@@ -3016,8 +3025,12 @@
                         if (win.isVisibleNow()) {
                             applyAnimationLocked(win,
                                     WindowManagerPolicy.TRANSIT_EXIT, false);
-                            mKeyWaiter.finishedKey(win.mSession, win.mClient, true,
-                                    KeyWaiter.RETURN_NOTHING);
+                            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                                mInputMonitor.windowIsBeingRemovedLw(win);
+                            } else {
+                                mKeyWaiter.finishedKey(win.mSession, win.mClient, true,
+                                        KeyWaiter.RETURN_NOTHING);
+                            }
                             changed = true;
                         }
                     }
@@ -3048,6 +3061,20 @@
                 "addAppToken()")) {
             throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
         }
+        
+        // Get the dispatching timeout here while we are not holding any locks so that it
+        // can be cached by the AppWindowToken.  The timeout value is used later by the
+        // input dispatcher in code that does hold locks.  If we did not cache the value
+        // here we would run the chance of introducing a deadlock between the window manager
+        // (which holds locks while updating the input dispatcher state) and the activity manager
+        // (which holds locks while querying the application token).
+        long inputDispatchingTimeoutNanos;
+        try {
+            inputDispatchingTimeoutNanos = token.getKeyDispatchingTimeout() * 1000000L;
+        } catch (RemoteException ex) {
+            Slog.w(TAG, "Could not get dispatching timeout.", ex);
+            inputDispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        }
 
         synchronized(mWindowMap) {
             AppWindowToken wtoken = findAppWindowToken(token.asBinder());
@@ -3056,6 +3083,7 @@
                 return;
             }
             wtoken = new AppWindowToken(token);
+            wtoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
             wtoken.groupId = groupId;
             wtoken.appFullscreen = fullscreen;
             wtoken.requestedOrientation = requestedOrientation;
@@ -3319,7 +3347,13 @@
                 if (DEBUG_FOCUS) Slog.v(TAG, "Clearing focused app, was " + mFocusedApp);
                 changed = mFocusedApp != null;
                 mFocusedApp = null;
-                mKeyWaiter.tickle();
+                if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                    if (changed) {
+                        mInputMonitor.setFocusedAppLw(null);
+                    }
+                } else {
+                    mKeyWaiter.tickle();
+                }
             } else {
                 AppWindowToken newFocus = findAppWindowToken(token);
                 if (newFocus == null) {
@@ -3329,7 +3363,13 @@
                 changed = mFocusedApp != newFocus;
                 mFocusedApp = newFocus;
                 if (DEBUG_FOCUS) Slog.v(TAG, "Set focused app to: " + mFocusedApp);
-                mKeyWaiter.tickle();
+                if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                    if (changed) {
+                        mInputMonitor.setFocusedAppLw(newFocus);
+                    }
+                } else {
+                    mKeyWaiter.tickle();
+                }
             }
 
             if (moveFocusNow && changed) {
@@ -3640,8 +3680,12 @@
                         applyAnimationLocked(win,
                                 WindowManagerPolicy.TRANSIT_EXIT, false);
                     }
-                    mKeyWaiter.finishedKey(win.mSession, win.mClient, true,
-                            KeyWaiter.RETURN_NOTHING);
+                    if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                        mInputMonitor.windowIsBecomingInvisibleLw(win);
+                    } else {
+                        mKeyWaiter.finishedKey(win.mSession, win.mClient, true,
+                                KeyWaiter.RETURN_NOTHING);
+                    }
                     changed = true;
                 }
             }
@@ -3926,7 +3970,11 @@
                     if (DEBUG_FOCUS) Slog.v(TAG, "Removing focused app token:" + wtoken);
                     mFocusedApp = null;
                     updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL);
-                    mKeyWaiter.tickle();
+                    if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                        mInputMonitor.setFocusedAppLw(null);
+                    } else {
+                        mKeyWaiter.tickle();
+                    }
                 }
             } else {
                 Slog.w(TAG, "Attempted to remove non-existing app token: " + token);
@@ -5075,456 +5123,375 @@
         return true;
     }
     
-    /* Notifies the window manager about a broken input channel.
-     * 
-     * Called by the InputManager.
-     */
-    public void notifyInputChannelBroken(InputChannel inputChannel) {
-        synchronized (mWindowMap) {
-            WindowState windowState = getWindowStateForInputChannelLocked(inputChannel);
-            if (windowState == null) {
-                return; // irrelevant
-            }
-            
-            Slog.i(TAG, "WINDOW DIED " + windowState);
-            removeWindowLocked(windowState.mSession, windowState);
-        }
-    }
-    
-    /* Notifies the window manager about a broken input channel.
-     * 
-     * Called by the InputManager.
-     */
-    public long notifyInputChannelANR(InputChannel inputChannel) {
-        IApplicationToken appToken;
-        synchronized (mWindowMap) {
-            WindowState windowState = getWindowStateForInputChannelLocked(inputChannel);
-            if (windowState == null) {
-                return -2; // irrelevant, abort dispatching (-2)
-            }
-            
-            Slog.i(TAG, "Input event dispatching timed out sending to "
-                    + windowState.mAttrs.getTitle());
-            appToken = windowState.getAppToken();
-        }
-        
-        try {
-            // Notify the activity manager about the timeout and let it decide whether
-            // to abort dispatching or keep waiting.
-            boolean abort = appToken.keyDispatchingTimedOut();
-            if (abort) {
-                return -2; // abort dispatching
-            }
-            
-            // Return new timeout.
-            // We use -1 for infinite timeout to avoid clash with -2 magic number.
-            long newTimeout = appToken.getKeyDispatchingTimeout() * 1000000;
-            return newTimeout < 0 ? -1 : newTimeout;
-        } catch (RemoteException ex) {
-            return -2; // abort dispatching
-        }
-    }
-
-    /* Notifies the window manager about a broken input channel.
-     * 
-     * Called by the InputManager.
-     */
-    public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) {
-        // Nothing to do just now.
-        // Just wait for the user to dismiss the ANR dialog.
-        
-        // TODO We could try to automatically dismiss the ANR dialog on recovery
-        //      although that might be disorienting.
-    }
-    
-    private WindowState getWindowStateForInputChannelLocked(InputChannel inputChannel) {
-        int windowCount = mWindows.size();
-        for (int i = 0; i < windowCount; i++) {
-            WindowState windowState = (WindowState) mWindows.get(i);
-            if (windowState.mInputChannel == inputChannel) {
-                return windowState;
-            }
-        }
-        
-        return null;
-    }
-
     // -------------------------------------------------------------
     // Input Events and Focus Management
     // -------------------------------------------------------------
     
-    private boolean checkInjectionPermissionTd(WindowState focus,
-            int injectorPid, int injectorUid) {
-        if (injectorUid > 0 && (focus == null || injectorUid != focus.mSession.mUid)) {
-            if (mContext.checkPermission(
-                    android.Manifest.permission.INJECT_EVENTS, injectorPid, injectorUid)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission denied: injecting key event from pid "
-                        + injectorPid + " uid " + injectorUid + " to window " + focus
-                        + " owned by uid " + focus.mSession.mUid);
-                return false;
+    InputMonitor mInputMonitor = new InputMonitor();
+    
+    /* Tracks the progress of input dispatch and ensures that input dispatch state
+     * is kept in sync with changes in window focus, visibility, registration, and
+     * other relevant Window Manager state transitions. */
+    final class InputMonitor {
+        // Current window with input focus for keys and other non-touch events.  May be null.
+        private WindowState mInputFocus;
+        
+        // When true, prevents input dispatch from proceeding until set to false again.
+        private boolean mInputDispatchFrozen;
+        
+        // When true, input dispatch proceeds normally.  Otherwise all events are dropped.
+        private boolean mInputDispatchEnabled = true;
+
+        // Temporary list of windows information to provide to the input dispatcher.
+        private InputWindowList mTempInputWindows = new InputWindowList();
+        
+        // Temporary input application object to provide to the input dispatcher.
+        private InputApplication mTempInputApplication = new InputApplication();
+        
+        /* Notifies the window manager about a broken input channel.
+         * 
+         * Called by the InputManager.
+         */
+        public void notifyInputChannelBroken(InputChannel inputChannel) {
+            synchronized (mWindowMap) {
+                WindowState windowState = getWindowStateForInputChannelLocked(inputChannel);
+                if (windowState == null) {
+                    return; // irrelevant
+                }
+                
+                Slog.i(TAG, "WINDOW DIED " + windowState);
+                removeWindowLocked(windowState.mSession, windowState);
             }
         }
-        return true;
-    }
-    
-    /* Gets the input targets for a key event.
-     * 
-     * Called by the InputManager on the InputDispatcher thread.
-     */
-    public int getKeyEventTargetsTd(InputTargetList inputTargets,
-            KeyEvent event, int nature, int policyFlags, int injectorPid, int injectorUid) {
-        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();
-        
-        if (! checkInjectionPermissionTd(focus, injectorPid, injectorUid)) {
-            return InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED;
-        }
-        
-        if (mPolicy.interceptKeyTi(focus, event.getKeyCode(), event.getMetaState(),
-                event.getAction() == KeyEvent.ACTION_DOWN,
-                event.getRepeatCount(), event.getFlags())) {
-            // Policy consumed the event.
-            return InputManager.INPUT_EVENT_INJECTION_SUCCEEDED;
-        }
-        
-        if (focus == null) {
-            return InputManager.INPUT_EVENT_INJECTION_FAILED;
-        }
-        
-        wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT);
-        
-        addInputTargetTd(inputTargets, focus, InputTarget.FLAG_SYNC);
-        return InputManager.INPUT_EVENT_INJECTION_SUCCEEDED;
-    }
-    
-    /* Gets the input targets for a motion event.
-     * 
-     * Called by the InputManager on the InputDispatcher thread.
-     */
-    public int getMotionEventTargetsTd(InputTargetList inputTargets,
-            MotionEvent event, int nature, int policyFlags, int injectorPid, int injectorUid) {
-        switch (nature) {
-            case InputQueue.INPUT_EVENT_NATURE_TRACKBALL:
-                return getMotionEventTargetsForTrackballTd(inputTargets, event, policyFlags,
-                        injectorPid, injectorUid);
+        /* Notifies the window manager about an input channel that is not responding.
+         * The method can either cause dispatching to be aborted by returning -2 or
+         * return a new timeout in nanoseconds.
+         * 
+         * Called by the InputManager.
+         */
+        public long notifyInputChannelANR(InputChannel inputChannel) {
+            AppWindowToken token;
+            synchronized (mWindowMap) {
+                WindowState windowState = getWindowStateForInputChannelLocked(inputChannel);
+                if (windowState == null) {
+                    return -2; // irrelevant, abort dispatching (-2)
+                }
                 
-            case InputQueue.INPUT_EVENT_NATURE_TOUCH:
-                return getMotionEventTargetsForTouchTd(inputTargets, event, policyFlags,
-                        injectorPid, injectorUid);
-                
-            default:
-                return InputManager.INPUT_EVENT_INJECTION_FAILED;
-        }
-    }
-    
-    /* Gets the input targets for a trackball event.
-     * 
-     * Called by the InputManager on the InputDispatcher thread.
-     */
-    private int getMotionEventTargetsForTrackballTd(InputTargetList inputTargets,
-            MotionEvent event, int policyFlags, int injectorPid, int injectorUid) {
-        WindowState focus = getFocusedWindow();
-        
-        if (! checkInjectionPermissionTd(focus, injectorPid, injectorUid)) {
-            return InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED;
-        }
-        
-        if (focus == null) {
-            return InputManager.INPUT_EVENT_INJECTION_FAILED;
-        }
-        
-        wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT);
-        
-        addInputTargetTd(inputTargets, focus, InputTarget.FLAG_SYNC);
-        return InputManager.INPUT_EVENT_INJECTION_SUCCEEDED;
-    }
-    
-    /* Set to true when a fat touch has been detected during the processing of a touch event.
-     * 
-     * Only used by getMotionEventTargetsForTouchTd.
-     * Set to true whenever a fat touch is detected and reset to false on ACTION_UP.
-     */
-    private boolean mFatTouch;
-    
-    /* Set to true when we think the touch event.
-     * 
-     * Only used by getMotionEventTargetsForTouchTd.
-     * Set to true on ACTION_DOWN and set to false on ACTION_UP.
-     */
-    private boolean mTouchDown;
-    
-    /* Current target of Motion events.
-     * 
-     * Only used by getMotionEventTargetsForTouchTd.
-     * Initialized on ACTION_DOWN and cleared on ACTION_UP.
-     */
-    private WindowState mTouchFocus;
-
-    /* Windows above the target that would like to receive an "outside" touch event
-     * for any down events outside of them.
-     * 
-     * Only used by getMotionEventTargetsForTouchTd.
-     * Initialized on ACTION_DOWN and cleared immediately afterwards.
-     */
-    private ArrayList<WindowState> mOutsideTouchTargets = new ArrayList<WindowState>();
-    
-    /* Wallpaper windows that are currently receiving touch events.
-     * 
-     * Only used by getMotionEventTargetsForTouchTd.
-     * Initialized on ACTION_DOWN and cleared on ACTION_UP.
-     */
-    private ArrayList<WindowState> mWallpaperTouchTargets = new ArrayList<WindowState>();
-    
-    /* Gets the input targets for a touch event.
-     * 
-     * Called by the InputManager on the InputDispatcher thread.
-     */
-    private int getMotionEventTargetsForTouchTd(InputTargetList inputTargets,
-            MotionEvent event, int policyFlags, int injectorPid, int injectorUid) {
-        final int action = event.getAction();
-        
-        if (action == MotionEvent.ACTION_DOWN) {
-            updateTouchFocusBeforeDownTd(event, policyFlags);
-        } else {
-            updateTouchFocusBeforeNonDownTd(event, policyFlags);
-        }
-
-        boolean skipDelivery = false;
-        int touchTargetFlags = 0;
-        
-        int injectionResult = InputManager.INPUT_EVENT_INJECTION_SUCCEEDED;
-        WindowState focusedTouchTarget = mTouchFocus;
-        if (focusedTouchTarget == 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);
-                injectionResult = InputManager.INPUT_EVENT_INJECTION_FAILED;
-            }
-        } else {
-            // We have a valid focused touch target.
-            if (! checkInjectionPermissionTd(focusedTouchTarget, injectorPid, injectorUid)) {
-                return InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+                Slog.i(TAG, "Input event dispatching timed out sending to "
+                        + windowState.mAttrs.getTitle());
+                token = windowState.mAppToken;
             }
             
-            wakeupIfNeeded(focusedTouchTarget, eventType(event));
-            
-            if ((focusedTouchTarget.mAttrs.flags &
-                    WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) != 0) {
-                // Target wants to ignore fat touch events
-                boolean cheekPress = mPolicy.isCheekPressedAgainstScreen(event);
-                
-                if (cheekPress) {
-                    if ((action == MotionEvent.ACTION_DOWN)) {
-                        mFatTouch = true;
-                        skipDelivery = true;
-                    } else {
-                        if (! mFatTouch) {
-                            // cancel the earlier event
-                            touchTargetFlags |= InputTarget.FLAG_CANCEL;
-                            mFatTouch = true;
-                        } else {
-                            skipDelivery = true;
-                        }
+            return notifyANRInternal(token);
+        }
+    
+        /* Notifies the window manager about an input channel spontaneously recovering from ANR
+         * by successfully delivering the event that originally timed out.
+         * 
+         * Called by the InputManager.
+         */
+        public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) {
+            // Nothing to do just now.
+            // Just wait for the user to dismiss the ANR dialog.
+        }
+        
+        /* Notifies the window manager about an application that is not responding
+         * in general rather than with respect to a particular input channel.
+         * The method can either cause dispatching to be aborted by returning -2 or
+         * return a new timeout in nanoseconds.
+         * 
+         * Called by the InputManager.
+         */
+        public long notifyANR(Object token) {
+            AppWindowToken appWindowToken = (AppWindowToken) token;
+
+            Slog.i(TAG, "Input event dispatching timed out sending to application "
+                    + appWindowToken.stringName);
+            return notifyANRInternal(appWindowToken);
+        }
+        
+        private long notifyANRInternal(AppWindowToken token) {
+            if (token != null && token.appToken != null) {
+                try {
+                    // Notify the activity manager about the timeout and let it decide whether
+                    // to abort dispatching or keep waiting.
+                    boolean abort = token.appToken.keyDispatchingTimedOut();
+                    if (! abort) {
+                        // The activity manager declined to abort dispatching.
+                        // Wait a bit longer and timeout again later.
+                        return token.inputDispatchingTimeoutNanos;
                     }
+                } catch (RemoteException ex) {
                 }
             }
+            return -2; // abort dispatching
         }
         
-        if (! skipDelivery) {
-            int outsideTargetCount = mOutsideTouchTargets.size();
-            for (int i = 0; i < outsideTargetCount; i++) {
-                WindowState outsideTouchTarget = mOutsideTouchTargets.get(i);
-                addInputTargetTd(inputTargets, outsideTouchTarget,
-                        InputTarget.FLAG_OUTSIDE | touchTargetFlags);
+        private WindowState getWindowStateForInputChannel(InputChannel inputChannel) {
+            synchronized (mWindowMap) {
+                return getWindowStateForInputChannelLocked(inputChannel);
+            }
+        }
+        
+        private WindowState getWindowStateForInputChannelLocked(InputChannel inputChannel) {
+            int windowCount = mWindows.size();
+            for (int i = 0; i < windowCount; i++) {
+                WindowState windowState = (WindowState) mWindows.get(i);
+                if (windowState.mInputChannel == inputChannel) {
+                    return windowState;
+                }
             }
             
-            int wallpaperTargetCount = mWallpaperTouchTargets.size();
-            for (int i = 0; i < wallpaperTargetCount; i++) {
-                WindowState wallpaperTouchTarget = mWallpaperTouchTargets.get(i);
-                addInputTargetTd(inputTargets, wallpaperTouchTarget,
-                        touchTargetFlags);
-            }
-            
-            if (focusedTouchTarget != null) {
-                addInputTargetTd(inputTargets, focusedTouchTarget,
-                        InputTarget.FLAG_SYNC | touchTargetFlags);
-            }
-        }
-
-        if (action == MotionEvent.ACTION_UP) {
-            updateTouchFocusAfterUpTd(event, policyFlags);
+            return null;
         }
         
-        return injectionResult;
-    }
-    
-    private void updateTouchFocusBeforeDownTd(MotionEvent event, int policyFlags) {
-        if (mTouchDown) {
-            // This is weird, we got a 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);
-            updateTouchFocusAfterUpTd(event, policyFlags);
-        }
-        
-        mTouchDown = true;
-        mPowerManager.logPointerDownEvent();
-        
-        final boolean screenWasOff = (policyFlags & WindowManagerPolicy.FLAG_BRIGHT_HERE) != 0;
-        synchronized (mWindowMap) {
-            final int x = (int) event.getX();
-            final int y = (int) event.getY();
-
+        /* Updates the cached window information provided to the input dispatcher. */
+        public void updateInputWindowsLw() {
+            // Populate the input window list with information about all of the windows that
+            // could potentially receive input.
+            // As an optimization, we could try to prune the list of windows but this turns
+            // out to be difficult because only the native code knows for sure which window
+            // currently has touch focus.
             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!");
+            for (int i = N - 1; i >= 0; i--) {
+                final WindowState child = (WindowState) windows.get(i);
+                if (child.mInputChannel == null) {
+                    // Skip this window because it cannot possibly receive input.
                     continue;
                 }
                 
-                if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) == 0) {
-                    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 flags = child.mAttrs.flags;
+                final int type = child.mAttrs.type;
+                
+                final boolean hasFocus = (child == mInputFocus);
+                final boolean isVisible = child.isVisibleLw();
+                final boolean hasWallpaper = (child == mWallpaperTarget)
+                        && (type != WindowManager.LayoutParams.TYPE_KEYGUARD);
+                
+                // Add a window to our list of input windows.
+                final InputWindow inputWindow = mTempInputWindows.add();
+                inputWindow.inputChannel = child.mInputChannel;
+                inputWindow.layoutParamsFlags = flags;
+                inputWindow.layoutParamsType = type;
+                inputWindow.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
+                inputWindow.visible = isVisible;
+                inputWindow.hasFocus = hasFocus;
+                inputWindow.hasWallpaper = hasWallpaper;
+                inputWindow.paused = child.mAppToken != null ? child.mAppToken.paused : false;
+                inputWindow.ownerPid = child.mSession.mPid;
+                inputWindow.ownerUid = child.mSession.mUid;
+                
+                final Rect frame = child.mFrame;
+                inputWindow.frameLeft = frame.left;
+                inputWindow.frameTop = frame.top;
+                
+                switch (child.mTouchableInsets) {
+                    default:
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+                        inputWindow.touchableAreaLeft = frame.left;
+                        inputWindow.touchableAreaTop = frame.top;
+                        inputWindow.touchableAreaRight = frame.right;
+                        inputWindow.touchableAreaBottom = frame.bottom;
+                        break;
+                        
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: {
+                        Rect inset = child.mGivenContentInsets;
+                        inputWindow.touchableAreaLeft = frame.left + inset.left;
+                        inputWindow.touchableAreaTop = frame.top + inset.top;
+                        inputWindow.touchableAreaRight = frame.right - inset.right;
+                        inputWindow.touchableAreaBottom = frame.bottom - inset.bottom;
+                        break;
                     }
-                    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;
-                        }
+                        
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: {
+                        Rect inset = child.mGivenVisibleInsets;
+                        inputWindow.touchableAreaLeft = frame.left + inset.left;
+                        inputWindow.touchableAreaTop = frame.top + inset.top;
+                        inputWindow.touchableAreaRight = frame.right - inset.right;
+                        inputWindow.touchableAreaBottom = frame.bottom - inset.bottom;
                         break;
                     }
                 }
-
-                if ((flags & WindowManager.LayoutParams
-                        .FLAG_WATCH_OUTSIDE_TOUCH) != 0) {
-                    //Slog.i(TAG, "Adding to outside target list: " + child);
-                    mOutsideTouchTargets.add(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;
-            }
+            // Send windows to native code.
+            mInputManager.setInputWindows(mTempInputWindows.toNullTerminatedArray());
             
-            // Drop the touch focus if the window is not visible.
-            if (mTouchFocus != null && ! mTouchFocus.isVisibleLw()) {
-                mTouchFocus = null;
-            }
+            // Clear the list in preparation for the next round.
+            // Also avoids keeping InputChannel objects referenced unnecessarily.
+            mTempInputWindows.clear();
+        }
+        
+        /* Notifies that an app switch key (BACK / HOME) has just been pressed.
+         * This essentially starts a .5 second timeout for the application to process
+         * subsequent input events while waiting for the app switch to occur.  If it takes longer
+         * than this, the pending events will be dropped.
+         */
+        public void notifyAppSwitchComing() {
+            // TODO Not implemented yet.  Should go in the native side.
+        }
+
+        /* Provides an opportunity for the window manager policy to intercept early key
+         * processing as soon as the key has been read from the device. */
+        public int interceptKeyBeforeQueueing(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;
             
-            // Determine wallpaper targets.
-            if (mTouchFocus != null
-                    && mTouchFocus == mWallpaperTarget
-                    && mTouchFocus.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) {
-                            mWallpaperTouchTargets.add(wallpaper);
+            return mPolicy.interceptKeyTq(event, isScreenOn);
+        }
+        
+        /* Provides an opportunity for the window manager policy to process a key before
+         * ordinary dispatch. */
+        public boolean interceptKeyBeforeDispatching(InputChannel focus, int keyCode,
+                int metaState, boolean down, int repeatCount, int policyFlags) {
+            WindowState windowState = getWindowStateForInputChannel(focus);
+            return mPolicy.interceptKeyTi(windowState, keyCode, metaState, down, repeatCount,
+                    policyFlags);
+        }
+        
+        /* Called when the current input focus changes.
+         * Layer assignment is assumed to be complete by the time this is called.
+         */
+        public void setInputFocusLw(WindowState newWindow) {
+            if (DEBUG_INPUT) {
+                Slog.d(TAG, "Input focus has changed to " + newWindow);
+            }
+
+            if (newWindow != mInputFocus) {
+                if (newWindow != null && newWindow.canReceiveKeys()) {
+                    // If the new input focus is an error window or appears above the current
+                    // input focus, preempt any pending synchronous dispatch so that we can
+                    // start delivering events to the new input focus as soon as possible.
+                    if ((newWindow.mAttrs.flags & FLAG_SYSTEM_ERROR) != 0) {
+                        if (DEBUG_INPUT) {
+                            Slog.v(TAG, "New SYSTEM_ERROR window; resetting state");
                         }
+                        preemptInputDispatchLw();
+                    } else if (mInputFocus != null && newWindow.mLayer > mInputFocus.mLayer) {
+                        if (DEBUG_INPUT) {
+                            Slog.v(TAG, "Transferring focus to new window at higher layer: "
+                                    + "old win layer=" + mInputFocus.mLayer
+                                    + ", new win layer=" + newWindow.mLayer);
+                        }
+                        preemptInputDispatchLw();
                     }
+                    
+                    // Displaying a window implicitly causes dispatching to be unpaused.
+                    // This is to protect against bugs if someone pauses dispatching but
+                    // forgets to resume.
+                    newWindow.mToken.paused = false;
                 }
-            }
-        }
-    }
-
-    private void updateTouchFocusBeforeNonDownTd(MotionEvent event, int policyFlags) {
-        synchronized (mWindowMap) {
-            // Drop the touch focus if the window is not visible.
-            if (mTouchFocus != null && ! mTouchFocus.isVisibleLw()) {
-                mTouchFocus = null;
-                mWallpaperTouchTargets.clear();
-            }
-        }
-    }
-    
-    private void updateTouchFocusAfterUpTd(MotionEvent event, int policyFlags) {
-        mFatTouch = false;
-        mTouchDown = false;
-        mTouchFocus = null;
-        mOutsideTouchTargets.clear();
-        mWallpaperTouchTargets.clear();
-        
-        mPowerManager.logPointerUpEvent();
-    }
-
-    /* Adds a window to a list of input targets.
-     * Do NOT call this method while holding any locks because the call to
-     * appToken.getKeyDispatchingTimeout() can potentially call into the ActivityManager
-     * and create a deadlock hazard.
-     */
-    private void addInputTargetTd(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);
+            
+                mInputFocus = newWindow;
+                updateInputWindowsLw();
             }
         }
         
-        inputTargets.add(window.mInputChannel, flags, timeoutNanos,
-                - window.mFrame.left, - window.mFrame.top);
+        public void windowIsBecomingInvisibleLw(WindowState window) {
+            // The window is becoming invisible.  Preempt input dispatch in progress
+            // so that the next window below can receive focus.
+            if (window == mInputFocus) {
+                mInputFocus = null;
+                preemptInputDispatchLw();
+            }
+            
+            updateInputWindowsLw();
+        }
+        
+        /* Tells the dispatcher to stop waiting for its current synchronous event targets.
+         * Essentially, just makes those dispatches asynchronous so a new dispatch cycle
+         * can begin.
+         */
+        private void preemptInputDispatchLw() {
+            mInputManager.preemptInputDispatch();
+        }
+        
+        public void setFocusedAppLw(AppWindowToken newApp) {
+            // Focused app has changed.
+            if (newApp == null) {
+                mInputManager.setFocusedApplication(null);
+            } else {
+                mTempInputApplication.name = newApp.toString();
+                mTempInputApplication.dispatchingTimeoutNanos =
+                        newApp.inputDispatchingTimeoutNanos;
+                mTempInputApplication.token = newApp;
+                
+                mInputManager.setFocusedApplication(mTempInputApplication);
+            }
+        }
+        
+        public void windowIsBeingRemovedLw(WindowState window) {
+            // Window is being removed.
+            updateInputWindowsLw();
+        }
+        
+        public void pauseDispatchingLw(WindowToken window) {
+            if (! window.paused) {
+                if (DEBUG_INPUT) {
+                    Slog.v(TAG, "Pausing WindowToken " + window);
+                }
+                
+                window.paused = true;
+                updateInputWindowsLw();
+            }
+        }
+        
+        public void resumeDispatchingLw(WindowToken window) {
+            if (window.paused) {
+                if (DEBUG_INPUT) {
+                    Slog.v(TAG, "Resuming WindowToken " + window);
+                }
+                
+                window.paused = false;
+                updateInputWindowsLw();
+            }
+        }
+        
+        public void freezeInputDispatchingLw() {
+            if (! mInputDispatchFrozen) {
+                if (DEBUG_INPUT) {
+                    Slog.v(TAG, "Freezing input dispatching");
+                }
+                
+                mInputDispatchFrozen = true;
+                updateInputDispatchModeLw();
+            }
+        }
+        
+        public void thawInputDispatchingLw() {
+            if (mInputDispatchFrozen) {
+                if (DEBUG_INPUT) {
+                    Slog.v(TAG, "Thawing input dispatching");
+                }
+                
+                mInputDispatchFrozen = false;
+                updateInputDispatchModeLw();
+            }
+        }
+        
+        public void setEventDispatchingLw(boolean enabled) {
+            if (mInputDispatchEnabled != enabled) {
+                if (DEBUG_INPUT) {
+                    Slog.v(TAG, "Setting event dispatching to " + enabled);
+                }
+                
+                mInputDispatchEnabled = enabled;
+                updateInputDispatchModeLw();
+            }
+        }
+        
+        private void updateInputDispatchModeLw() {
+            mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
+        }
     }
 
     private final void wakeupIfNeeded(WindowState targetWin, int eventType) {
@@ -5582,6 +5549,8 @@
             return OTHER_EVENT;
         }
     }
+    
+    private boolean mFatTouch; // remove me together with dispatchPointer
 
     /**
      * @return Returns true if event was dispatched, false if it was dropped for any reason
@@ -5978,7 +5947,11 @@
         synchronized (mWindowMap) {
             WindowToken token = mTokenMap.get(_token);
             if (token != null) {
-                mKeyWaiter.pauseDispatchingLocked(token);
+                if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                    mInputMonitor.pauseDispatchingLw(token);
+                } else {
+                    mKeyWaiter.pauseDispatchingLocked(token);
+                }
             }
         }
     }
@@ -5992,7 +5965,11 @@
         synchronized (mWindowMap) {
             WindowToken token = mTokenMap.get(_token);
             if (token != null) {
-                mKeyWaiter.resumeDispatchingLocked(token);
+                if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                    mInputMonitor.resumeDispatchingLw(token);
+                } else {
+                    mKeyWaiter.resumeDispatchingLocked(token);
+                }
             }
         }
     }
@@ -6004,7 +5981,11 @@
         }
 
         synchronized (mWindowMap) {
-            mKeyWaiter.setEventDispatchingLocked(enabled);
+            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                mInputMonitor.setEventDispatchingLw(enabled);
+            } else {
+                mKeyWaiter.setEventDispatchingLocked(enabled);
+            }
         }
     }
 
@@ -7383,6 +7364,9 @@
         public void finishKey(IWindow window) {
             if (localLOGV) Slog.v(
                 TAG, "IWindow finishKey called for " + window);
+            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                throw new IllegalStateException("Should not be called anymore.");
+            }
             mKeyWaiter.finishedKey(this, window, false,
                     KeyWaiter.RETURN_NOTHING);
         }
@@ -7390,6 +7374,9 @@
         public MotionEvent getPendingPointerMove(IWindow window) {
             if (localLOGV) Slog.v(
                     TAG, "IWindow getPendingMotionEvent called for " + window);
+            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                throw new IllegalStateException("Should not be called anymore.");
+            }
             return mKeyWaiter.finishedKey(this, window, false,
                     KeyWaiter.RETURN_PENDING_POINTER);
         }
@@ -7397,6 +7384,9 @@
         public MotionEvent getPendingTrackballMove(IWindow window) {
             if (localLOGV) Slog.v(
                     TAG, "IWindow getPendingMotionEvent called for " + window);
+            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                throw new IllegalStateException("Should not be called anymore.");
+            }
             return mKeyWaiter.finishedKey(this, window, false,
                     KeyWaiter.RETURN_PENDING_TRACKBALL);
         }
@@ -7943,6 +7933,12 @@
         public IApplicationToken getAppToken() {
             return mAppToken != null ? mAppToken.appToken : null;
         }
+        
+        public long getInputDispatchingTimeoutNanos() {
+            return mAppToken != null
+                    ? mAppToken.inputDispatchingTimeoutNanos
+                    : DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        }
 
         public boolean hasAppShownWindows() {
             return mAppToken != null ? mAppToken.firstWindowDrawn : false;
@@ -8079,10 +8075,12 @@
             // Window is no longer on-screen, so can no longer receive
             // key events...  if we were waiting for it to finish
             // handling a key event, the wait is over!
-            mKeyWaiter.finishedKey(mSession, mClient, true,
-                    KeyWaiter.RETURN_NOTHING);
-            mKeyWaiter.releasePendingPointerLocked(mSession);
-            mKeyWaiter.releasePendingTrackballLocked(mSession);
+            if (! ENABLE_NATIVE_INPUT_DISPATCH) {
+                mKeyWaiter.finishedKey(mSession, mClient, true,
+                        KeyWaiter.RETURN_NOTHING);
+                mKeyWaiter.releasePendingPointerLocked(mSession);
+                mKeyWaiter.releasePendingTrackballLocked(mSession);
+            }
 
             if (mAppToken != null && this == mAppToken.startingWindow) {
                 mAppToken.startingDisplayed = false;
@@ -8098,6 +8096,10 @@
                     i--;
                     WindowState c = (WindowState)mChildWindows.get(i);
                     c.mAttachedHidden = true;
+                    
+                    if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                        mInputMonitor.windowIsBecomingInvisibleLw(c);
+                    }
                 }
 
                 if (mReportDestroySurface) {
@@ -8405,7 +8407,14 @@
                     Slog.w(TAG, "Error hiding surface in " + this, e);
                 }
                 mLastHidden = true;
-                mKeyWaiter.releasePendingPointerLocked(mSession);
+                
+                if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                    for (int i=0; i<N; i++) {
+                        mInputMonitor.windowIsBecomingInvisibleLw((WindowState)mChildWindows.get(i));
+                    }
+                } else {
+                    mKeyWaiter.releasePendingPointerLocked(mSession);
+                }
             }
             mExiting = false;
             if (mRemoveOnExit) {
@@ -9098,6 +9107,9 @@
         int groupId = -1;
         boolean appFullscreen;
         int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+        
+        // The input dispatching timeout for this application token in nanoseconds.
+        long inputDispatchingTimeoutNanos;
 
         // These are used for determining when all windows associated with
         // an activity have been drawn, so they can be made visible together
@@ -10158,6 +10170,11 @@
                 }
             }
         }
+        
+        // Window frames may have changed.  Tell the input dispatcher about it.
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputMonitor.updateInputWindowsLw();
+        }
 
         return mPolicy.finishLayoutLw();
     }
@@ -10958,7 +10975,11 @@
                                     Slog.w(TAG, "Exception hiding surface in " + w);
                                 }
                             }
-                            mKeyWaiter.releasePendingPointerLocked(w.mSession);
+                            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                                mInputMonitor.windowIsBecomingInvisibleLw(w);
+                            } else {
+                                mKeyWaiter.releasePendingPointerLocked(w.mSession);
+                            }
                         }
                         // If we are waiting for this window to handle an
                         // orientation change, well, it is hidden, so
@@ -11548,14 +11569,26 @@
                     assignLayersLocked();
                 }
             }
-
-            if (newFocus != null && mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
-                mKeyWaiter.handleNewWindowLocked(newFocus);
+            
+            if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
+                // If we defer assigning layers, then the caller is responsible for
+                // doing this part.
+                finishUpdateFocusedWindowAfterAssignLayersLocked();
             }
             return true;
         }
         return false;
     }
+    
+    private void finishUpdateFocusedWindowAfterAssignLayersLocked() {
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputMonitor.setInputFocusLw(mCurrentFocus);
+        } else {
+            if (mCurrentFocus != null) {
+                mKeyWaiter.handleNewWindowLocked(mCurrentFocus);
+            }
+        }
+    }
 
     private WindowState computeFocusedWindowLocked() {
         WindowState result = null;
@@ -11634,8 +11667,10 @@
             // still frozen, the events will continue to be blocked while the
             // successive orientation change is processed.  To prevent spurious
             // ANRs, we reset the event dispatch timeout in this case.
-            synchronized (mKeyWaiter) {
-                mKeyWaiter.mWasFrozen = true;
+            if (! ENABLE_NATIVE_INPUT_DISPATCH) {
+                synchronized (mKeyWaiter) {
+                    mKeyWaiter.mWasFrozen = true;
+                }
             }
             return;
         }
@@ -11658,6 +11693,11 @@
         if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException());
         
         mDisplayFrozen = true;
+        
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputMonitor.freezeInputDispatchingLw();
+        }
+        
         if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
             mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
             mNextAppTransitionPackage = null;
@@ -11691,9 +11731,13 @@
 
         // Reset the key delivery timeout on unfreeze, too.  We force a wakeup here
         // too because regular key delivery processing should resume immediately.
-        synchronized (mKeyWaiter) {
-            mKeyWaiter.mWasFrozen = true;
-            mKeyWaiter.notifyAll();
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputMonitor.thawInputDispatchingLw();
+        } else {
+            synchronized (mKeyWaiter) {
+                mKeyWaiter.mWasFrozen = true;
+                mKeyWaiter.notifyAll();
+            }
         }
 
         // While the display is frozen we don't re-compute the orientation
@@ -11949,20 +11993,25 @@
             }
             pw.print("  DisplayWidth="); pw.print(mDisplay.getWidth());
                     pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight());
-            pw.println("  KeyWaiter state:");
-            pw.print("    mLastWin="); pw.print(mKeyWaiter.mLastWin);
-                    pw.print(" mLastBinder="); pw.println(mKeyWaiter.mLastBinder);
-            pw.print("    mFinished="); pw.print(mKeyWaiter.mFinished);
-                    pw.print(" mGotFirstWindow="); pw.print(mKeyWaiter.mGotFirstWindow);
-                    pw.print(" mEventDispatching="); pw.print(mKeyWaiter.mEventDispatching);
-                    pw.print(" mTimeToSwitch="); pw.println(mKeyWaiter.mTimeToSwitch);
+
+            if (! ENABLE_NATIVE_INPUT_DISPATCH) {
+                pw.println("  KeyWaiter state:");
+                pw.print("    mLastWin="); pw.print(mKeyWaiter.mLastWin);
+                        pw.print(" mLastBinder="); pw.println(mKeyWaiter.mLastBinder);
+                pw.print("    mFinished="); pw.print(mKeyWaiter.mFinished);
+                        pw.print(" mGotFirstWindow="); pw.print(mKeyWaiter.mGotFirstWindow);
+                        pw.print(" mEventDispatching="); pw.print(mKeyWaiter.mEventDispatching);
+                        pw.print(" mTimeToSwitch="); pw.println(mKeyWaiter.mTimeToSwitch);
+            }
         }
     }
 
+    // Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection).
     public void monitor() {
         synchronized (mWindowMap) { }
         synchronized (mKeyguardTokenWatcher) { }
         synchronized (mKeyWaiter) { }
+        synchronized (mInputMonitor) { }
     }
 
     public void virtualKeyFeedback(KeyEvent event) {