Loading core/api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -56397,6 +56397,7 @@ package android.view { method public int getFitInsetsTypes(); method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean getFrameRateBoostOnTouchEnabled(); method public final CharSequence getTitle(); method @FlaggedApi("com.android.hardware.input.request_key_capture_api") public boolean hasKeyboardCapture(); method public boolean isFitInsetsIgnoringVisibility(); method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean isFrameRatePowerSavingsBalanced(); method public boolean isHdrConversionEnabled(); Loading @@ -56410,6 +56411,7 @@ package android.view { method public void setFitInsetsTypes(int); method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRateBoostOnTouchEnabled(boolean); method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRatePowerSavingsBalanced(boolean); method @FlaggedApi("com.android.hardware.input.request_key_capture_api") @RequiresPermission(android.Manifest.permission.CAPTURE_KEYBOARD) public void setHasKeyboardCapture(boolean); method public void setHdrConversionEnabled(boolean); method public final void setTitle(CharSequence); method public void setWallpaperTouchEventsEnabled(boolean); core/java/android/view/WindowManager.java +55 −1 Original line number Diff line number Diff line Loading @@ -4607,6 +4607,14 @@ public interface WindowManager extends ViewManager { public static final int INPUT_FEATURE_DISPLAY_TOPOLOGY_AWARE = 1 << 4; /** * Input feature flag used to indicate that this window wants to capture keys before * system processes system shortcuts and actions. * * @hide */ public static final int INPUT_FEATURE_CAPTURE_KEYBOARD = 1 << 5; /** * An internal annotation for flags that can be specified to {@link #inputFeatures}. * Loading @@ -4620,7 +4628,8 @@ public interface WindowManager extends ViewManager { INPUT_FEATURE_DISABLE_USER_ACTIVITY, INPUT_FEATURE_SPY, INPUT_FEATURE_SENSITIVE_FOR_PRIVACY, INPUT_FEATURE_DISPLAY_TOPOLOGY_AWARE INPUT_FEATURE_DISPLAY_TOPOLOGY_AWARE, INPUT_FEATURE_CAPTURE_KEYBOARD }) public @interface InputFeatureFlags { } Loading @@ -4638,6 +4647,47 @@ public interface WindowManager extends ViewManager { @UnsupportedAppUsage public int inputFeatures; /** * Allows the currently focused window to capture keys before system processes system * shortcuts and actions. * * <p> * This will allow the application to receive keys before the system processes system * shortcuts and actions. But certain system keys (Power keys, etc.) and shortcuts can be * reserved and can never be blocked by the current focused window even with "keyboard * capture" on. * </p> * * <p> * Window which set this attribute to {@code true}, but doesn't have the required * permission will not be allowed to capture system shortcuts and actions. No exception * will be thrown due to missing permission, we will just fallback to the default * behavior of processing system shortcuts and actions. * </p> * * @param hasCapture whether the window should capture system shortcuts and actions. */ @FlaggedApi(com.android.hardware.input.Flags.FLAG_REQUEST_KEY_CAPTURE_API) @RequiresPermission(permission.CAPTURE_KEYBOARD) public void setHasKeyboardCapture(boolean hasCapture) { if (hasCapture) { inputFeatures |= INPUT_FEATURE_CAPTURE_KEYBOARD; } else { inputFeatures &= ~INPUT_FEATURE_CAPTURE_KEYBOARD; } } /** * Returns whether "keyboard capture" is on. * * @return whether currently focused window is capturing keys before system processes * shortcuts and actions. */ @FlaggedApi(com.android.hardware.input.Flags.FLAG_REQUEST_KEY_CAPTURE_API) public boolean hasKeyboardCapture() { return (inputFeatures & INPUT_FEATURE_CAPTURE_KEYBOARD) != 0; } /** * Sets the number of milliseconds before the user activity timeout occurs * when this window has focus. A value of -1 uses the standard timeout. Loading Loading @@ -6312,6 +6362,10 @@ public interface WindowManager extends ViewManager { inputFeatures &= ~INPUT_FEATURE_DISPLAY_TOPOLOGY_AWARE; features.add("INPUT_FEATURE_DISPLAY_TOPOLOGY_AWARE"); } if ((inputFeatures & INPUT_FEATURE_CAPTURE_KEYBOARD) != 0) { inputFeatures &= ~INPUT_FEATURE_CAPTURE_KEYBOARD; features.add("INPUT_FEATURE_CAPTURE_KEYBOARD"); } if (inputFeatures != 0) { features.add(Integer.toHexString(inputFeatures)); } Loading services/core/java/com/android/server/input/KeyGestureController.java +15 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.input; import static android.Manifest.permission.CAPTURE_KEYBOARD; import static android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW; import static android.content.PermissionChecker.PERMISSION_GRANTED; import static android.content.PermissionChecker.PID_UNKNOWN; Loading Loading @@ -617,7 +618,10 @@ final class KeyGestureController { return KEY_INTERCEPT_RESULT_CONSUMED; } // TODO(b/416681006): Key capture stage // Allow focused window to capture key events if (canFocusedWindowCaptureKeys(focus)) { return KEY_INTERCEPT_RESULT_NOT_CONSUMED; } // Capture shortcuts and system keys if focused window is not capturing keys if (mInterceptStages.get(INTERCEPT_STAGE_SHORTCUTS_AFTER_KEY_CAPTURE).interceptKey(focus, Loading Loading @@ -650,6 +654,16 @@ final class KeyGestureController { null, null, null) == PERMISSION_GRANTED; } private boolean canFocusedWindowCaptureKeys(IBinder focusedToken) { KeyInterceptionInfo info = mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken); boolean hasCaptureKeyboardFlag = info != null && (info.layoutParamsInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_CAPTURE_KEYBOARD) != 0; return hasCaptureKeyboardFlag && PermissionChecker.checkPermissionForDataDelivery(mContext, CAPTURE_KEYBOARD, PID_UNKNOWN, info.windowOwnerUid, null, null, null) == PERMISSION_GRANTED; } @SuppressLint("MissingPermission") private boolean interceptShortcutsBeforeKeyCapture(@Nullable IBinder focusedToken, @NonNull KeyEvent event) { Loading tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +65 −0 Original line number Diff line number Diff line Loading @@ -452,6 +452,46 @@ class KeyGestureControllerTests { } } @Keep private fun nonCapturableKeyGestures(): Array<KeyGestureData> { return KeyGestureTestData.NON_CAPTURABLE_SYSTEM_GESTURES } @Test @Parameters(method = "nonCapturableKeyGestures") fun testKeyGestures_withKeyCapture_nonCapturableGestures(test: KeyGestureData) { setupKeyGestureController() enableKeyCaptureForFocussedWindow() testKeyGestureProduced(test, BLOCKING_APP) } @Keep private fun capturableKeyGestures(): Array<KeyGestureData> { return KeyGestureTestData.CAPTURABLE_STATEFUL_SYSTEM_GESTURES + KeyGestureTestData.CAPTURABLE_SYSTEM_GESTURES } @Test @Parameters(method = "capturableKeyGestures") fun testKeyGestures_withKeyCapture_capturableGestures(test: KeyGestureData) { setupKeyGestureController() enableKeyCaptureForFocussedWindow() testKeyGestureNotProduced(test, BLOCKING_APP) } @Keep private fun capturableKeyGestures_handledAsFallback(): Array<KeyGestureData> { return KeyGestureTestData.CAPTURABLE_SYSTEM_GESTURES } @Test @Parameters(method = "capturableKeyGestures_handledAsFallback") fun testKeyGestures_withKeyCapture_capturableGesturesHandledAsFallback(test: KeyGestureData) { setupKeyGestureController() enableKeyCaptureForFocussedWindow() testKeyGestureProduced(test, PASS_THROUGH_APP) } @Test fun testKeycodesFullyConsumed_irrespectiveOfHandlers() { setupKeyGestureController() Loading Loading @@ -1429,6 +1469,31 @@ class KeyGestureControllerTests { .thenReturn(info) } fun enableKeyCaptureForFocussedWindow() { ExtendedMockito.doReturn(PermissionChecker.PERMISSION_GRANTED).`when` { PermissionChecker.checkPermissionForDataDelivery( any(), eq(Manifest.permission.CAPTURE_KEYBOARD), anyInt(), anyInt(), any(), any(), any(), ) } val info = KeyInterceptionInfo( /* type = */ 0, /* flags = */ 0, WindowManager.LayoutParams.INPUT_FEATURE_CAPTURE_KEYBOARD, "title", /* uid = */ 0, ) Mockito.`when`(windowManagerInternal.getKeyInterceptionInfoFromToken(any())) .thenReturn(info) } inner class KeyGestureEventListener( private var listener: (event: AidlKeyGestureEvent) -> Unit ) : IKeyGestureEventListener.Stub() { Loading Loading
core/api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -56397,6 +56397,7 @@ package android.view { method public int getFitInsetsTypes(); method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean getFrameRateBoostOnTouchEnabled(); method public final CharSequence getTitle(); method @FlaggedApi("com.android.hardware.input.request_key_capture_api") public boolean hasKeyboardCapture(); method public boolean isFitInsetsIgnoringVisibility(); method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean isFrameRatePowerSavingsBalanced(); method public boolean isHdrConversionEnabled(); Loading @@ -56410,6 +56411,7 @@ package android.view { method public void setFitInsetsTypes(int); method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRateBoostOnTouchEnabled(boolean); method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRatePowerSavingsBalanced(boolean); method @FlaggedApi("com.android.hardware.input.request_key_capture_api") @RequiresPermission(android.Manifest.permission.CAPTURE_KEYBOARD) public void setHasKeyboardCapture(boolean); method public void setHdrConversionEnabled(boolean); method public final void setTitle(CharSequence); method public void setWallpaperTouchEventsEnabled(boolean);
core/java/android/view/WindowManager.java +55 −1 Original line number Diff line number Diff line Loading @@ -4607,6 +4607,14 @@ public interface WindowManager extends ViewManager { public static final int INPUT_FEATURE_DISPLAY_TOPOLOGY_AWARE = 1 << 4; /** * Input feature flag used to indicate that this window wants to capture keys before * system processes system shortcuts and actions. * * @hide */ public static final int INPUT_FEATURE_CAPTURE_KEYBOARD = 1 << 5; /** * An internal annotation for flags that can be specified to {@link #inputFeatures}. * Loading @@ -4620,7 +4628,8 @@ public interface WindowManager extends ViewManager { INPUT_FEATURE_DISABLE_USER_ACTIVITY, INPUT_FEATURE_SPY, INPUT_FEATURE_SENSITIVE_FOR_PRIVACY, INPUT_FEATURE_DISPLAY_TOPOLOGY_AWARE INPUT_FEATURE_DISPLAY_TOPOLOGY_AWARE, INPUT_FEATURE_CAPTURE_KEYBOARD }) public @interface InputFeatureFlags { } Loading @@ -4638,6 +4647,47 @@ public interface WindowManager extends ViewManager { @UnsupportedAppUsage public int inputFeatures; /** * Allows the currently focused window to capture keys before system processes system * shortcuts and actions. * * <p> * This will allow the application to receive keys before the system processes system * shortcuts and actions. But certain system keys (Power keys, etc.) and shortcuts can be * reserved and can never be blocked by the current focused window even with "keyboard * capture" on. * </p> * * <p> * Window which set this attribute to {@code true}, but doesn't have the required * permission will not be allowed to capture system shortcuts and actions. No exception * will be thrown due to missing permission, we will just fallback to the default * behavior of processing system shortcuts and actions. * </p> * * @param hasCapture whether the window should capture system shortcuts and actions. */ @FlaggedApi(com.android.hardware.input.Flags.FLAG_REQUEST_KEY_CAPTURE_API) @RequiresPermission(permission.CAPTURE_KEYBOARD) public void setHasKeyboardCapture(boolean hasCapture) { if (hasCapture) { inputFeatures |= INPUT_FEATURE_CAPTURE_KEYBOARD; } else { inputFeatures &= ~INPUT_FEATURE_CAPTURE_KEYBOARD; } } /** * Returns whether "keyboard capture" is on. * * @return whether currently focused window is capturing keys before system processes * shortcuts and actions. */ @FlaggedApi(com.android.hardware.input.Flags.FLAG_REQUEST_KEY_CAPTURE_API) public boolean hasKeyboardCapture() { return (inputFeatures & INPUT_FEATURE_CAPTURE_KEYBOARD) != 0; } /** * Sets the number of milliseconds before the user activity timeout occurs * when this window has focus. A value of -1 uses the standard timeout. Loading Loading @@ -6312,6 +6362,10 @@ public interface WindowManager extends ViewManager { inputFeatures &= ~INPUT_FEATURE_DISPLAY_TOPOLOGY_AWARE; features.add("INPUT_FEATURE_DISPLAY_TOPOLOGY_AWARE"); } if ((inputFeatures & INPUT_FEATURE_CAPTURE_KEYBOARD) != 0) { inputFeatures &= ~INPUT_FEATURE_CAPTURE_KEYBOARD; features.add("INPUT_FEATURE_CAPTURE_KEYBOARD"); } if (inputFeatures != 0) { features.add(Integer.toHexString(inputFeatures)); } Loading
services/core/java/com/android/server/input/KeyGestureController.java +15 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.input; import static android.Manifest.permission.CAPTURE_KEYBOARD; import static android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW; import static android.content.PermissionChecker.PERMISSION_GRANTED; import static android.content.PermissionChecker.PID_UNKNOWN; Loading Loading @@ -617,7 +618,10 @@ final class KeyGestureController { return KEY_INTERCEPT_RESULT_CONSUMED; } // TODO(b/416681006): Key capture stage // Allow focused window to capture key events if (canFocusedWindowCaptureKeys(focus)) { return KEY_INTERCEPT_RESULT_NOT_CONSUMED; } // Capture shortcuts and system keys if focused window is not capturing keys if (mInterceptStages.get(INTERCEPT_STAGE_SHORTCUTS_AFTER_KEY_CAPTURE).interceptKey(focus, Loading Loading @@ -650,6 +654,16 @@ final class KeyGestureController { null, null, null) == PERMISSION_GRANTED; } private boolean canFocusedWindowCaptureKeys(IBinder focusedToken) { KeyInterceptionInfo info = mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken); boolean hasCaptureKeyboardFlag = info != null && (info.layoutParamsInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_CAPTURE_KEYBOARD) != 0; return hasCaptureKeyboardFlag && PermissionChecker.checkPermissionForDataDelivery(mContext, CAPTURE_KEYBOARD, PID_UNKNOWN, info.windowOwnerUid, null, null, null) == PERMISSION_GRANTED; } @SuppressLint("MissingPermission") private boolean interceptShortcutsBeforeKeyCapture(@Nullable IBinder focusedToken, @NonNull KeyEvent event) { Loading
tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +65 −0 Original line number Diff line number Diff line Loading @@ -452,6 +452,46 @@ class KeyGestureControllerTests { } } @Keep private fun nonCapturableKeyGestures(): Array<KeyGestureData> { return KeyGestureTestData.NON_CAPTURABLE_SYSTEM_GESTURES } @Test @Parameters(method = "nonCapturableKeyGestures") fun testKeyGestures_withKeyCapture_nonCapturableGestures(test: KeyGestureData) { setupKeyGestureController() enableKeyCaptureForFocussedWindow() testKeyGestureProduced(test, BLOCKING_APP) } @Keep private fun capturableKeyGestures(): Array<KeyGestureData> { return KeyGestureTestData.CAPTURABLE_STATEFUL_SYSTEM_GESTURES + KeyGestureTestData.CAPTURABLE_SYSTEM_GESTURES } @Test @Parameters(method = "capturableKeyGestures") fun testKeyGestures_withKeyCapture_capturableGestures(test: KeyGestureData) { setupKeyGestureController() enableKeyCaptureForFocussedWindow() testKeyGestureNotProduced(test, BLOCKING_APP) } @Keep private fun capturableKeyGestures_handledAsFallback(): Array<KeyGestureData> { return KeyGestureTestData.CAPTURABLE_SYSTEM_GESTURES } @Test @Parameters(method = "capturableKeyGestures_handledAsFallback") fun testKeyGestures_withKeyCapture_capturableGesturesHandledAsFallback(test: KeyGestureData) { setupKeyGestureController() enableKeyCaptureForFocussedWindow() testKeyGestureProduced(test, PASS_THROUGH_APP) } @Test fun testKeycodesFullyConsumed_irrespectiveOfHandlers() { setupKeyGestureController() Loading Loading @@ -1429,6 +1469,31 @@ class KeyGestureControllerTests { .thenReturn(info) } fun enableKeyCaptureForFocussedWindow() { ExtendedMockito.doReturn(PermissionChecker.PERMISSION_GRANTED).`when` { PermissionChecker.checkPermissionForDataDelivery( any(), eq(Manifest.permission.CAPTURE_KEYBOARD), anyInt(), anyInt(), any(), any(), any(), ) } val info = KeyInterceptionInfo( /* type = */ 0, /* flags = */ 0, WindowManager.LayoutParams.INPUT_FEATURE_CAPTURE_KEYBOARD, "title", /* uid = */ 0, ) Mockito.`when`(windowManagerInternal.getKeyInterceptionInfoFromToken(any())) .thenReturn(info) } inner class KeyGestureEventListener( private var listener: (event: AidlKeyGestureEvent) -> Unit ) : IKeyGestureEventListener.Stub() { Loading