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

Commit ac01554b authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Shift block shortcuts before KeyGestureController" into main

parents 0867e667 0c81597e
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.server.input;

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;
import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT;
import static android.view.KeyEvent.KEYCODE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -37,6 +40,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.graphics.PixelFormat;
@@ -121,6 +125,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.os.SomeArgs;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.DisplayThread;
@@ -130,6 +135,7 @@ import com.android.server.input.InputManagerInternal.LidSwitchCallback;
import com.android.server.input.debug.FocusEventDebugView;
import com.android.server.input.debug.TouchpadDebugViewController;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.WindowManagerInternal;

import libcore.io.IoUtils;

@@ -177,6 +183,7 @@ public class InputManagerService extends IInputManager.Stub
    private final InputManagerHandler mHandler;
    private DisplayManagerInternal mDisplayManagerInternal;

    private WindowManagerInternal mWindowManagerInternal;
    private PackageManagerInternal mPackageManagerInternal;

    private final File mDoubleTouchGestureEnableFile;
@@ -548,6 +555,7 @@ public class InputManagerService extends IInputManager.Stub
        }

        mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
        mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);

        mSettingsObserver.registerAndUpdate();
@@ -2471,6 +2479,11 @@ public class InputManagerService extends IInputManager.Stub
    long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
        final long keyNotConsumed = 0;
        long value = keyNotConsumed;
        // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts
        if ((event.isMetaPressed() || KeyEvent.isMetaKey(event.getKeyCode()))
                && shouldInterceptShortcuts(focus)) {
            return keyNotConsumed;
        }
        if (useKeyGestureEventHandler()) {
            value = mKeyGestureController.interceptKeyBeforeDispatching(focus, event, policyFlags);
        }
@@ -2481,6 +2494,16 @@ public class InputManagerService extends IInputManager.Stub
        return value;
    }

    private boolean shouldInterceptShortcuts(IBinder focusedToken) {
        KeyInterceptionInfo info =
                mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
        boolean hasInterceptWindowFlag = info != null && (info.layoutParamsPrivateFlags
                & WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) != 0;
        return hasInterceptWindowFlag && PermissionChecker.checkPermissionForDataDelivery(mContext,
                OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW, PID_UNKNOWN, info.windowOwnerUid,
                null, null, null) == PERMISSION_GRANTED;
    }

    // Native callback.
    @SuppressWarnings("unused")
    private KeyEvent dispatchUnhandledKey(IBinder focus, KeyEvent event, int policyFlags) {
+0 −15
Original line number Diff line number Diff line
@@ -3350,12 +3350,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            }
        }

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

        Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
        if (consumedKeys == null) {
            consumedKeys = new HashSet<>();
@@ -4064,15 +4058,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
    }

    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
+0 −96
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);
    }
}
+0 −5
Original line number Diff line number Diff line
@@ -52,7 +52,6 @@ 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;
@@ -634,10 +633,6 @@ class TestPhoneWindowManager {
                .when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt());
    }

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

    void overrideKeyEventPolicyFlags(int flags) {
        mKeyEventPolicyFlags = flags;
    }
+113 −10
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@
package com.android.server.input


import android.Manifest
import android.content.Context
import android.content.ContextWrapper
import android.content.PermissionChecker
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayViewport
import android.hardware.display.VirtualDisplay
@@ -28,19 +30,26 @@ import android.os.InputEventInjectionSync
import android.os.SystemClock
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings
import android.view.View.OnKeyListener
import android.view.InputDevice
import android.view.KeyEvent
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.WindowManager
import android.test.mock.MockContentResolver
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.policy.KeyInterceptionInfo
import com.android.internal.util.test.FakeSettingsProvider
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.server.LocalServices
import com.android.server.wm.WindowManagerInternal
import com.google.common.truth.Truth.assertThat
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -49,15 +58,15 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when`
import org.mockito.stubbing.OngoingStubbing

/**
@@ -69,14 +78,24 @@ import org.mockito.stubbing.OngoingStubbing
@Presubmit
class InputManagerServiceTests {

    @get:Rule
    val mockitoRule = MockitoJUnit.rule()!!
    companion object {
        val ACTION_KEY_EVENTS = listOf(
            KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_LEFT),
            KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_RIGHT),
            KeyEvent( /* downTime= */0, /* eventTime= */0, /* action= */0, /* code= */0,
                /* repeat= */0, KeyEvent.META_META_ON
            )
        )
    }

    @get:Rule
    val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
    @JvmField
    @Rule
    val extendedMockitoRule =
        ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java)
            .mockStatic(PermissionChecker::class.java).build()!!

    @get:Rule
    val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()!!
    val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!

    @Mock
    private lateinit var native: NativeInputManagerService
@@ -84,6 +103,9 @@ class InputManagerServiceTests {
    @Mock
    private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks

    @Mock
    private lateinit var windowManagerInternal: WindowManagerInternal

    @Mock
    private lateinit var uEventManager: UEventManager

@@ -119,6 +141,10 @@ class InputManagerServiceTests {
        whenever(context.getSystemService(InputManager::class.java)).thenReturn(inputManager)
        whenever(context.getSystemService(Context.INPUT_SERVICE)).thenReturn(inputManager)

        ExtendedMockito.doReturn(windowManagerInternal).`when` {
            LocalServices.getService(eq(WindowManagerInternal::class.java))
        }

        assertTrue("Local service must be registered", this::localService.isInitialized)
        service.setWindowManagerCallbacks(wmCallbacks)
    }
@@ -195,7 +221,7 @@ class InputManagerServiceTests {
    }

    @Test
    fun testAddAndRemoveVirtualmKeyboardLayoutAssociation() {
    fun testAddAndRemoveVirtualKeyboardLayoutAssociation() {
        val inputPort = "input port"
        val languageTag = "language"
        val layoutType = "layoutType"
@@ -206,6 +232,48 @@ class InputManagerServiceTests {
        verify(native, times(2)).changeKeyboardLayoutAssociation()
    }

    @Test
    fun testActionKeyEventsForwardedToFocusedWindow_whenCorrectlyRequested() {
        service.systemRunning()
        overrideSendActionKeyEventsToFocusedWindow(
            /* hasPermission = */true,
            /* hasPrivateFlag = */true
        )
        whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(-1)

        for (event in ACTION_KEY_EVENTS) {
            assertEquals(0, service.interceptKeyBeforeDispatching(null, event, 0))
        }
    }

    @Test
    fun testActionKeyEventsNotForwardedToFocusedWindow_whenNoPermissions() {
        service.systemRunning()
        overrideSendActionKeyEventsToFocusedWindow(
            /* hasPermission = */false,
            /* hasPrivateFlag = */true
        )
        whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(-1)

        for (event in ACTION_KEY_EVENTS) {
            assertNotEquals(0, service.interceptKeyBeforeDispatching(null, event, 0))
        }
    }

    @Test
    fun testActionKeyEventsNotForwardedToFocusedWindow_whenNoPrivateFlag() {
        service.systemRunning()
        overrideSendActionKeyEventsToFocusedWindow(
            /* hasPermission = */true,
            /* hasPrivateFlag = */false
        )
        whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(-1)

        for (event in ACTION_KEY_EVENTS) {
            assertNotEquals(0, service.interceptKeyBeforeDispatching(null, event, 0))
        }
    }

    private fun createVirtualDisplays(count: Int): List<VirtualDisplay> {
        val displayManager: DisplayManager = context.getSystemService(
                DisplayManager::class.java
@@ -373,6 +441,41 @@ class InputManagerServiceTests {
        verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent)
        verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
    }

    fun overrideSendActionKeyEventsToFocusedWindow(
        hasPermission: Boolean,
        hasPrivateFlag: Boolean
    ) {
        ExtendedMockito.doReturn(
            if (hasPermission) {
                PermissionChecker.PERMISSION_GRANTED
            } else {
                PermissionChecker.PERMISSION_HARD_DENIED
            }
        ).`when` {
            PermissionChecker.checkPermissionForDataDelivery(
                any(),
                eq(Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW),
                anyInt(),
                anyInt(),
                any(),
                any(),
                any()
            )
        }

        val info = KeyInterceptionInfo(
            /* type = */0,
            if (hasPrivateFlag) {
                WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS
            } else {
                0
            },
            "title",
            /* uid = */0
        )
        whenever(windowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info)
    }
}

private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)