Loading core/java/android/content/pm/ActivityInfo.java +26 −0 Original line number Diff line number Diff line Loading @@ -1687,6 +1687,32 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @Disabled public static final long OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS = 388014743L; /** * This change id converts {@link android.view.MotionEvent} from a mouse device into touch * events by rewriting its source and tool type when they're delivered to the application. * * <p>Some apps don't work well with mouse events. The override enabled by this change id allows * them to work better with mouse devices by simulating touch events. This is disabled by * default, and can be enabled by device manufacturers on a per-application basis, controlled * via * <a href="https://developer.android.com/guide/practices/device-compatibility-mode#device_manufacturer_per-app_overrides">Device manufacturer per-app overrides</a>. * * <p>App developers whose apps can correctly handle mouse events but are affected by this * override can opt-out by declaring the {@link PackageManager.FEATURE_PC} feature in the * application's manifest. * * <p><b>Syntax to opt-out:</b> * <pre> * <uses-feature android:name="android.hardware.type.pc" * android:required="false" /> * </pre> * @hide */ @ChangeId @Overridable @Disabled public static final long OVERRIDE_MOUSE_TO_TOUCH = 413207127L; /** * Optional set of a certificates identifying apps that are allowed to embed this activity. From * the "knownActivityEmbeddingCerts" attribute. Loading core/java/android/hardware/input/input_framework.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -215,3 +215,10 @@ flag { purpose: PURPOSE_BUGFIX } } flag { name: "mouse_to_touch_per_app_compat" namespace: "lse_desktop_experience" description: "Enables per-app overrides compatibility for mouse to touch" bug: "413207127" } core/java/android/view/input/InputEventCompatHandler.java +42 −0 Original line number Diff line number Diff line Loading @@ -18,8 +18,14 @@ package android.view.input; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.app.compat.CompatChanges; import android.content.Context; import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Handler; import android.text.TextUtils; import android.util.Log; import android.view.InputEvent; import android.view.InputEventCompatProcessor; Loading Loading @@ -128,6 +134,11 @@ public class InputEventCompatHandler { } } if (MouseToTouchProcessor.isCompatibilityNeeded(context)) { chainHead = new InputEventCompatHandler( new MouseToTouchProcessor(context, handler), chainHead); } if (LetterboxScrollProcessor.isCompatibilityNeeded()) { chainHead = new InputEventCompatHandler( new LetterboxScrollProcessor(context, handler), chainHead); Loading @@ -140,4 +151,35 @@ public class InputEventCompatHandler { return chainHead; } /** * Return whether the compatibility of given change ID is required for the given context. * If the application declares {@link PackageManager.FEATURE_PC} in the manifest, the * compatibility is not required. */ static boolean isPcInputCompatibilityNeeded(Context context, long changeId) { if (ActivityThread.isSystem() || !CompatChanges.isChangeEnabled(changeId)) { return false; } // Enabled by the device manufacturer. Check if the app opts out. try { final PackageInfo pkgInfo = context.getPackageManager() .getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS); if (pkgInfo.reqFeatures != null) { for (FeatureInfo feature : pkgInfo.reqFeatures) { if (TextUtils.equals(feature.name, PackageManager.FEATURE_PC)) { return false; } } } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Cannot obtain package info.", e); // pass through. } Log.i(TAG, "Input compatibility " + changeId + " is enabled for " + context.getPackageName()); return true; } } core/java/android/view/input/MouseToTouchProcessor.java 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 android.view.input; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ActivityInfo; import android.os.Handler; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventCompatProcessor; import android.view.MotionEvent; import java.util.List; /** * This rewrites {@link MotionEvent} to have {@link MotionEvent#TOOL_TYPE_FINGER} and * {@link InputDevice.SOURCE_TOUCHSCREEN} if the event is from mouse (or touchpad) if per-app * overrides is enabled on the target application. * * @hide */ public class MouseToTouchProcessor extends InputEventCompatProcessor { private static final String TAG = MouseToTouchProcessor.class.getSimpleName(); /** * Return {@code true} if this compatibility is required based on the given context. * * <p>For debugging, you can toggle this by the following command: * - adb shell am compat enable|disable OVERRIDE_MOUSE_TO_TOUCH [pkg_name] */ public static boolean isCompatibilityNeeded(Context context) { if (!com.android.hardware.input.Flags.mouseToTouchPerAppCompat()) { return false; } return InputEventCompatHandler.isPcInputCompatibilityNeeded( context, ActivityInfo.OVERRIDE_MOUSE_TO_TOUCH); } public MouseToTouchProcessor(Context context, Handler handler) { super(context, handler); } @Override public List<InputEvent> processInputEventForCompatibility(@NonNull InputEvent event) { // TODO(b/413207127): Implement the feature. return null; } @Nullable @Override public InputEvent processInputEventBeforeFinish(@NonNull InputEvent inputEvent) { return inputEvent; } } core/tests/coretests/src/android/view/input/MouseToTouchProcessorTest.kt 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright 2025 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 android.view.input import android.compat.testing.PlatformCompatChangeRule import android.content.Context import android.content.pm.ActivityInfo import android.content.pm.FeatureInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.Presubmit import android.platform.test.flag.junit.SetFlagsRule import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.hardware.input.Flags import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.MatcherAssert.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock /** * Tests for [MouseToTouchProcessor]. * * Build/Install/Run: * atest FrameworksCoreTests:MouseToTouchProcessorTest */ @SmallTest @Presubmit class MouseToTouchProcessorTest { private lateinit var processor: MouseToTouchProcessor private lateinit var context: Context @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() @get:Rule val compatChangeRule = PlatformCompatChangeRule() @Before fun setUp() { context = InstrumentationRegistry.getInstrumentation().targetContext processor = MouseToTouchProcessor(context, null) } @Test @DisableFlags(Flags.FLAG_MOUSE_TO_TOUCH_PER_APP_COMPAT) fun compatibilityNotNeededIfFlagIsDisabled() { assertThat(MouseToTouchProcessor.isCompatibilityNeeded(context), equalTo(false)) } @Test @EnableFlags(Flags.FLAG_MOUSE_TO_TOUCH_PER_APP_COMPAT) @DisableCompatChanges(ActivityInfo.OVERRIDE_MOUSE_TO_TOUCH) fun compatibilityNotNeededIfCompatChangesDisabled() { assertThat(MouseToTouchProcessor.isCompatibilityNeeded(context), equalTo(false)) } @Test @EnableFlags(Flags.FLAG_MOUSE_TO_TOUCH_PER_APP_COMPAT) @EnableCompatChanges(ActivityInfo.OVERRIDE_MOUSE_TO_TOUCH) fun compatibilityNotNeededIfCompatChangesEnabled() { assertThat(MouseToTouchProcessor.isCompatibilityNeeded(context), equalTo(true)) } @Test @EnableFlags(Flags.FLAG_MOUSE_TO_TOUCH_PER_APP_COMPAT) @EnableCompatChanges(ActivityInfo.OVERRIDE_MOUSE_TO_TOUCH) fun compatibilityNotNeededIfFeaturePCPresent() { val mockPackageInfo = PackageInfo().apply { reqFeatures = arrayOf(FeatureInfo().apply { name = PackageManager.FEATURE_PC }) } val packageManager = mock<PackageManager> { on { getPackageInfo(anyOrNull<String>(), any<Int>()) } doReturn mockPackageInfo } val mockContext = mock<Context> { on { getPackageManager() } doReturn packageManager } assertThat(MouseToTouchProcessor.isCompatibilityNeeded(mockContext), equalTo(false)) } @Test @EnableFlags(Flags.FLAG_MOUSE_TO_TOUCH_PER_APP_COMPAT) @EnableCompatChanges(ActivityInfo.OVERRIDE_MOUSE_TO_TOUCH) fun compatibilityNeededIfFeaturePCNotPresent() { val mockPackageInfo = PackageInfo().apply { reqFeatures = arrayOf(FeatureInfo().apply { name = PackageManager.FEATURE_TOUCHSCREEN }) } val packageManager = mock<PackageManager> { on { getPackageInfo(anyOrNull<String>(), any<Int>()) } doReturn mockPackageInfo } val mockContext = mock<Context> { on { getPackageManager() } doReturn packageManager } assertThat(MouseToTouchProcessor.isCompatibilityNeeded(mockContext), equalTo(true)) } } Loading
core/java/android/content/pm/ActivityInfo.java +26 −0 Original line number Diff line number Diff line Loading @@ -1687,6 +1687,32 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @Disabled public static final long OVERRIDE_EXCLUDE_CAPTION_INSETS_FROM_APP_BOUNDS = 388014743L; /** * This change id converts {@link android.view.MotionEvent} from a mouse device into touch * events by rewriting its source and tool type when they're delivered to the application. * * <p>Some apps don't work well with mouse events. The override enabled by this change id allows * them to work better with mouse devices by simulating touch events. This is disabled by * default, and can be enabled by device manufacturers on a per-application basis, controlled * via * <a href="https://developer.android.com/guide/practices/device-compatibility-mode#device_manufacturer_per-app_overrides">Device manufacturer per-app overrides</a>. * * <p>App developers whose apps can correctly handle mouse events but are affected by this * override can opt-out by declaring the {@link PackageManager.FEATURE_PC} feature in the * application's manifest. * * <p><b>Syntax to opt-out:</b> * <pre> * <uses-feature android:name="android.hardware.type.pc" * android:required="false" /> * </pre> * @hide */ @ChangeId @Overridable @Disabled public static final long OVERRIDE_MOUSE_TO_TOUCH = 413207127L; /** * Optional set of a certificates identifying apps that are allowed to embed this activity. From * the "knownActivityEmbeddingCerts" attribute. Loading
core/java/android/hardware/input/input_framework.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -215,3 +215,10 @@ flag { purpose: PURPOSE_BUGFIX } } flag { name: "mouse_to_touch_per_app_compat" namespace: "lse_desktop_experience" description: "Enables per-app overrides compatibility for mouse to touch" bug: "413207127" }
core/java/android/view/input/InputEventCompatHandler.java +42 −0 Original line number Diff line number Diff line Loading @@ -18,8 +18,14 @@ package android.view.input; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.app.compat.CompatChanges; import android.content.Context; import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Handler; import android.text.TextUtils; import android.util.Log; import android.view.InputEvent; import android.view.InputEventCompatProcessor; Loading Loading @@ -128,6 +134,11 @@ public class InputEventCompatHandler { } } if (MouseToTouchProcessor.isCompatibilityNeeded(context)) { chainHead = new InputEventCompatHandler( new MouseToTouchProcessor(context, handler), chainHead); } if (LetterboxScrollProcessor.isCompatibilityNeeded()) { chainHead = new InputEventCompatHandler( new LetterboxScrollProcessor(context, handler), chainHead); Loading @@ -140,4 +151,35 @@ public class InputEventCompatHandler { return chainHead; } /** * Return whether the compatibility of given change ID is required for the given context. * If the application declares {@link PackageManager.FEATURE_PC} in the manifest, the * compatibility is not required. */ static boolean isPcInputCompatibilityNeeded(Context context, long changeId) { if (ActivityThread.isSystem() || !CompatChanges.isChangeEnabled(changeId)) { return false; } // Enabled by the device manufacturer. Check if the app opts out. try { final PackageInfo pkgInfo = context.getPackageManager() .getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS); if (pkgInfo.reqFeatures != null) { for (FeatureInfo feature : pkgInfo.reqFeatures) { if (TextUtils.equals(feature.name, PackageManager.FEATURE_PC)) { return false; } } } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Cannot obtain package info.", e); // pass through. } Log.i(TAG, "Input compatibility " + changeId + " is enabled for " + context.getPackageName()); return true; } }
core/java/android/view/input/MouseToTouchProcessor.java 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 android.view.input; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ActivityInfo; import android.os.Handler; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventCompatProcessor; import android.view.MotionEvent; import java.util.List; /** * This rewrites {@link MotionEvent} to have {@link MotionEvent#TOOL_TYPE_FINGER} and * {@link InputDevice.SOURCE_TOUCHSCREEN} if the event is from mouse (or touchpad) if per-app * overrides is enabled on the target application. * * @hide */ public class MouseToTouchProcessor extends InputEventCompatProcessor { private static final String TAG = MouseToTouchProcessor.class.getSimpleName(); /** * Return {@code true} if this compatibility is required based on the given context. * * <p>For debugging, you can toggle this by the following command: * - adb shell am compat enable|disable OVERRIDE_MOUSE_TO_TOUCH [pkg_name] */ public static boolean isCompatibilityNeeded(Context context) { if (!com.android.hardware.input.Flags.mouseToTouchPerAppCompat()) { return false; } return InputEventCompatHandler.isPcInputCompatibilityNeeded( context, ActivityInfo.OVERRIDE_MOUSE_TO_TOUCH); } public MouseToTouchProcessor(Context context, Handler handler) { super(context, handler); } @Override public List<InputEvent> processInputEventForCompatibility(@NonNull InputEvent event) { // TODO(b/413207127): Implement the feature. return null; } @Nullable @Override public InputEvent processInputEventBeforeFinish(@NonNull InputEvent inputEvent) { return inputEvent; } }
core/tests/coretests/src/android/view/input/MouseToTouchProcessorTest.kt 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright 2025 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 android.view.input import android.compat.testing.PlatformCompatChangeRule import android.content.Context import android.content.pm.ActivityInfo import android.content.pm.FeatureInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.Presubmit import android.platform.test.flag.junit.SetFlagsRule import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.hardware.input.Flags import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.MatcherAssert.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock /** * Tests for [MouseToTouchProcessor]. * * Build/Install/Run: * atest FrameworksCoreTests:MouseToTouchProcessorTest */ @SmallTest @Presubmit class MouseToTouchProcessorTest { private lateinit var processor: MouseToTouchProcessor private lateinit var context: Context @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() @get:Rule val compatChangeRule = PlatformCompatChangeRule() @Before fun setUp() { context = InstrumentationRegistry.getInstrumentation().targetContext processor = MouseToTouchProcessor(context, null) } @Test @DisableFlags(Flags.FLAG_MOUSE_TO_TOUCH_PER_APP_COMPAT) fun compatibilityNotNeededIfFlagIsDisabled() { assertThat(MouseToTouchProcessor.isCompatibilityNeeded(context), equalTo(false)) } @Test @EnableFlags(Flags.FLAG_MOUSE_TO_TOUCH_PER_APP_COMPAT) @DisableCompatChanges(ActivityInfo.OVERRIDE_MOUSE_TO_TOUCH) fun compatibilityNotNeededIfCompatChangesDisabled() { assertThat(MouseToTouchProcessor.isCompatibilityNeeded(context), equalTo(false)) } @Test @EnableFlags(Flags.FLAG_MOUSE_TO_TOUCH_PER_APP_COMPAT) @EnableCompatChanges(ActivityInfo.OVERRIDE_MOUSE_TO_TOUCH) fun compatibilityNotNeededIfCompatChangesEnabled() { assertThat(MouseToTouchProcessor.isCompatibilityNeeded(context), equalTo(true)) } @Test @EnableFlags(Flags.FLAG_MOUSE_TO_TOUCH_PER_APP_COMPAT) @EnableCompatChanges(ActivityInfo.OVERRIDE_MOUSE_TO_TOUCH) fun compatibilityNotNeededIfFeaturePCPresent() { val mockPackageInfo = PackageInfo().apply { reqFeatures = arrayOf(FeatureInfo().apply { name = PackageManager.FEATURE_PC }) } val packageManager = mock<PackageManager> { on { getPackageInfo(anyOrNull<String>(), any<Int>()) } doReturn mockPackageInfo } val mockContext = mock<Context> { on { getPackageManager() } doReturn packageManager } assertThat(MouseToTouchProcessor.isCompatibilityNeeded(mockContext), equalTo(false)) } @Test @EnableFlags(Flags.FLAG_MOUSE_TO_TOUCH_PER_APP_COMPAT) @EnableCompatChanges(ActivityInfo.OVERRIDE_MOUSE_TO_TOUCH) fun compatibilityNeededIfFeaturePCNotPresent() { val mockPackageInfo = PackageInfo().apply { reqFeatures = arrayOf(FeatureInfo().apply { name = PackageManager.FEATURE_TOUCHSCREEN }) } val packageManager = mock<PackageManager> { on { getPackageInfo(anyOrNull<String>(), any<Int>()) } doReturn mockPackageInfo } val mockContext = mock<Context> { on { getPackageManager() } doReturn packageManager } assertThat(MouseToTouchProcessor.isCompatibilityNeeded(mockContext), equalTo(true)) } }