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

Commit fd23e3ed authored by Jeff Brown's avatar Jeff Brown
Browse files

Fix bugs in fallback key handling.

If a fallback key is generated using a key plus a modifier,
then it's possible we might get a different fallback key
generated if the modifier has changed.  PhoneWindowManager
needs to remember which fallback is last generated for a
given key code so that it can apply the same fallback action.

When generating cancellation events, it's important to have
preserved the policyFlags of the original event.  Otherwise
we may not dispatch the cancellation properly.  For example,
some actions are not performed if the POLICY_FLAG_TRUSTED
is not specified.

Remember the metaState associated with a key event so we can
include it when canceled.

Tell the policy when a fallback is being cancelled so that it
can clean up its state.

After a SEARCH shortcut is invoked, clear the flag indicating
that a shortcut is pending.  This is to prevent SEARCH from
getting stuck down in the case where we might forget to send
the up.  (Shouldn't happen anymore after the prior fixes.)

Bug: 5616255
Change-Id: I68f0a9679c7af464eaf31c099f2aa50b53fecf1f
parent af67fc65
Loading
Loading
Loading
Loading
+46 −9
Original line number Original line Diff line number Diff line
@@ -386,19 +386,19 @@ public class KeyCharacterMap implements Parcelable {
     *
     *
     * @param keyCode The key code.
     * @param keyCode The key code.
     * @param metaState The meta key modifier state.
     * @param metaState The meta key modifier state.
     * @param outFallbackAction The fallback action object to populate.
     * @return The fallback action, or null if none.  Remember to recycle the fallback action.
     * @return True if a fallback action was found, false otherwise.
     *
     *
     * @hide
     * @hide
     */
     */
    public boolean getFallbackAction(int keyCode, int metaState,
    public FallbackAction getFallbackAction(int keyCode, int metaState) {
            FallbackAction outFallbackAction) {
        FallbackAction action = FallbackAction.obtain();
        if (outFallbackAction == null) {
            throw new IllegalArgumentException("fallbackAction must not be null");
        }

        metaState = KeyEvent.normalizeMetaState(metaState);
        metaState = KeyEvent.normalizeMetaState(metaState);
        return nativeGetFallbackAction(mPtr, keyCode, metaState, outFallbackAction);
        if (nativeGetFallbackAction(mPtr, keyCode, metaState, action)) {
            action.metaState = KeyEvent.normalizeMetaState(action.metaState);
            return action;
        }
        action.recycle();
        return null;
    }
    }


    /**
    /**
@@ -727,7 +727,44 @@ public class KeyCharacterMap implements Parcelable {
     * @hide
     * @hide
     */
     */
    public static final class FallbackAction {
    public static final class FallbackAction {
        private static final int MAX_RECYCLED = 10;
        private static final Object sRecycleLock = new Object();
        private static FallbackAction sRecycleBin;
        private static int sRecycledCount;

        private FallbackAction next;

        public int keyCode;
        public int keyCode;
        public int metaState;
        public int metaState;

        private FallbackAction() {
        }

        public static FallbackAction obtain() {
            final FallbackAction target;
            synchronized (sRecycleLock) {
                if (sRecycleBin == null) {
                    target = new FallbackAction();
                } else {
                    target = sRecycleBin;
                    sRecycleBin = target.next;
                    sRecycledCount--;
                    target.next = null;
                }
            }
            return target;
        }

        public void recycle() {
            synchronized (sRecycleLock) {
                if (sRecycledCount < MAX_RECYCLED) {
                    next = sRecycleBin;
                    sRecycleBin = this;
                    sRecycledCount += 1;
                } else {
                    next = null;
                }
            }
        }
    }
    }
}
}
+13 −16
Original line number Original line Diff line number Diff line
@@ -328,8 +328,6 @@ public final class ViewRootImpl implements ViewParent,


    private final int mDensity;
    private final int mDensity;


    final KeyCharacterMap.FallbackAction mFallbackAction = new KeyCharacterMap.FallbackAction();

    /**
    /**
     * Consistency verifier for debugging purposes.
     * Consistency verifier for debugging purposes.
     */
     */
@@ -4446,20 +4444,19 @@ public final class ViewRootImpl implements ViewParent,
            final int keyCode = event.getKeyCode();
            final int keyCode = event.getKeyCode();
            final int metaState = event.getMetaState();
            final int metaState = event.getMetaState();


            KeyEvent fallbackEvent = null;
            synchronized (mFallbackAction) {
            // Check for fallback actions specified by the key character map.
            // Check for fallback actions specified by the key character map.
                if (kcm.getFallbackAction(keyCode, metaState, mFallbackAction)) {
            KeyCharacterMap.FallbackAction fallbackAction =
                    int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
                    kcm.getFallbackAction(keyCode, metaState);
                    fallbackEvent = KeyEvent.obtain(
            if (fallbackAction != null) {
                final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
                KeyEvent fallbackEvent = KeyEvent.obtain(
                        event.getDownTime(), event.getEventTime(),
                        event.getDownTime(), event.getEventTime(),
                            event.getAction(), mFallbackAction.keyCode,
                        event.getAction(), fallbackAction.keyCode,
                            event.getRepeatCount(), mFallbackAction.metaState,
                        event.getRepeatCount(), fallbackAction.metaState,
                        event.getDeviceId(), event.getScanCode(),
                        event.getDeviceId(), event.getScanCode(),
                        flags, event.getSource(), null);
                        flags, event.getSource(), null);
                }
                fallbackAction.recycle();
            }

            if (fallbackEvent != null) {
                dispatchKey(fallbackEvent);
                dispatchKey(fallbackEvent);
            }
            }
        }
        }
+2 −0
Original line number Original line Diff line number Diff line
@@ -262,6 +262,8 @@ public:


    inline int32_t getFlags() const { return mFlags; }
    inline int32_t getFlags() const { return mFlags; }


    inline void setFlags(int32_t flags) { mFlags = flags; }

    inline int32_t getKeyCode() const { return mKeyCode; }
    inline int32_t getKeyCode() const { return mKeyCode; }


    inline int32_t getScanCode() const { return mScanCode; }
    inline int32_t getScanCode() const { return mScanCode; }
+62 −41
Original line number Original line Diff line number Diff line
@@ -463,8 +463,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
    Intent mHomeIntent;
    Intent mHomeIntent;
    Intent mCarDockIntent;
    Intent mCarDockIntent;
    Intent mDeskDockIntent;
    Intent mDeskDockIntent;
    int mShortcutKeyPressed = -1;
    boolean mSearchKeyShortcutPending;
    boolean mConsumeShortcutKeyUp;
    boolean mConsumeSearchKeyUp;


    // support for activating the lock screen while the screen is on
    // support for activating the lock screen while the screen is on
    boolean mAllowLockscreenWhenOn;
    boolean mAllowLockscreenWhenOn;
@@ -509,7 +509,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
    ShortcutManager mShortcutManager;
    ShortcutManager mShortcutManager;
    PowerManager.WakeLock mBroadcastWakeLock;
    PowerManager.WakeLock mBroadcastWakeLock;


    final KeyCharacterMap.FallbackAction mFallbackAction = new KeyCharacterMap.FallbackAction();
    // Fallback actions by key code.
    private final SparseArray<KeyCharacterMap.FallbackAction> mFallbackActions =
            new SparseArray<KeyCharacterMap.FallbackAction>();


    private static final int MSG_ENABLE_POINTER_LOCATION = 1;
    private static final int MSG_ENABLE_POINTER_LOCATION = 1;
    private static final int MSG_DISABLE_POINTER_LOCATION = 2;
    private static final int MSG_DISABLE_POINTER_LOCATION = 2;
@@ -1709,7 +1711,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {


        if (false) {
        if (false) {
            Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
            Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
                    + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed);
                    + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed
                    + " canceled=" + canceled);
        }
        }


        // If we think we might have a volume down & power key chord on the way
        // If we think we might have a volume down & power key chord on the way
@@ -1842,13 +1845,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        } else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
        } else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
            if (down) {
            if (down) {
                if (repeatCount == 0) {
                if (repeatCount == 0) {
                    mShortcutKeyPressed = keyCode;
                    mSearchKeyShortcutPending = true;
                    mConsumeShortcutKeyUp = false;
                    mConsumeSearchKeyUp = false;
                }
                }
            } else if (keyCode == mShortcutKeyPressed) {
            } else {
                mShortcutKeyPressed = -1;
                mSearchKeyShortcutPending = false;
                if (mConsumeShortcutKeyUp) {
                if (mConsumeSearchKeyUp) {
                    mConsumeShortcutKeyUp = false;
                    mConsumeSearchKeyUp = false;
                    return -1;
                    return -1;
                }
                }
            }
            }
@@ -1865,10 +1868,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        // even if no shortcut was invoked.  This prevents text from being
        // even if no shortcut was invoked.  This prevents text from being
        // inadvertently inserted when using a keyboard that has built-in macro
        // inadvertently inserted when using a keyboard that has built-in macro
        // shortcut keys (that emit Search+x) and some of them are not registered.
        // shortcut keys (that emit Search+x) and some of them are not registered.
        if (mShortcutKeyPressed != -1) {
        if (mSearchKeyShortcutPending) {
            final KeyCharacterMap kcm = event.getKeyCharacterMap();
            final KeyCharacterMap kcm = event.getKeyCharacterMap();
            if (kcm.isPrintingKey(keyCode)) {
            if (kcm.isPrintingKey(keyCode)) {
                mConsumeShortcutKeyUp = true;
                mConsumeSearchKeyUp = true;
                mSearchKeyShortcutPending = false;
                if (down && repeatCount == 0 && !keyguardOn) {
                if (down && repeatCount == 0 && !keyguardOn) {
                    Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, metaState);
                    Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, metaState);
                    if (shortcutIntent != null) {
                    if (shortcutIntent != null) {
@@ -1878,13 +1882,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                        } catch (ActivityNotFoundException ex) {
                        } catch (ActivityNotFoundException ex) {
                            Slog.w(TAG, "Dropping shortcut key combination because "
                            Slog.w(TAG, "Dropping shortcut key combination because "
                                    + "the activity to which it is registered was not found: "
                                    + "the activity to which it is registered was not found: "
                                    + KeyEvent.keyCodeToString(mShortcutKeyPressed)
                                    + "SEARCH+" + KeyEvent.keyCodeToString(keyCode), ex);
                                    + "+" + KeyEvent.keyCodeToString(keyCode), ex);
                        }
                        }
                    } else {
                    } else {
                        Slog.i(TAG, "Dropping unregistered shortcut key combination: "
                        Slog.i(TAG, "Dropping unregistered shortcut key combination: "
                                + KeyEvent.keyCodeToString(mShortcutKeyPressed)
                                + "SEARCH+" + KeyEvent.keyCodeToString(keyCode));
                                + "+" + KeyEvent.keyCodeToString(keyCode));
                    }
                    }
                }
                }
                return -1;
                return -1;
@@ -1964,51 +1966,70 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    + ", policyFlags=" + policyFlags);
                    + ", policyFlags=" + policyFlags);
        }
        }


        KeyEvent fallbackEvent = null;
        if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
        if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
            final KeyCharacterMap kcm = event.getKeyCharacterMap();
            final KeyCharacterMap kcm = event.getKeyCharacterMap();
            final int keyCode = event.getKeyCode();
            final int keyCode = event.getKeyCode();
            final int metaState = event.getMetaState();
            final int metaState = event.getMetaState();
            final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
                    && event.getRepeatCount() == 0;


            // Check for fallback actions specified by the key character map.
            // Check for fallback actions specified by the key character map.
            if (getFallbackAction(kcm, keyCode, metaState, mFallbackAction)) {
            final FallbackAction fallbackAction;
            if (initialDown) {
                fallbackAction = kcm.getFallbackAction(keyCode, metaState);
            } else {
                fallbackAction = mFallbackActions.get(keyCode);
            }

            if (fallbackAction != null) {
                if (DEBUG_FALLBACK) {
                if (DEBUG_FALLBACK) {
                    Slog.d(TAG, "Fallback: keyCode=" + mFallbackAction.keyCode
                    Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode
                            + " metaState=" + Integer.toHexString(mFallbackAction.metaState));
                            + " metaState=" + Integer.toHexString(fallbackAction.metaState));
                }
                }


                int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
                final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
                KeyEvent fallbackEvent = KeyEvent.obtain(
                fallbackEvent = KeyEvent.obtain(
                        event.getDownTime(), event.getEventTime(),
                        event.getDownTime(), event.getEventTime(),
                        event.getAction(), mFallbackAction.keyCode,
                        event.getAction(), fallbackAction.keyCode,
                        event.getRepeatCount(), mFallbackAction.metaState,
                        event.getRepeatCount(), fallbackAction.metaState,
                        event.getDeviceId(), event.getScanCode(),
                        event.getDeviceId(), event.getScanCode(),
                        flags, event.getSource(), null);
                        flags, event.getSource(), null);
                int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags, true);

                if ((actions & ACTION_PASS_TO_USER) != 0) {
                if (!interceptFallback(win, fallbackEvent, policyFlags)) {
                    long delayMillis = interceptKeyBeforeDispatching(
                    fallbackEvent.recycle();
                            win, fallbackEvent, policyFlags);
                    fallbackEvent = null;
                    if (delayMillis == 0) {
                        if (DEBUG_FALLBACK) {
                            Slog.d(TAG, "Performing fallback.");
                }
                }
                        return fallbackEvent;

                if (initialDown) {
                    mFallbackActions.put(keyCode, fallbackAction);
                } else if (event.getAction() == KeyEvent.ACTION_UP) {
                    mFallbackActions.remove(keyCode);
                    fallbackAction.recycle();
                }
                }
            }
            }
                fallbackEvent.recycle();
            }
        }
        }


        if (DEBUG_FALLBACK) {
        if (DEBUG_FALLBACK) {
            if (fallbackEvent == null) {
                Slog.d(TAG, "No fallback.");
                Slog.d(TAG, "No fallback.");
            } else {
                Slog.d(TAG, "Performing fallback: " + fallbackEvent);
            }
            }
        return null;
        }
        return fallbackEvent;
    }
    }


    private boolean getFallbackAction(KeyCharacterMap kcm, int keyCode, int metaState,
    private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
            FallbackAction outFallbackAction) {
        int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags, true);
        // Consult the key character map for specific fallback actions.
        if ((actions & ACTION_PASS_TO_USER) != 0) {
        // For example, map NUMPAD_1 to MOVE_HOME when NUMLOCK is not pressed.
            long delayMillis = interceptKeyBeforeDispatching(
        return kcm.getFallbackAction(keyCode, metaState, outFallbackAction);
                    win, fallbackEvent, policyFlags);
            if (delayMillis == 0) {
                return true;
            }
        }
        return false;
    }
    }


    /**
    /**
+32 −8
Original line number Original line Diff line number Diff line
@@ -3354,6 +3354,25 @@ bool InputDispatcher::afterKeyEventLockedInterruptible(const sp<Connection>& con
            // generated a fallback or if the window is not a foreground window,
            // generated a fallback or if the window is not a foreground window,
            // then cancel the associated fallback key, if any.
            // then cancel the associated fallback key, if any.
            if (fallbackKeyCode != -1) {
            if (fallbackKeyCode != -1) {
                // Dispatch the unhandled key to the policy with the cancel flag.
#if DEBUG_OUTBOUND_EVENT_DETAILS
                ALOGD("Unhandled key event: Asking policy to cancel fallback action.  "
                        "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
                        keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount,
                        keyEntry->policyFlags);
#endif
                KeyEvent event;
                initializeKeyEvent(&event, keyEntry);
                event.setFlags(event.getFlags() | AKEY_EVENT_FLAG_CANCELED);

                mLock.unlock();

                mPolicy->dispatchUnhandledKey(connection->inputWindowHandle,
                        &event, keyEntry->policyFlags, &event);

                mLock.lock();

                // Cancel the fallback key.
                if (fallbackKeyCode != AKEYCODE_UNKNOWN) {
                if (fallbackKeyCode != AKEYCODE_UNKNOWN) {
                    CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
                    CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
                            "application handled the original non-fallback key "
                            "application handled the original non-fallback key "
@@ -3374,8 +3393,9 @@ bool InputDispatcher::afterKeyEventLockedInterruptible(const sp<Connection>& con
#if DEBUG_OUTBOUND_EVENT_DETAILS
#if DEBUG_OUTBOUND_EVENT_DETAILS
                ALOGD("Unhandled key event: Skipping unhandled key event processing "
                ALOGD("Unhandled key event: Skipping unhandled key event processing "
                        "since this is not an initial down.  "
                        "since this is not an initial down.  "
                        "keyCode=%d, action=%d, repeatCount=%d",
                        "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
                        originalKeyCode, keyEntry->action, keyEntry->repeatCount);
                        originalKeyCode, keyEntry->action, keyEntry->repeatCount,
                        keyEntry->policyFlags);
#endif
#endif
                return false;
                return false;
            }
            }
@@ -3383,8 +3403,9 @@ bool InputDispatcher::afterKeyEventLockedInterruptible(const sp<Connection>& con
            // Dispatch the unhandled key to the policy.
            // Dispatch the unhandled key to the policy.
#if DEBUG_OUTBOUND_EVENT_DETAILS
#if DEBUG_OUTBOUND_EVENT_DETAILS
            ALOGD("Unhandled key event: Asking policy to perform fallback action.  "
            ALOGD("Unhandled key event: Asking policy to perform fallback action.  "
                    "keyCode=%d, action=%d, repeatCount=%d",
                    "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
                    keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount);
                    keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount,
                    keyEntry->policyFlags);
#endif
#endif
            KeyEvent event;
            KeyEvent event;
            initializeKeyEvent(&event, keyEntry);
            initializeKeyEvent(&event, keyEntry);
@@ -3903,8 +3924,10 @@ void InputDispatcher::InputState::addKeyMemento(const KeyEntry* entry, int32_t f
    memento.source = entry->source;
    memento.source = entry->source;
    memento.keyCode = entry->keyCode;
    memento.keyCode = entry->keyCode;
    memento.scanCode = entry->scanCode;
    memento.scanCode = entry->scanCode;
    memento.metaState = entry->metaState;
    memento.flags = flags;
    memento.flags = flags;
    memento.downTime = entry->downTime;
    memento.downTime = entry->downTime;
    memento.policyFlags = entry->policyFlags;
}
}


void InputDispatcher::InputState::addMotionMemento(const MotionEntry* entry,
void InputDispatcher::InputState::addMotionMemento(const MotionEntry* entry,
@@ -3919,6 +3942,7 @@ void InputDispatcher::InputState::addMotionMemento(const MotionEntry* entry,
    memento.downTime = entry->downTime;
    memento.downTime = entry->downTime;
    memento.setPointers(entry);
    memento.setPointers(entry);
    memento.hovering = hovering;
    memento.hovering = hovering;
    memento.policyFlags = entry->policyFlags;
}
}


void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
@@ -3935,9 +3959,9 @@ void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTim
        const KeyMemento& memento = mKeyMementos.itemAt(i);
        const KeyMemento& memento = mKeyMementos.itemAt(i);
        if (shouldCancelKey(memento, options)) {
        if (shouldCancelKey(memento, options)) {
            outEvents.push(new KeyEntry(currentTime,
            outEvents.push(new KeyEntry(currentTime,
                    memento.deviceId, memento.source, 0,
                    memento.deviceId, memento.source, memento.policyFlags,
                    AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED,
                    AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED,
                    memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
                    memento.keyCode, memento.scanCode, memento.metaState, 0, memento.downTime));
        }
        }
    }
    }


@@ -3945,7 +3969,7 @@ void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTim
        const MotionMemento& memento = mMotionMementos.itemAt(i);
        const MotionMemento& memento = mMotionMementos.itemAt(i);
        if (shouldCancelMotion(memento, options)) {
        if (shouldCancelMotion(memento, options)) {
            outEvents.push(new MotionEntry(currentTime,
            outEvents.push(new MotionEntry(currentTime,
                    memento.deviceId, memento.source, 0,
                    memento.deviceId, memento.source, memento.policyFlags,
                    memento.hovering
                    memento.hovering
                            ? AMOTION_EVENT_ACTION_HOVER_EXIT
                            ? AMOTION_EVENT_ACTION_HOVER_EXIT
                            : AMOTION_EVENT_ACTION_CANCEL,
                            : AMOTION_EVENT_ACTION_CANCEL,
Loading