diff options
5 files changed, 136 insertions, 126 deletions
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 44d675305ac0..edc35e553244 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -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) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 98091b1e617f..ef37464704ae 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -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 diff --git a/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java b/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java deleted file mode 100644 index b979335233e3..000000000000 --- a/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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); - } -} diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 0b55e2b9313e..98401b33840f 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -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; } diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index 8829f74f5092..c2e71f8269da 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -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 @@ -85,6 +104,9 @@ class InputManagerServiceTests { private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks @Mock + private lateinit var windowManagerInternal: WindowManagerInternal + + @Mock private lateinit var uEventManager: UEventManager private lateinit var service: InputManagerService @@ -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) |