Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d5bb82d1 authored by Jeff Brown's avatar Jeff Brown Committed by Winson Chung
Browse files

DO NOT MERGE. Improve screenshot chord debouncing.

Bug: 5011907

Introduce a 150ms delay in handling volume down keys
while waiting to see if a power key will follow.

Don't trigger the screenshot chord if both volume up and
volume down are pressed together.

Don't trigger the long-press power menu if volume keys are
also pressed.

Require the user to press both keys in the chord within
the debounce time and continue long-pressing them in order
to trigger the screenshot action.

Change-Id: I248968d37b73c09d6d08e7f62667c443eba32da0
parent 99b70f3f
Loading
Loading
Loading
Loading
+11 −9
Original line number Diff line number Diff line
@@ -104,23 +104,23 @@ public interface WindowManagerPolicy {
     */
    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 @@ public interface WindowManagerPolicy {
     *            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
+180 −130
Original line number Diff line number Diff line
@@ -267,7 +267,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
    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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
    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,15 +559,37 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        if (!mPowerKeyHandled) {
            mHandler.removeCallbacks(mPowerLongPress);
            return !canceled;
        } else {
            mPowerKeyHandled = true;
        }
        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(
@@ -583,6 +612,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                break;
            }
        }
    };

    private final Runnable mScreenshotChordLongPress = new Runnable() {
        public void run() {
            takeScreenshot();
        }
    };

@@ -1381,11 +1415,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {

    /** {@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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    + 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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                } 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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    }
                }
            }
            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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    }
                    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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                                + "+" + KeyEvent.keyCodeToString(keyCode));
                    }
                }
                return true;
                return -1;
            }
        }

        return false;
        return 0;
    }

    /** {@inheritDoc} */
@@ -1606,7 +1661,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                        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,23 +2529,20 @@ public class PhoneWindowManager implements WindowManagerPolicy {

    final Object mScreenshotLock = new Object();
    ServiceConnection mScreenshotConnection = null;
    Runnable mScreenshotTimeout = null;

    void finishScreenshotLSS(ServiceConnection conn) {
        if (mScreenshotConnection == conn) {
            mContext.unbindService(conn);
    final Runnable mScreenshotTimeout = new Runnable() {
        @Override public void run() {
            synchronized (mScreenshotLock) {
                if (mScreenshotConnection != null) {
                    mContext.unbindService(mScreenshotConnection);
                    mScreenshotConnection = null;
            if (mScreenshotTimeout != null) {
                mHandler.removeCallbacks(mScreenshotTimeout);
                mScreenshotTimeout = null;
                }
            }
        }
    };

    // Assume this is called from the Handler thread.
    private void takeScreenshot() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
        synchronized (mScreenshotLock) {
            if (mScreenshotConnection != null) {
                return;
@@ -2511,7 +2565,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                            @Override
                            public void handleMessage(Message msg) {
                                synchronized (mScreenshotLock) {
                                            finishScreenshotLSS(myConn);
                                    if (mScreenshotConnection == myConn) {
                                        mContext.unbindService(mScreenshotConnection);
                                        mScreenshotConnection = null;
                                        mHandler.removeCallbacks(mScreenshotTimeout);
                                    }
                                }
                            }
                        };
@@ -2527,22 +2585,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            };
            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);
            }
        }
    }
        });
    }

    /** {@inheritDoc} */
    @Override
@@ -2609,28 +2655,35 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        // Handle special keys.
        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_DOWN:
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_MUTE: {
                if (keyCode == 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;
                        if (isScreenOn && !mVolumeDownKeyTriggered
                                && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                            mVolumeDownKeyTriggered = true;
                            mVolumeDownKeyTime = event.getDownTime();
                            mVolumeDownKeyConsumedByScreenshotChord = false;
                            cancelPendingPowerKeyAction();
                            interceptScreenshotChord();
                        }
                    } else {
                        mVolumeDownKeyTriggered = false;
                        cancelPendingScreenshotChordAction();
                    }
                        mVolumeDownTriggered = true;
                } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
                    if (down) {
                        if (isScreenOn && !mVolumeUpKeyTriggered
                                && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                            mVolumeUpKeyTriggered = true;
                            cancelPendingPowerKeyAction();
                            cancelPendingScreenshotChordAction();
                        }
                    } else {
                    mVolumeDownTriggered = false;
                        mVolumeUpKeyTriggered = false;
                        cancelPendingScreenshotChordAction();
                    }
                }
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_MUTE: {
                if (down) {
                    ITelephony telephonyService = getTelephonyService();
                    if (telephonyService != null) {
@@ -2709,17 +2762,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                            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;
            }
+24 −5
Original line number Diff line number Diff line
@@ -804,6 +804,18 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        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 @@ void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(

    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 @@ InputDispatcher::KeyEntry::KeyEntry(nsecs_t eventTime,
        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 @@ void InputDispatcher::KeyEntry::recycle() {
    dispatchInProgress = false;
    syntheticRepeat = false;
    interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
    interceptKeyWakeupTime = 0;
}


+3 −1
Original line number Diff line number Diff line
@@ -242,7 +242,7 @@ public:
    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 @@ private:
            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,
+2 −2
Original line number Diff line number Diff line
@@ -75,9 +75,9 @@ private:
    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,
Loading