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

Commit e18c4266 authored by Vaibhav Devmurari's avatar Vaibhav Devmurari Committed by Android (Google) Code Review
Browse files

Merge "Add new API for "keyboard capture"" into main

parents f169e979 f4f9746d
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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();
@@ -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);
+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() {