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

Commit 20c78a6c authored by Michal Brzezinski's avatar Michal Brzezinski Committed by Michał Brzeziński
Browse files

Passing meta/action key related key events when private flag is set

New private flag: PRIVATE_FLAG_INTERCEPT_KEYBOARD_SHORTCUTS
Window using it also needs to have permission OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW
in order to receive key events containing meta/action key

Test: add private flag and permission and see key events received in
focused window
Test: PhoneWindowManagerTests
Flag: EXEMPT usage of private flag is flagged in SysUI with com.android.systemui.shared.new_touchpad_gestures_tutorial
Bug: 358587037

Change-Id: I0ad1b838c5a9e2bbe71198950ddb23ea92b2cd47
parent fda1f432
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -3461,6 +3461,15 @@ public interface WindowManager extends ViewManager {
         */
        public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;

        /**
         * Indicates that the window should receive key events including Action/Meta key.
         * They will not be intercepted as usual and instead will be passed to the window with other
         * key events.
         * TODO(b/358569822) Remove this once we have nicer API for listening to shortcuts
         * @hide
         */
        public static final int PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS = 1 << 23;

        /**
         * Flag to indicate that the window is color space agnostic, and the color can be
         * interpreted to any color space.
+15 −0
Original line number Diff line number Diff line
@@ -3352,6 +3352,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            mConsumedKeysForDevice.put(deviceId, consumedKeys);
        }

        // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts
        if ((event.isMetaPressed() || KeyEvent.isMetaKey(keyCode))
                && shouldInterceptShortcuts(focusedToken)) {
            return keyNotConsumed;
        }

        if (interceptSystemKeysAndShortcuts(focusedToken, event)
                && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
            consumedKeys.add(keyCode);
@@ -3842,6 +3848,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        return (metaState & KeyEvent.META_META_ON) != 0;
    }

    private boolean shouldInterceptShortcuts(IBinder focusedToken) {
        KeyInterceptionInfo info =
                mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
        boolean hasInterceptWindowFlag = (info.layoutParamsPrivateFlags
                & WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) != 0;
        return hasInterceptWindowFlag && mButtonOverridePermissionChecker.canAppOverrideSystemKey(
                mContext, info.windowOwnerUid);
    }

    /**
     * In this function, we check whether a system key should be sent to the application. We also
     * detect the key gesture on this key, even if the key will be sent to the app. The gesture
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.policy;

import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS;

import static com.google.common.truth.Truth.assertThat;

import android.view.KeyEvent;
import android.view.WindowManager;

import androidx.test.filters.SmallTest;

import com.android.internal.policy.KeyInterceptionInfo;

import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

/**
 * Testing {@link PhoneWindowManager} functionality of letting app intercepting key events
 * containing META.
 */
@SmallTest
public class MetaKeyEventsInterceptionTests extends ShortcutKeyTestBase {

    private static final List<KeyEvent> META_KEY_EVENTS = Arrays.asList(
            new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_LEFT),
            new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_RIGHT),
            new KeyEvent(/* downTime= */ 0, /* eventTime= */
                    0, /* action= */ 0, /* code= */ 0, /* repeat= */ 0,
                    /* metaState= */ KeyEvent.META_META_ON));

    @Before
    public void setUp() {
        setUpPhoneWindowManager();
    }

    @Test
    public void doesntInterceptMetaKeyEvents_whenWindowAskedForIt() {
        mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true);
        setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS);

        META_KEY_EVENTS.forEach(keyEvent -> {
            assertKeyInterceptionResult(keyEvent, /* intercepted= */ false);
        });
    }

    @Test
    public void interceptsMetaKeyEvents_whenWindowDoesntHaveFlagSet() {
        mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true);
        setWindowKeyInterceptionWithPrivateFlags(0);

        META_KEY_EVENTS.forEach(keyEvent -> {
            assertKeyInterceptionResult(keyEvent, /* intercepted= */ true);
        });
    }

    @Test
    public void interceptsMetaKeyEvents_whenWindowDoesntHavePermission() {
        mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ false);
        setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS);

        META_KEY_EVENTS.forEach(keyEvent -> {
            assertKeyInterceptionResult(keyEvent, /* intercepted= */ true);
        });
    }

    private void setWindowKeyInterceptionWithPrivateFlags(int privateFlags) {
        KeyInterceptionInfo info = new KeyInterceptionInfo(
                WindowManager.LayoutParams.TYPE_APPLICATION, privateFlags, "title", 0);
        mPhoneWindowManager.overrideWindowKeyInterceptionInfo(info);
    }

    private void assertKeyInterceptionResult(KeyEvent keyEvent, boolean intercepted) {
        long result = mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent);
        int expected = intercepted ? -1 : 0;
        assertThat(result).isEqualTo(expected);
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import static org.mockito.Mockito.after;
import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;

import android.app.ActivityManagerInternal;
@@ -614,6 +615,10 @@ class TestPhoneWindowManager {
                .when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt());
    }

    void overrideWindowKeyInterceptionInfo(KeyInterceptionInfo info) {
        when(mWindowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info);
    }

    void overrideKeyEventPolicyFlags(int flags) {
        mKeyEventPolicyFlags = flags;
    }