From 13f696dcb9c85bbc33109012304ceb6b631182dc Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Wed, 21 Jun 2023 15:50:44 +0000 Subject: [PATCH] Refactor PWM to fully capture shortcut gesture Currently some keys and shortcuts are partially captured. So, a part of the key event gesture is sent to applications and a part of it is not. Make sure keys and shortcuts are tracked from first action down, and if they are intercepted the whole key event gesture, down, repeated down, up are all intercepted. Example: In the current logic some keys were getting captured only on first ACTION_DOWN, this means applications were still receiving ACTION_UP which they should not since they didn't receive the down event. Further refactoring: Fix interceptBeforeQueuing and separate the logic to a separate file to handle shortcuts and add more strict testing. Test: manual Bug: 283241997 Change-Id: Ia48086458c86b35c00e01b4a960f6a9bfb80bae9 --- .../server/policy/PhoneWindowManager.java | 237 ++++++++++-------- 1 file changed, 135 insertions(+), 102 deletions(-) diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 9402fca9574fa..279a480842522 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -91,6 +91,7 @@ import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManagerInternal; @@ -632,6 +633,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { SettingsObserver mSettingsObserver; ModifierShortcutManager mModifierShortcutManager; + /** Currently fully consumed key codes per device */ + private final SparseArray> mConsumedKeysForDevice = new SparseArray<>(); PowerManager.WakeLock mBroadcastWakeLock; PowerManager.WakeLock mPowerKeyWakeLock; boolean mHavePendingMediaKeyRepeatWithWakeLock; @@ -1817,7 +1820,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDisplayId = displayId; } - int handleHomeButton(IBinder focusedToken, KeyEvent event) { + boolean handleHomeButton(IBinder focusedToken, KeyEvent event) { final boolean keyguardOn = keyguardOn(); final int repeatCount = event.getRepeatCount(); final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; @@ -1838,12 +1841,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHomePressed = false; if (mHomeConsumed) { mHomeConsumed = false; - return -1; + return true; } if (canceled) { Log.i(TAG, "Ignoring HOME; event canceled."); - return -1; + return true; } // Delay handling home if a double-tap is possible. @@ -1855,13 +1858,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHomeDoubleTapPending = true; mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable, ViewConfiguration.getDoubleTapTimeout()); - return -1; + return true; } } // Post to main thread to avoid blocking input pipeline. mHandler.post(() -> handleShortPressOnHome(mDisplayId)); - return -1; + return true; } final KeyInterceptionInfo info = @@ -1873,12 +1876,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { || (info.layoutParamsType == TYPE_NOTIFICATION_SHADE && isKeyguardShowing())) { // the "app" is keyguard, so give it the key - return 0; + return false; } for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) { if (info.layoutParamsType == t) { // don't do anything, but also don't pass it to the app - return -1; + return true; } } } @@ -1903,7 +1906,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { event.getEventTime())); } } - return -1; + return true; } private void handleDoubleTapOnHome() { @@ -2949,24 +2952,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public long interceptKeyBeforeDispatching(IBinder focusedToken, 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(); - final int displayId = event.getDisplayId(); - final long key_consumed = -1; - final long key_not_consumed = 0; + final long keyConsumed = -1; + final long keyNotConsumed = 0; + final int deviceId = event.getDeviceId(); if (DEBUG_INPUT) { - Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount=" - + repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled); + Log.d(TAG, + "interceptKeyTi keyCode=" + keyCode + " action=" + event.getAction() + + " repeatCount=" + event.getRepeatCount() + " keyguardOn=" + + keyguardOn() + " canceled=" + event.isCanceled()); } if (mKeyCombinationManager.isKeyConsumed(event)) { - return key_consumed; + return keyConsumed; } if ((flags & KeyEvent.FLAG_FALLBACK) == 0) { @@ -2977,8 +2977,54 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - // Cancel any pending meta actions if we see any other keys being pressed between the down - // of the meta key and its corresponding up. + Set consumedKeys = mConsumedKeysForDevice.get(deviceId); + if (consumedKeys == null) { + consumedKeys = new HashSet<>(); + mConsumedKeysForDevice.put(deviceId, consumedKeys); + } + + if (interceptSystemKeysAndShortcuts(focusedToken, event) + && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + consumedKeys.add(keyCode); + return keyConsumed; + } + + boolean needToConsumeKey = consumedKeys.contains(keyCode); + if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) { + consumedKeys.remove(keyCode); + if (consumedKeys.isEmpty()) { + mConsumedKeysForDevice.remove(deviceId); + } + } + + return needToConsumeKey ? keyConsumed : keyNotConsumed; + } + + // You can only start consuming the key gesture if ACTION_DOWN and repeat count + // is 0. If you start intercepting the key halfway, then key will not be consumed + // and will be sent to apps for processing too. + // e.g. If a certain combination is only handled on ACTION_UP (i.e. + // interceptShortcut() returns true only for ACTION_UP), then since we already + // sent the ACTION_DOWN events to the application, we MUST also send the + // ACTION_UP to the application. + // So, to ensure that your intercept logic works properly, and we don't send any + // conflicting events to application, make sure to consume the event on + // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential + // to maintain event parity and to not have incomplete key gestures. + @SuppressLint("MissingPermission") + private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) { + final boolean keyguardOn = keyguardOn(); + final int keyCode = event.getKeyCode(); + final int repeatCount = event.getRepeatCount(); + final int metaState = event.getMetaState(); + final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; + final boolean canceled = event.isCanceled(); + final int displayId = event.getDisplayId(); + final int deviceId = event.getDeviceId(); + final boolean firstDown = down && repeatCount == 0; + + // Cancel any pending meta actions if we see any other keys being pressed between the + // down of the meta key and its corresponding up. if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) { mPendingMetaAction = false; } @@ -2992,50 +3038,49 @@ public class PhoneWindowManager implements WindowManagerPolicy { dismissKeyboardShortcutsMenu(); mPendingMetaAction = false; mPendingCapsLockToggle = false; - return key_consumed; + return true; } } - switch(keyCode) { + switch (keyCode) { case KeyEvent.KEYCODE_HOME: - logKeyboardSystemsEvent(event, FrameworkStatsLog - .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME); + logKeyboardSystemsEvent(event, + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME); return handleHomeShortcuts(displayId, focusedToken, event); case KeyEvent.KEYCODE_MENU: // Hijack modified menu keys for debugging features final int chordBug = KeyEvent.META_SHIFT_ON; - if (down && repeatCount == 0) { - if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { - Intent intent = new Intent(Intent.ACTION_BUG_REPORT); - mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, - null, null, null, 0, null, null); - return key_consumed; - } + if (mEnableShiftMenuBugReports && firstDown + && (metaState & chordBug) == chordBug) { + Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, + null, null, null, 0, null, null); + return true; } break; case KeyEvent.KEYCODE_RECENT_APPS: - if (down && repeatCount == 0) { + if (firstDown) { showRecentApps(false /* triggeredFromAltTab */); - logKeyboardSystemsEvent(event, FrameworkStatsLog - .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS); + logKeyboardSystemsEvent(event, + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS); } - return key_consumed; + return true; case KeyEvent.KEYCODE_APP_SWITCH: if (!keyguardOn) { - if (down && repeatCount == 0) { + if (firstDown) { preloadRecentApps(); } else if (!down) { toggleRecentApps(); } } - return key_consumed; + return true; case KeyEvent.KEYCODE_A: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, - event.getDeviceId(), - event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN); - return key_consumed; + deviceId, event.getEventTime(), + AssistUtils.INVOCATION_TYPE_UNKNOWN); + return true; } break; case KeyEvent.KEYCODE_H: @@ -3045,73 +3090,73 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_I: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { showSystemSettings(); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_L: - if (down && event.isMetaPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed()) { lockNow(null /* options */); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_N: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { if (event.isCtrlPressed()) { sendSystemKeyToStatusBarAsync(event); } else { toggleNotificationPanel(); } - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_S: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_T: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { toggleTaskbar(); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_DPAD_UP: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); if (statusbar != null) { statusbar.goToFullscreenFromSplit(); + return true; } - return key_consumed; } break; case KeyEvent.KEYCODE_DPAD_LEFT: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { enterStageSplitFromRunningApp(true /* leftOrTop */); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_DPAD_RIGHT: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { enterStageSplitFromRunningApp(false /* leftOrTop */); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_SLASH: - if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) { + if (firstDown && event.isMetaPressed() && !keyguardOn) { toggleKeyboardShortcutsMenu(event.getDeviceId()); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_ASSIST: Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing"); - return key_consumed; + return true; case KeyEvent.KEYCODE_VOICE_ASSIST: Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in" + " interceptKeyBeforeQueueing"); - return key_consumed; + return true; case KeyEvent.KEYCODE_VIDEO_APP_1: case KeyEvent.KEYCODE_VIDEO_APP_2: case KeyEvent.KEYCODE_VIDEO_APP_3: @@ -3129,7 +3174,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_DEMO_APP_3: case KeyEvent.KEYCODE_DEMO_APP_4: Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing"); - return key_consumed; + return true; case KeyEvent.KEYCODE_BRIGHTNESS_UP: case KeyEvent.KEYCODE_BRIGHTNESS_DOWN: if (down) { @@ -3169,20 +3214,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), UserHandle.CURRENT_OR_SELF); } - return key_consumed; + return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN: if (down) { mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId()); } - return key_consumed; + return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP: if (down) { mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId()); } - return key_consumed; + return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE: // TODO: Add logic - return key_consumed; + return true; case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: @@ -3190,7 +3235,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // On TVs or when the configuration is enabled, volume keys never // go to the foreground app. dispatchDirectAudioEvent(event); - return key_consumed; + return true; } // If the device is in VR mode and keys are "internal" (e.g. on the side of the @@ -3199,26 +3244,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) { final InputDevice d = event.getDevice(); if (d != null && !d.isExternal()) { - return key_consumed; + return true; } } break; case KeyEvent.KEYCODE_TAB: - if (down && event.isMetaPressed()) { - if (!keyguardOn && isUserSetupComplete()) { + if (firstDown && !keyguardOn && isUserSetupComplete()) { + if (event.isMetaPressed()) { showRecentApps(false); - return key_consumed; - } - } else if (down && repeatCount == 0) { - // Display task switcher for ALT-TAB. - if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) { + return true; + } else if (mRecentAppsHeldModifiers == 0) { final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK; if (KeyEvent.metaStateHasModifiers( shiftlessModifiers, KeyEvent.META_ALT_ON)) { mRecentAppsHeldModifiers = shiftlessModifiers; showRecentApps(true); - return key_consumed; + return true; } } } @@ -3230,18 +3272,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { msg.setAsynchronous(true); msg.sendToTarget(); } - return key_consumed; + return true; case KeyEvent.KEYCODE_NOTIFICATION: if (!down) { toggleNotificationPanel(); } - return key_consumed; + return true; case KeyEvent.KEYCODE_SEARCH: - if (down && repeatCount == 0 && !keyguardOn()) { - switch(mSearchKeyBehavior) { + if (firstDown && !keyguardOn) { + switch (mSearchKeyBehavior) { case SEARCH_BEHAVIOR_TARGET_ACTIVITY: { launchTargetSearchActivity(); - return key_consumed; + return true; } case SEARCH_BEHAVIOR_DEFAULT_SEARCH: default: @@ -3250,21 +3292,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_LANGUAGE_SWITCH: - if (down && repeatCount == 0) { + if (firstDown) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; sendSwitchKeyboardLayout(event, direction); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_SPACE: // Handle keyboard layout switching. (META + SPACE) - if ((metaState & KeyEvent.META_META_MASK) == 0) { - return key_not_consumed; - } - if (down && repeatCount == 0) { + if (firstDown && event.isMetaPressed()) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; sendSwitchKeyboardLayout(event, direction); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_META_LEFT: @@ -3289,7 +3328,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingMetaAction = false; } } - return key_consumed; + return true; case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: if (down) { @@ -3305,14 +3344,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { && (metaState & mRecentAppsHeldModifiers) == 0) { mRecentAppsHeldModifiers = 0; hideRecentApps(true, false); - return key_consumed; + return true; } // Toggle Caps Lock on META-ALT. if (mPendingCapsLockToggle) { mInputManagerInternal.toggleCapsLock(event.getDeviceId()); mPendingCapsLockToggle = false; - return key_consumed; + return true; } } break; @@ -3322,24 +3361,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in" + " interceptKeyBeforeQueueing"); - return key_consumed; + return true; } - if (isValidGlobalKey(keyCode) && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { - return key_consumed; + return true; } // Reserve all the META modifier combos for system behavior - if ((metaState & KeyEvent.META_META_ON) != 0) { - return key_consumed; - } - - // Let the application handle the key. - return key_not_consumed; + return (metaState & KeyEvent.META_META_ON) != 0; } - private int handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) { + private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) { // 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