Merge "Improve screenshot chord debouncing. Bug: 5011907"
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index bfd2959..17bdff2 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -104,23 +104,23 @@
      */
     public final static String EXTRA_HDMI_PLUGGED_STATE = "state";
 
-    // flags for interceptKeyTq
     /**
-     * Pass this event to the user / app.  To be returned from {@link #interceptKeyTq}.
+     * Pass this event to the user / app.  To be returned from
+     * {@link #interceptKeyBeforeQueueing}.
      */
     public final static int ACTION_PASS_TO_USER = 0x00000001;
 
     /**
      * This key event should extend the user activity timeout and turn the lights on.
-     * To be returned from {@link #interceptKeyTq}. Do not return this and
-     * {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}.
+     * To be returned from {@link #interceptKeyBeforeQueueing}.
+     * Do not return this and {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}.
      */
     public final static int ACTION_POKE_USER_ACTIVITY = 0x00000002;
 
     /**
      * This key event should put the device to sleep (and engage keyguard if necessary)
-     * To be returned from {@link #interceptKeyTq}.  Do not return this and
-     * {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}.
+     * To be returned from {@link #interceptKeyBeforeQueueing}.
+     * Do not return this and {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}.
      */
     public final static int ACTION_GO_TO_SLEEP = 0x00000004;
 
@@ -677,10 +677,12 @@
      *            event will normally go.
      * @param event The key event.
      * @param policyFlags The policy flags associated with the key.
-     * @return Returns true if the policy consumed the event and it should
-     * not be further dispatched.
+     * @return 0 if the key should be dispatched immediately, -1 if the key should
+     * not be dispatched ever, or a positive value indicating the number of
+     * milliseconds by which the key dispatch should be delayed before trying
+     * again.
      */
-    public boolean interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags);
+    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags);
 
     /**
      * Called from the input dispatcher thread when an application did not handle
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 487063d..3eb04cb 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -267,7 +267,8 @@
     WindowState mKeyguard = null;
     KeyguardViewMediator mKeyguardMediator;
     GlobalActions mGlobalActions;
-    volatile boolean mPowerKeyHandled;
+    volatile boolean mPowerKeyHandled; // accessed from input reader and handler thread
+    boolean mPendingPowerKeyUpCanceled;
     RecentApplicationsDialog mRecentAppsDialog;
     Handler mHandler;
 
@@ -403,8 +404,14 @@
     private int mLongPressOnHomeBehavior = -1;
 
     // Screenshot trigger states
-    private boolean mVolumeDownTriggered;
-    private boolean mPowerDownTriggered;
+    // Time to volume and power must be pressed within this interval of each other.
+    private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150;
+    private boolean mVolumeDownKeyTriggered;
+    private long mVolumeDownKeyTime;
+    private boolean mVolumeDownKeyConsumedByScreenshotChord;
+    private boolean mVolumeUpKeyTriggered;
+    private boolean mPowerKeyTriggered;
+    private long mPowerKeyTime;
 
     ShortcutManager mShortcutManager;
     PowerManager.WakeLock mBroadcastWakeLock;
@@ -552,37 +559,64 @@
         if (!mPowerKeyHandled) {
             mHandler.removeCallbacks(mPowerLongPress);
             return !canceled;
-        } else {
-            mPowerKeyHandled = true;
-            return false;
         }
+        return false;
+    }
+
+    private void cancelPendingPowerKeyAction() {
+        if (!mPowerKeyHandled) {
+            mHandler.removeCallbacks(mPowerLongPress);
+        }
+        mPendingPowerKeyUpCanceled = true;
+    }
+
+    private void interceptScreenshotChord() {
+        if (mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {
+            final long now = SystemClock.uptimeMillis();
+            if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
+                    && now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
+                mVolumeDownKeyConsumedByScreenshotChord = true;
+                cancelPendingPowerKeyAction();
+
+                mHandler.postDelayed(mScreenshotChordLongPress,
+                        ViewConfiguration.getGlobalActionKeyTimeout());
+            }
+        }
+    }
+
+    private void cancelPendingScreenshotChordAction() {
+        mHandler.removeCallbacks(mScreenshotChordLongPress);
     }
 
     private final Runnable mPowerLongPress = new Runnable() {
         public void run() {
-            if (!mPowerKeyHandled) {
-                // The context isn't read
-                if (mLongPressOnPowerBehavior < 0) {
-                    mLongPressOnPowerBehavior = mContext.getResources().getInteger(
-                            com.android.internal.R.integer.config_longPressOnPowerBehavior);
-                }
-                switch (mLongPressOnPowerBehavior) {
-                case LONG_PRESS_POWER_NOTHING:
-                    break;
-                case LONG_PRESS_POWER_GLOBAL_ACTIONS:
-                    mPowerKeyHandled = true;
-                    performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
-                    sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
-                    showGlobalActionsDialog();
-                    break;
-                case LONG_PRESS_POWER_SHUT_OFF:
-                    mPowerKeyHandled = true;
-                    performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
-                    sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
-                    ShutdownThread.shutdown(mContext, true);
-                    break;
-                }
+            // The context isn't read
+            if (mLongPressOnPowerBehavior < 0) {
+                mLongPressOnPowerBehavior = mContext.getResources().getInteger(
+                        com.android.internal.R.integer.config_longPressOnPowerBehavior);
             }
+            switch (mLongPressOnPowerBehavior) {
+            case LONG_PRESS_POWER_NOTHING:
+                break;
+            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
+                mPowerKeyHandled = true;
+                performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
+                showGlobalActionsDialog();
+                break;
+            case LONG_PRESS_POWER_SHUT_OFF:
+                mPowerKeyHandled = true;
+                performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
+                ShutdownThread.shutdown(mContext, true);
+                break;
+            }
+        }
+    };
+
+    private final Runnable mScreenshotChordLongPress = new Runnable() {
+        public void run() {
+            takeScreenshot();
         }
     };
 
@@ -1381,11 +1415,12 @@
 
     /** {@inheritDoc} */
     @Override
-    public boolean interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
+    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
         final boolean keyguardOn = keyguardOn();
         final int keyCode = event.getKeyCode();
         final int repeatCount = event.getRepeatCount();
         final int metaState = event.getMetaState();
+        final int flags = event.getFlags();
         final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
         final boolean canceled = event.isCanceled();
 
@@ -1394,6 +1429,26 @@
                     + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed);
         }
 
+        // If we think we might have a volume down & power key chord on the way
+        // but we're not sure, then tell the dispatcher to wait a little while and
+        // try again later before dispatching.
+        if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
+            if (mVolumeDownKeyTriggered && !mPowerKeyTriggered) {
+                final long now = SystemClock.uptimeMillis();
+                final long timeoutTime = mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS;
+                if (now < timeoutTime) {
+                    return timeoutTime - now;
+                }
+            }
+            if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                    && mVolumeDownKeyConsumedByScreenshotChord) {
+                if (!down) {
+                    mVolumeDownKeyConsumedByScreenshotChord = false;
+                }
+                return -1;
+            }
+        }
+
         // First we always handle the home key here, so applications
         // can never break it, although if keyguard is on, we do let
         // it handle it, because that gives us the correct 5 second
@@ -1425,7 +1480,7 @@
                 } else {
                     Log.i(TAG, "Ignoring HOME; event canceled.");
                 }
-                return true;
+                return -1;
             }
 
             // If a system window has focus, then it doesn't make sense
@@ -1436,13 +1491,13 @@
                 if (type == WindowManager.LayoutParams.TYPE_KEYGUARD
                         || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
                     // the "app" is keyguard, so give it the key
-                    return false;
+                    return 0;
                 }
                 final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
                 for (int i=0; i<typeCount; i++) {
                     if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
                         // don't do anything, but also don't pass it to the app
-                        return true;
+                        return -1;
                     }
                 }
             }
@@ -1456,7 +1511,7 @@
                     }
                 }
             }
-            return true;
+            return -1;
         } else if (keyCode == KeyEvent.KEYCODE_MENU) {
             // Hijack modified menu keys for debugging features
             final int chordBug = KeyEvent.META_SHIFT_ON;
@@ -1465,7 +1520,7 @@
                 if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) {
                     Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
                     mContext.sendOrderedBroadcast(intent, null);
-                    return true;
+                    return -1;
                 } else if (SHOW_PROCESSES_ON_ALT_MENU &&
                         (metaState & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON) {
                     Intent service = new Intent();
@@ -1480,7 +1535,7 @@
                     }
                     Settings.System.putInt(
                             res, Settings.System.SHOW_PROCESSES, shown ? 0 : 1);
-                    return true;
+                    return -1;
                 }
             }
         } else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
@@ -1493,15 +1548,15 @@
                 mShortcutKeyPressed = -1;
                 if (mConsumeShortcutKeyUp) {
                     mConsumeShortcutKeyUp = false;
-                    return true;
+                    return -1;
                 }
             }
-            return false;
+            return 0;
         } else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) {
             if (down && repeatCount == 0) {
                 showOrHideRecentAppsDialog(0, true /*dismissIfShown*/);
             }
-            return true;
+            return -1;
         }
 
         // Shortcuts are invoked through Search+key, so intercept those here
@@ -1531,11 +1586,11 @@
                                 + "+" + KeyEvent.keyCodeToString(keyCode));
                     }
                 }
-                return true;
+                return -1;
             }
         }
 
-        return false;
+        return 0;
     }
 
     /** {@inheritDoc} */
@@ -1606,7 +1661,9 @@
                         flags, event.getSource(), null);
                 int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags, true);
                 if ((actions & ACTION_PASS_TO_USER) != 0) {
-                    if (!interceptKeyBeforeDispatching(win, fallbackEvent, policyFlags)) {
+                    long delayMillis = interceptKeyBeforeDispatching(
+                            win, fallbackEvent, policyFlags);
+                    if (delayMillis == 0) {
                         if (DEBUG_FALLBACK) {
                             Slog.d(TAG, "Performing fallback.");
                         }
@@ -2472,76 +2529,65 @@
 
     final Object mScreenshotLock = new Object();
     ServiceConnection mScreenshotConnection = null;
-    Runnable mScreenshotTimeout = null;
 
-    void finishScreenshotLSS(ServiceConnection conn) {
-        if (mScreenshotConnection == conn) {
-            mContext.unbindService(conn);
-            mScreenshotConnection = null;
-            if (mScreenshotTimeout != null) {
-                mHandler.removeCallbacks(mScreenshotTimeout);
-                mScreenshotTimeout = null;
-            }
-        }
-    }
-
-    private void takeScreenshot() {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                synchronized (mScreenshotLock) {
-                    if (mScreenshotConnection != null) {
-                        return;
-                    }
-                    ComponentName cn = new ComponentName("com.android.systemui",
-                            "com.android.systemui.screenshot.TakeScreenshotService");
-                    Intent intent = new Intent();
-                    intent.setComponent(cn);
-                    ServiceConnection conn = new ServiceConnection() {
-                        @Override
-                        public void onServiceConnected(ComponentName name, IBinder service) {
-                            synchronized (mScreenshotLock) {
-                                if (mScreenshotConnection != this) {
-                                    return;
-                                }
-                                Messenger messenger = new Messenger(service);
-                                Message msg = Message.obtain(null, 1);
-                                final ServiceConnection myConn = this;
-                                Handler h = new Handler(mHandler.getLooper()) {
-                                    @Override
-                                    public void handleMessage(Message msg) {
-                                        synchronized (mScreenshotLock) {
-                                            finishScreenshotLSS(myConn);
-                                        }
-                                    }
-                                };
-                                msg.replyTo = new Messenger(h);
-                                try {
-                                    messenger.send(msg);
-                                } catch (RemoteException e) {
-                                }
-                            }
-                        }
-                        @Override
-                        public void onServiceDisconnected(ComponentName name) {}
-                    };
-                    if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
-                        mScreenshotConnection = conn;
-                        mScreenshotTimeout = new Runnable() {
-                            @Override public void run() {
-                                synchronized (mScreenshotLock) {
-                                    if (mScreenshotConnection != null) {
-                                        finishScreenshotLSS(mScreenshotConnection);
-                                    }
-                                }
-                            }
-    
-                        };
-                        mHandler.postDelayed(mScreenshotTimeout, 10000);
-                    }
+    final Runnable mScreenshotTimeout = new Runnable() {
+        @Override public void run() {
+            synchronized (mScreenshotLock) {
+                if (mScreenshotConnection != null) {
+                    mContext.unbindService(mScreenshotConnection);
+                    mScreenshotConnection = null;
                 }
             }
-        });
+        }
+    };
+
+    // Assume this is called from the Handler thread.
+    private void takeScreenshot() {
+        synchronized (mScreenshotLock) {
+            if (mScreenshotConnection != null) {
+                return;
+            }
+            ComponentName cn = new ComponentName("com.android.systemui",
+                    "com.android.systemui.screenshot.TakeScreenshotService");
+            Intent intent = new Intent();
+            intent.setComponent(cn);
+            ServiceConnection conn = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    synchronized (mScreenshotLock) {
+                        if (mScreenshotConnection != this) {
+                            return;
+                        }
+                        Messenger messenger = new Messenger(service);
+                        Message msg = Message.obtain(null, 1);
+                        final ServiceConnection myConn = this;
+                        Handler h = new Handler(mHandler.getLooper()) {
+                            @Override
+                            public void handleMessage(Message msg) {
+                                synchronized (mScreenshotLock) {
+                                    if (mScreenshotConnection == myConn) {
+                                        mContext.unbindService(mScreenshotConnection);
+                                        mScreenshotConnection = null;
+                                        mHandler.removeCallbacks(mScreenshotTimeout);
+                                    }
+                                }
+                            }
+                        };
+                        msg.replyTo = new Messenger(h);
+                        try {
+                            messenger.send(msg);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+                @Override
+                public void onServiceDisconnected(ComponentName name) {}
+            };
+            if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
+                mScreenshotConnection = conn;
+                mHandler.postDelayed(mScreenshotTimeout, 10000);
+            }
+        }
     }
 
     /** {@inheritDoc} */
@@ -2609,28 +2655,35 @@
         // Handle special keys.
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_DOWN:
-                if (down) {
-                    if (isScreenOn) {
-                        // If the power key down was already triggered, take the screenshot
-                        if (mPowerDownTriggered) {
-                            // Dismiss the power-key longpress
-                            mHandler.removeCallbacks(mPowerLongPress);
-                            mPowerKeyHandled = true;
-
-                            // Take the screenshot
-                            takeScreenshot();
-
-                            // Prevent the event from being passed through to the current activity
-                            result &= ~ACTION_PASS_TO_USER;
-                            break;
-                        }
-                        mVolumeDownTriggered = true;
-                    }
-                } else {
-                    mVolumeDownTriggered = false;
-                }
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
+                if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+                    if (down) {
+                        if (isScreenOn && !mVolumeDownKeyTriggered
+                                && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+                            mVolumeDownKeyTriggered = true;
+                            mVolumeDownKeyTime = event.getDownTime();
+                            mVolumeDownKeyConsumedByScreenshotChord = false;
+                            cancelPendingPowerKeyAction();
+                            interceptScreenshotChord();
+                        }
+                    } else {
+                        mVolumeDownKeyTriggered = false;
+                        cancelPendingScreenshotChordAction();
+                    }
+                } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+                    if (down) {
+                        if (isScreenOn && !mVolumeUpKeyTriggered
+                                && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+                            mVolumeUpKeyTriggered = true;
+                            cancelPendingPowerKeyAction();
+                            cancelPendingScreenshotChordAction();
+                        }
+                    } else {
+                        mVolumeUpKeyTriggered = false;
+                        cancelPendingScreenshotChordAction();
+                    }
+                }
                 if (down) {
                     ITelephony telephonyService = getTelephonyService();
                     if (telephonyService != null) {
@@ -2709,17 +2762,11 @@
             case KeyEvent.KEYCODE_POWER: {
                 result &= ~ACTION_PASS_TO_USER;
                 if (down) {
-                    if (isScreenOn) {
-                        // If the volume down key has been triggered, then just take the screenshot
-                        if (mVolumeDownTriggered) {
-                            // Take the screenshot
-                            takeScreenshot();
-                            mPowerKeyHandled = true;
-
-                            // Prevent the event from being passed through to the current activity
-                            break;
-                        }
-                        mPowerDownTriggered = true;
+                    if (isScreenOn && !mPowerKeyTriggered
+                            && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+                        mPowerKeyTriggered = true;
+                        mPowerKeyTime = event.getDownTime();
+                        interceptScreenshotChord();
                     }
 
                     ITelephony telephonyService = getTelephonyService();
@@ -2741,12 +2788,15 @@
                             Log.w(TAG, "ITelephony threw RemoteException", ex);
                         }
                     }
-                    interceptPowerKeyDown(!isScreenOn || hungUp);
+                    interceptPowerKeyDown(!isScreenOn || hungUp
+                            || mVolumeDownKeyTriggered || mVolumeUpKeyTriggered);
                 } else {
-                    mPowerDownTriggered = false;
-                    if (interceptPowerKeyUp(canceled)) {
+                    mPowerKeyTriggered = false;
+                    cancelPendingScreenshotChordAction();
+                    if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) {
                         result = (result & ~ACTION_POKE_USER_ACTIVITY) | ACTION_GO_TO_SLEEP;
                     }
+                    mPendingPowerKeyUpCanceled = false;
                 }
                 break;
             }
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index 1eb5f0e..04b4855 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -804,6 +804,18 @@
         logOutboundKeyDetailsLocked("dispatchKey - ", entry);
     }
 
+    // Handle case where the policy asked us to try again later last time.
+    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
+        if (currentTime < entry->interceptKeyWakeupTime) {
+            if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
+                *nextWakeupTime = entry->interceptKeyWakeupTime;
+            }
+            return false; // wait until next wakeup
+        }
+        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+        entry->interceptKeyWakeupTime = 0;
+    }
+
     // Give the policy a chance to intercept the key.
     if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
         if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
@@ -3827,14 +3839,19 @@
 
     mLock.unlock();
 
-    bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
+    nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
             &event, entry->policyFlags);
 
     mLock.lock();
 
-    entry->interceptKeyResult = consumed
-            ? KeyEntry::INTERCEPT_KEY_RESULT_SKIP
-            : KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+    if (delay < 0) {
+        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
+    } else if (!delay) {
+        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+    } else {
+        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
+        entry->interceptKeyWakeupTime = now() + delay;
+    }
     entry->release();
 }
 
@@ -4156,7 +4173,8 @@
         deviceId(deviceId), source(source), action(action), flags(flags),
         keyCode(keyCode), scanCode(scanCode), metaState(metaState),
         repeatCount(repeatCount), downTime(downTime),
-        syntheticRepeat(false), interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+        syntheticRepeat(false), interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN),
+        interceptKeyWakeupTime(0) {
 }
 
 InputDispatcher::KeyEntry::~KeyEntry() {
@@ -4168,6 +4186,7 @@
     dispatchInProgress = false;
     syntheticRepeat = false;
     interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+    interceptKeyWakeupTime = 0;
 }
 
 
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index e78f7bd..8ae5a56 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -242,7 +242,7 @@
     virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) = 0;
 
     /* Allows the policy a chance to intercept a key before dispatching. */
-    virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
+    virtual nsecs_t interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
             const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
 
     /* Allows the policy a chance to perform default processing for an unhandled key.
@@ -481,8 +481,10 @@
             INTERCEPT_KEY_RESULT_UNKNOWN,
             INTERCEPT_KEY_RESULT_SKIP,
             INTERCEPT_KEY_RESULT_CONTINUE,
+            INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,
         };
         InterceptKeyResult interceptKeyResult; // set based on the interception result
+        nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
 
         KeyEntry(nsecs_t eventTime,
                 int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
diff --git a/services/input/tests/InputDispatcher_test.cpp b/services/input/tests/InputDispatcher_test.cpp
index 8dfb44b..961566f 100644
--- a/services/input/tests/InputDispatcher_test.cpp
+++ b/services/input/tests/InputDispatcher_test.cpp
@@ -75,9 +75,9 @@
     virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
     }
 
-    virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
+    virtual nsecs_t interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
             const KeyEvent* keyEvent, uint32_t policyFlags) {
-        return false;
+        return 0;
     }
 
     virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java
index 60333a3..df7e0e1 100644
--- a/services/java/com/android/server/wm/InputManager.java
+++ b/services/java/com/android/server/wm/InputManager.java
@@ -575,7 +575,7 @@
         }
 
         @SuppressWarnings("unused")
-        public boolean interceptKeyBeforeDispatching(InputWindowHandle focus,
+        public long interceptKeyBeforeDispatching(InputWindowHandle focus,
                 KeyEvent event, int policyFlags) {
             return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching(
                     focus, event, policyFlags);
diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java
index 9a559e0..fb74d27 100644
--- a/services/java/com/android/server/wm/InputMonitor.java
+++ b/services/java/com/android/server/wm/InputMonitor.java
@@ -288,7 +288,7 @@
 
     /* Provides an opportunity for the window manager policy to process a key before
      * ordinary dispatch. */
-    public boolean interceptKeyBeforeDispatching(
+    public long interceptKeyBeforeDispatching(
             InputWindowHandle focus, KeyEvent event, int policyFlags) {
         WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
         return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index f976301..7e9fba8 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -149,6 +149,12 @@
     }
 }
 
+enum {
+    WM_ACTION_PASS_TO_USER = 1,
+    WM_ACTION_POKE_USER_ACTIVITY = 2,
+    WM_ACTION_GO_TO_SLEEP = 4,
+};
+
 
 // --- NativeInputManager ---
 
@@ -199,7 +205,8 @@
     virtual bool isKeyRepeatEnabled();
     virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags);
     virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags);
-    virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
+    virtual nsecs_t interceptKeyBeforeDispatching(
+            const sp<InputWindowHandle>& inputWindowHandle,
             const KeyEvent* keyEvent, uint32_t policyFlags);
     virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
             const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent);
@@ -819,12 +826,6 @@
 
 void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
         uint32_t& policyFlags) {
-    enum {
-        WM_ACTION_PASS_TO_USER = 1,
-        WM_ACTION_POKE_USER_ACTIVITY = 2,
-        WM_ACTION_GO_TO_SLEEP = 4,
-    };
-
     if (wmActions & WM_ACTION_GO_TO_SLEEP) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
         LOGD("handleInterceptActions: Going to sleep.");
@@ -848,14 +849,14 @@
     }
 }
 
-bool NativeInputManager::interceptKeyBeforeDispatching(
+nsecs_t NativeInputManager::interceptKeyBeforeDispatching(
         const sp<InputWindowHandle>& inputWindowHandle,
         const KeyEvent* keyEvent, uint32_t policyFlags) {
     // Policy:
     // - Ignore untrusted events and pass them along.
     // - Filter normal events and trusted injected events through the window manager policy to
     //   handle the HOME key and the like.
-    bool result = false;
+    nsecs_t result = 0;
     if (policyFlags & POLICY_FLAG_TRUSTED) {
         JNIEnv* env = jniEnv();
 
@@ -863,13 +864,19 @@
         jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle);
         jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
         if (keyEventObj) {
-            jboolean consumed = env->CallBooleanMethod(mCallbacksObj,
+            jlong delayMillis = env->CallLongMethod(mCallbacksObj,
                     gCallbacksClassInfo.interceptKeyBeforeDispatching,
                     inputWindowHandleObj, keyEventObj, policyFlags);
             bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
             android_view_KeyEvent_recycle(env, keyEventObj);
             env->DeleteLocalRef(keyEventObj);
-            result = consumed && !error;
+            if (!error) {
+                if (delayMillis < 0) {
+                    result = -1;
+                } else if (delayMillis > 0) {
+                    result = milliseconds_to_nanoseconds(delayMillis);
+                }
+            }
         } else {
             LOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
         }
@@ -1433,7 +1440,7 @@
 
     GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, clazz,
             "interceptKeyBeforeDispatching",
-            "(Lcom/android/server/wm/InputWindowHandle;Landroid/view/KeyEvent;I)Z");
+            "(Lcom/android/server/wm/InputWindowHandle;Landroid/view/KeyEvent;I)J");
 
     GET_METHOD_ID(gCallbacksClassInfo.dispatchUnhandledKey, clazz,
             "dispatchUnhandledKey",