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

Commit f4f9746d authored by Vaibhav Devmurari's avatar Vaibhav Devmurari
Browse files

Add new API for "keyboard capture"

Test: atest InputTests
DD: go/key_capture
Bug: 416681006
Flag: com.android.hardware.input.request_key_capture_api
Change-Id: Ie78024cb38b2aabcc63f12320e60fe01a0121920
parent 7ec4a10b
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -56376,6 +56376,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();
@@ -56389,6 +56390,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);
+55 −1
Original line number Diff line number Diff line
@@ -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}.
         *
@@ -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 {
        }
@@ -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.
@@ -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));
            }
+15 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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,
@@ -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) {
+65 −0
Original line number Diff line number Diff line
@@ -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()
@@ -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() {