Move common device metrics code to InputDeviceMetricsSource

Move common device metrics code from InputDeviceMetricsCollector to
InputDeviceMetricsSource.
The common code moved is:
* enum class InputDeviceUsageSource
* InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t, const NotifyKeyArgs&)
* set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs&)

This code will be used by both InputDeviceMetricsCollector,
which is a part of inputflinger as well as LatencyTracker,
which is a part of the dispatcher.
InputDeviceMetricsSource can be included in the places using the common code.

Bug: 303059859
Test: atest inputflinger_tests:InputDeviceMetricsSourceDeviceClassificationTest
Change-Id: Ia8528e641cd162d5025495d5bf6903b2dbab2ed3
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 5d1d4af..76729ef 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -182,6 +182,7 @@
 filegroup {
     name: "libinputflinger_base_sources",
     srcs: [
+        "InputDeviceMetricsSource.cpp",
         "InputListener.cpp",
         "InputReaderBase.cpp",
         "InputThread.cpp",
@@ -199,6 +200,7 @@
         "libcutils",
         "libinput",
         "liblog",
+        "libstatslog",
         "libutils",
     ],
     header_libs: [
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index 8e04150..cefb140 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -17,11 +17,10 @@
 #define LOG_TAG "InputDeviceMetricsCollector"
 #include "InputDeviceMetricsCollector.h"
 
-#include "KeyCodeClassifications.h"
+#include "InputDeviceMetricsSource.h"
 
 #include <android-base/stringprintf.h>
 #include <input/PrintTools.h>
-#include <linux/input.h>
 
 namespace android {
 
@@ -113,96 +112,6 @@
 
 } // namespace
 
-InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType,
-                                                const NotifyKeyArgs& keyArgs) {
-    if (!isFromSource(keyArgs.source, AINPUT_SOURCE_KEYBOARD)) {
-        return InputDeviceUsageSource::UNKNOWN;
-    }
-
-    if (isFromSource(keyArgs.source, AINPUT_SOURCE_DPAD) &&
-        DPAD_ALL_KEYCODES.count(keyArgs.keyCode) != 0) {
-        return InputDeviceUsageSource::DPAD;
-    }
-
-    if (isFromSource(keyArgs.source, AINPUT_SOURCE_GAMEPAD) &&
-        GAMEPAD_KEYCODES.count(keyArgs.keyCode) != 0) {
-        return InputDeviceUsageSource::GAMEPAD;
-    }
-
-    if (keyboardType == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
-        return InputDeviceUsageSource::KEYBOARD;
-    }
-
-    return InputDeviceUsageSource::BUTTONS;
-}
-
-std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs& motionArgs) {
-    LOG_ALWAYS_FATAL_IF(motionArgs.getPointerCount() < 1, "Received motion args without pointers");
-    std::set<InputDeviceUsageSource> sources;
-
-    for (uint32_t i = 0; i < motionArgs.getPointerCount(); i++) {
-        const auto toolType = motionArgs.pointerProperties[i].toolType;
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE)) {
-            if (toolType == ToolType::MOUSE) {
-                sources.emplace(InputDeviceUsageSource::MOUSE);
-                continue;
-            }
-            if (toolType == ToolType::FINGER) {
-                sources.emplace(InputDeviceUsageSource::TOUCHPAD);
-                continue;
-            }
-            if (isStylusToolType(toolType)) {
-                sources.emplace(InputDeviceUsageSource::STYLUS_INDIRECT);
-                continue;
-            }
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE_RELATIVE) &&
-            toolType == ToolType::MOUSE) {
-            sources.emplace(InputDeviceUsageSource::MOUSE_CAPTURED);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHPAD) &&
-            toolType == ToolType::FINGER) {
-            sources.emplace(InputDeviceUsageSource::TOUCHPAD_CAPTURED);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_BLUETOOTH_STYLUS) &&
-            isStylusToolType(toolType)) {
-            sources.emplace(InputDeviceUsageSource::STYLUS_FUSED);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_STYLUS) && isStylusToolType(toolType)) {
-            sources.emplace(InputDeviceUsageSource::STYLUS_DIRECT);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCH_NAVIGATION)) {
-            sources.emplace(InputDeviceUsageSource::TOUCH_NAVIGATION);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_JOYSTICK)) {
-            sources.emplace(InputDeviceUsageSource::JOYSTICK);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_ROTARY_ENCODER)) {
-            sources.emplace(InputDeviceUsageSource::ROTARY_ENCODER);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TRACKBALL)) {
-            sources.emplace(InputDeviceUsageSource::TRACKBALL);
-            continue;
-        }
-        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHSCREEN)) {
-            sources.emplace(InputDeviceUsageSource::TOUCHSCREEN);
-            continue;
-        }
-        sources.emplace(InputDeviceUsageSource::UNKNOWN);
-    }
-
-    return sources;
-}
-
-// --- InputDeviceMetricsCollector ---
-
 InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener)
       : InputDeviceMetricsCollector(listener, sStatsdLogger, DEFAULT_USAGE_SESSION_TIMEOUT) {}
 
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index 7775087..9633664 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "InputDeviceMetricsSource.h"
 #include "InputListener.h"
 #include "NotifyArgs.h"
 #include "SyncQueue.h"
@@ -23,7 +24,6 @@
 #include <ftl/mixins.h>
 #include <gui/WindowInfo.h>
 #include <input/InputDevice.h>
-#include <statslog.h>
 #include <chrono>
 #include <functional>
 #include <map>
@@ -52,38 +52,6 @@
     virtual void dump(std::string& dump) = 0;
 };
 
-/**
- * Enum representation of the InputDeviceUsageSource.
- */
-enum class InputDeviceUsageSource : int32_t {
-    UNKNOWN = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__UNKNOWN,
-    BUTTONS = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__BUTTONS,
-    KEYBOARD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__KEYBOARD,
-    DPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__DPAD,
-    GAMEPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__GAMEPAD,
-    JOYSTICK = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__JOYSTICK,
-    MOUSE = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__MOUSE,
-    MOUSE_CAPTURED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__MOUSE_CAPTURED,
-    TOUCHPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHPAD,
-    TOUCHPAD_CAPTURED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHPAD_CAPTURED,
-    ROTARY_ENCODER = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__ROTARY_ENCODER,
-    STYLUS_DIRECT = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_DIRECT,
-    STYLUS_INDIRECT = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_INDIRECT,
-    STYLUS_FUSED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_FUSED,
-    TOUCH_NAVIGATION = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCH_NAVIGATION,
-    TOUCHSCREEN = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHSCREEN,
-    TRACKBALL = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TRACKBALL,
-
-    ftl_first = UNKNOWN,
-    ftl_last = TRACKBALL,
-};
-
-/** Returns the InputDeviceUsageSource that corresponds to the key event. */
-InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType, const NotifyKeyArgs&);
-
-/** Returns the InputDeviceUsageSources that correspond to the motion event. */
-std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs&);
-
 /** The logging interface for the metrics collector, injected for testing. */
 class InputDeviceMetricsLogger {
 public:
diff --git a/services/inputflinger/InputDeviceMetricsSource.cpp b/services/inputflinger/InputDeviceMetricsSource.cpp
new file mode 100644
index 0000000..dee4cb8
--- /dev/null
+++ b/services/inputflinger/InputDeviceMetricsSource.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include "InputDeviceMetricsSource.h"
+
+#include "KeyCodeClassifications.h"
+
+#include <android/input.h>
+#include <input/Input.h>
+#include <linux/input.h>
+#include <log/log_main.h>
+
+#include <set>
+
+namespace android {
+
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType,
+                                                const NotifyKeyArgs& keyArgs) {
+    if (!isFromSource(keyArgs.source, AINPUT_SOURCE_KEYBOARD)) {
+        return InputDeviceUsageSource::UNKNOWN;
+    }
+
+    if (isFromSource(keyArgs.source, AINPUT_SOURCE_DPAD) &&
+        DPAD_ALL_KEYCODES.count(keyArgs.keyCode) != 0) {
+        return InputDeviceUsageSource::DPAD;
+    }
+
+    if (isFromSource(keyArgs.source, AINPUT_SOURCE_GAMEPAD) &&
+        GAMEPAD_KEYCODES.count(keyArgs.keyCode) != 0) {
+        return InputDeviceUsageSource::GAMEPAD;
+    }
+
+    if (keyboardType == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
+        return InputDeviceUsageSource::KEYBOARD;
+    }
+
+    return InputDeviceUsageSource::BUTTONS;
+}
+
+std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs& motionArgs) {
+    LOG_ALWAYS_FATAL_IF(motionArgs.getPointerCount() < 1, "Received motion args without pointers");
+    std::set<InputDeviceUsageSource> sources;
+
+    for (uint32_t i = 0; i < motionArgs.getPointerCount(); i++) {
+        const auto toolType = motionArgs.pointerProperties[i].toolType;
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE)) {
+            if (toolType == ToolType::MOUSE) {
+                sources.emplace(InputDeviceUsageSource::MOUSE);
+                continue;
+            }
+            if (toolType == ToolType::FINGER) {
+                sources.emplace(InputDeviceUsageSource::TOUCHPAD);
+                continue;
+            }
+            if (isStylusToolType(toolType)) {
+                sources.emplace(InputDeviceUsageSource::STYLUS_INDIRECT);
+                continue;
+            }
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE_RELATIVE) &&
+            toolType == ToolType::MOUSE) {
+            sources.emplace(InputDeviceUsageSource::MOUSE_CAPTURED);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHPAD) &&
+            toolType == ToolType::FINGER) {
+            sources.emplace(InputDeviceUsageSource::TOUCHPAD_CAPTURED);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_BLUETOOTH_STYLUS) &&
+            isStylusToolType(toolType)) {
+            sources.emplace(InputDeviceUsageSource::STYLUS_FUSED);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_STYLUS) && isStylusToolType(toolType)) {
+            sources.emplace(InputDeviceUsageSource::STYLUS_DIRECT);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCH_NAVIGATION)) {
+            sources.emplace(InputDeviceUsageSource::TOUCH_NAVIGATION);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_JOYSTICK)) {
+            sources.emplace(InputDeviceUsageSource::JOYSTICK);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_ROTARY_ENCODER)) {
+            sources.emplace(InputDeviceUsageSource::ROTARY_ENCODER);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TRACKBALL)) {
+            sources.emplace(InputDeviceUsageSource::TRACKBALL);
+            continue;
+        }
+        if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHSCREEN)) {
+            sources.emplace(InputDeviceUsageSource::TOUCHSCREEN);
+            continue;
+        }
+        sources.emplace(InputDeviceUsageSource::UNKNOWN);
+    }
+
+    return sources;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputDeviceMetricsSource.h b/services/inputflinger/InputDeviceMetricsSource.h
new file mode 100644
index 0000000..3ac91c8
--- /dev/null
+++ b/services/inputflinger/InputDeviceMetricsSource.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#pragma once
+
+#include "NotifyArgs.h"
+
+#include <linux/input.h>
+#include <statslog.h>
+
+namespace android {
+
+/**
+ * Enum representation of the InputDeviceUsageSource.
+ */
+enum class InputDeviceUsageSource : int32_t {
+    UNKNOWN = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__UNKNOWN,
+    BUTTONS = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__BUTTONS,
+    KEYBOARD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__KEYBOARD,
+    DPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__DPAD,
+    GAMEPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__GAMEPAD,
+    JOYSTICK = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__JOYSTICK,
+    MOUSE = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__MOUSE,
+    MOUSE_CAPTURED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__MOUSE_CAPTURED,
+    TOUCHPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHPAD,
+    TOUCHPAD_CAPTURED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHPAD_CAPTURED,
+    ROTARY_ENCODER = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__ROTARY_ENCODER,
+    STYLUS_DIRECT = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_DIRECT,
+    STYLUS_INDIRECT = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_INDIRECT,
+    STYLUS_FUSED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_FUSED,
+    TOUCH_NAVIGATION = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCH_NAVIGATION,
+    TOUCHSCREEN = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHSCREEN,
+    TRACKBALL = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TRACKBALL,
+
+    ftl_first = UNKNOWN,
+    ftl_last = TRACKBALL,
+};
+
+/** Returns the InputDeviceUsageSource that corresponds to the key event. */
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType, const NotifyKeyArgs&);
+
+/** Returns the InputDeviceUsageSources that correspond to the motion event. */
+std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs&);
+
+} // namespace android
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index d87a5a7..c0353af 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -50,6 +50,7 @@
         "HardwareProperties_test.cpp",
         "HardwareStateConverter_test.cpp",
         "InputDeviceMetricsCollector_test.cpp",
+        "InputDeviceMetricsSource_test.cpp",
         "InputMapperTest.cpp",
         "InputProcessor_test.cpp",
         "InputProcessorConverter_test.cpp",
diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
index fdf9ed1..85e055d 100644
--- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -36,7 +36,6 @@
 
 constexpr auto USAGE_TIMEOUT = 8765309ns;
 constexpr auto TIME = 999999ns;
-constexpr auto ALL_USAGE_SOURCES = ftl::enum_range<InputDeviceUsageSource>();
 
 constexpr int32_t DEVICE_ID = 3;
 constexpr int32_t DEVICE_ID_2 = 4;
@@ -48,10 +47,6 @@
 const std::string UNIQUE_ID = "Yosemite";
 constexpr uint32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN;
 constexpr uint32_t STYLUS = AINPUT_SOURCE_STYLUS;
-constexpr uint32_t KEY_SOURCES =
-        AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD;
-constexpr int32_t POINTER_1_DOWN =
-        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
 InputDeviceIdentifier generateTestIdentifier(int32_t id = DEVICE_ID) {
     InputDeviceIdentifier identifier;
@@ -87,258 +82,6 @@
 
 } // namespace
 
-// --- InputDeviceMetricsCollectorDeviceClassificationTest ---
-
-class DeviceClassificationFixture : public ::testing::Test,
-                                    public ::testing::WithParamInterface<InputDeviceUsageSource> {};
-
-TEST_P(DeviceClassificationFixture, ValidClassifications) {
-    const InputDeviceUsageSource usageSource = GetParam();
-
-    // Use a switch to ensure a test is added for all source classifications.
-    switch (usageSource) {
-        case InputDeviceUsageSource::UNKNOWN: {
-            ASSERT_EQ(InputDeviceUsageSource::UNKNOWN,
-                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NONE,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, TOUCHSCREEN)
-                                                       .build()));
-
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::UNKNOWN};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_KEYBOARD)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::PALM)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::BUTTONS: {
-            ASSERT_EQ(InputDeviceUsageSource::BUTTONS,
-                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .keyCode(AKEYCODE_STYLUS_BUTTON_TAIL)
-                                                       .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::KEYBOARD: {
-            ASSERT_EQ(InputDeviceUsageSource::KEYBOARD,
-                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::DPAD: {
-            ASSERT_EQ(InputDeviceUsageSource::DPAD,
-                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .keyCode(AKEYCODE_DPAD_CENTER)
-                                                       .build()));
-
-            ASSERT_EQ(InputDeviceUsageSource::DPAD,
-                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .keyCode(AKEYCODE_DPAD_CENTER)
-                                                       .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::GAMEPAD: {
-            ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
-                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .keyCode(AKEYCODE_BUTTON_A)
-                                                       .build()));
-
-            ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
-                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
-                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
-                                                       .keyCode(AKEYCODE_BUTTON_A)
-                                                       .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::JOYSTICK: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::JOYSTICK};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_JOYSTICK)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
-                                                       .axis(AMOTION_EVENT_AXIS_GAS, 1.f))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::MOUSE: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::MOUSE};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
-                                                AINPUT_SOURCE_MOUSE)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::MOUSE)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::MOUSE_CAPTURED: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::MOUSE_CAPTURED};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
-                                                AINPUT_SOURCE_MOUSE_RELATIVE)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::MOUSE)
-                                                       .x(100)
-                                                       .y(200)
-                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 100)
-                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::TOUCHPAD: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHPAD};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::TOUCHPAD_CAPTURED: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHPAD_CAPTURED};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHPAD)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
-                                                       .x(100)
-                                                       .y(200)
-                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 1)
-                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 2))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::ROTARY_ENCODER: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::ROTARY_ENCODER};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL,
-                                                AINPUT_SOURCE_ROTARY_ENCODER)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
-                                                       .axis(AMOTION_EVENT_AXIS_SCROLL, 10)
-                                                       .axis(AMOTION_EVENT_AXIS_VSCROLL, 10))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::STYLUS_DIRECT: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_DIRECT};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
-                                                STYLUS | TOUCHSCREEN)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::STYLUS_INDIRECT: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_INDIRECT};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
-                                                STYLUS | TOUCHSCREEN | AINPUT_SOURCE_MOUSE)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::STYLUS_FUSED: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_FUSED};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
-                                                AINPUT_SOURCE_BLUETOOTH_STYLUS | TOUCHSCREEN)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::TOUCH_NAVIGATION: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCH_NAVIGATION};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
-                                                AINPUT_SOURCE_TOUCH_NAVIGATION)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
-                                                       .x(100)
-                                                       .y(200))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::TOUCHSCREEN: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHSCREEN};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(POINTER_1_DOWN, TOUCHSCREEN)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
-                                                       .x(100)
-                                                       .y(200))
-                                      .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER)
-                                                       .x(300)
-                                                       .y(400))
-                                      .build()));
-            break;
-        }
-
-        case InputDeviceUsageSource::TRACKBALL: {
-            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TRACKBALL};
-            ASSERT_EQ(srcs,
-                      getUsageSourcesForMotionArgs(
-                              MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL,
-                                                AINPUT_SOURCE_TRACKBALL)
-                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
-                                                       .axis(AMOTION_EVENT_AXIS_VSCROLL, 100)
-                                                       .axis(AMOTION_EVENT_AXIS_HSCROLL, 200))
-                                      .build()));
-            break;
-        }
-    }
-}
-
-INSTANTIATE_TEST_SUITE_P(InputDeviceMetricsCollectorDeviceClassificationTest,
-                         DeviceClassificationFixture,
-                         ::testing::ValuesIn(ALL_USAGE_SOURCES.begin(), ALL_USAGE_SOURCES.end()),
-                         [](const testing::TestParamInfo<InputDeviceUsageSource>& testParamInfo) {
-                             return ftl::enum_string(testParamInfo.param);
-                         });
-
-TEST(InputDeviceMetricsCollectorDeviceClassificationTest, MixedClassificationTouchscreenStylus) {
-    std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHSCREEN,
-                                          InputDeviceUsageSource::STYLUS_DIRECT};
-    ASSERT_EQ(srcs,
-              getUsageSourcesForMotionArgs(
-                      MotionArgsBuilder(POINTER_1_DOWN, TOUCHSCREEN | STYLUS)
-                              .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(100).y(200))
-                              .pointer(PointerBuilder(/*id=*/2, ToolType::STYLUS).x(300).y(400))
-                              .build()));
-}
-
 // --- InputDeviceMetricsCollectorTest ---
 
 class InputDeviceMetricsCollectorTest : public testing::Test, public InputDeviceMetricsLogger {
diff --git a/services/inputflinger/tests/InputDeviceMetricsSource_test.cpp b/services/inputflinger/tests/InputDeviceMetricsSource_test.cpp
new file mode 100644
index 0000000..84ef52c
--- /dev/null
+++ b/services/inputflinger/tests/InputDeviceMetricsSource_test.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include "../InputDeviceMetricsSource.h"
+
+#include <NotifyArgsBuilders.h>
+
+#include <android/input.h>
+#include <ftl/enum.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <input/InputEventBuilders.h>
+#include <linux/input.h>
+
+#include <set>
+
+namespace android {
+
+namespace {
+
+constexpr auto ALL_USAGE_SOURCES = ftl::enum_range<InputDeviceUsageSource>();
+constexpr uint32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN;
+constexpr uint32_t STYLUS = AINPUT_SOURCE_STYLUS;
+constexpr uint32_t KEY_SOURCES =
+        AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD;
+constexpr int32_t POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+} // namespace
+
+// --- InputDeviceMetricsSourceDeviceClassificationTest ---
+
+class DeviceClassificationFixture : public ::testing::Test,
+                                    public ::testing::WithParamInterface<InputDeviceUsageSource> {};
+
+TEST_P(DeviceClassificationFixture, ValidClassifications) {
+    const InputDeviceUsageSource usageSource = GetParam();
+
+    // Use a switch to ensure a test is added for all source classifications.
+    switch (usageSource) {
+        case InputDeviceUsageSource::UNKNOWN: {
+            ASSERT_EQ(InputDeviceUsageSource::UNKNOWN,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NONE,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, TOUCHSCREEN)
+                                                       .build()));
+
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::UNKNOWN};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_KEYBOARD)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::PALM)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::BUTTONS: {
+            ASSERT_EQ(InputDeviceUsageSource::BUTTONS,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .keyCode(AKEYCODE_STYLUS_BUTTON_TAIL)
+                                                       .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::KEYBOARD: {
+            ASSERT_EQ(InputDeviceUsageSource::KEYBOARD,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::DPAD: {
+            ASSERT_EQ(InputDeviceUsageSource::DPAD,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .keyCode(AKEYCODE_DPAD_CENTER)
+                                                       .build()));
+
+            ASSERT_EQ(InputDeviceUsageSource::DPAD,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .keyCode(AKEYCODE_DPAD_CENTER)
+                                                       .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::GAMEPAD: {
+            ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .keyCode(AKEYCODE_BUTTON_A)
+                                                       .build()));
+
+            ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+                                               KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+                                                       .keyCode(AKEYCODE_BUTTON_A)
+                                                       .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::JOYSTICK: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::JOYSTICK};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_JOYSTICK)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
+                                                       .axis(AMOTION_EVENT_AXIS_GAS, 1.f))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::MOUSE: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::MOUSE};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::MOUSE)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::MOUSE_CAPTURED: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::MOUSE_CAPTURED};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
+                                                AINPUT_SOURCE_MOUSE_RELATIVE)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::MOUSE)
+                                                       .x(100)
+                                                       .y(200)
+                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 100)
+                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::TOUCHPAD: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHPAD};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::TOUCHPAD_CAPTURED: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHPAD_CAPTURED};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHPAD)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+                                                       .x(100)
+                                                       .y(200)
+                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 1)
+                                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 2))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::ROTARY_ENCODER: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::ROTARY_ENCODER};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL,
+                                                AINPUT_SOURCE_ROTARY_ENCODER)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
+                                                       .axis(AMOTION_EVENT_AXIS_SCROLL, 10)
+                                                       .axis(AMOTION_EVENT_AXIS_VSCROLL, 10))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::STYLUS_DIRECT: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_DIRECT};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                STYLUS | TOUCHSCREEN)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::STYLUS_INDIRECT: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_INDIRECT};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                STYLUS | TOUCHSCREEN | AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::STYLUS_FUSED: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_FUSED};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                AINPUT_SOURCE_BLUETOOTH_STYLUS | TOUCHSCREEN)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::TOUCH_NAVIGATION: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCH_NAVIGATION};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
+                                                AINPUT_SOURCE_TOUCH_NAVIGATION)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+                                                       .x(100)
+                                                       .y(200))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::TOUCHSCREEN: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHSCREEN};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(POINTER_1_DOWN, TOUCHSCREEN)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+                                                       .x(100)
+                                                       .y(200))
+                                      .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER)
+                                                       .x(300)
+                                                       .y(400))
+                                      .build()));
+            break;
+        }
+
+        case InputDeviceUsageSource::TRACKBALL: {
+            std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TRACKBALL};
+            ASSERT_EQ(srcs,
+                      getUsageSourcesForMotionArgs(
+                              MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL,
+                                                AINPUT_SOURCE_TRACKBALL)
+                                      .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
+                                                       .axis(AMOTION_EVENT_AXIS_VSCROLL, 100)
+                                                       .axis(AMOTION_EVENT_AXIS_HSCROLL, 200))
+                                      .build()));
+            break;
+        }
+    }
+}
+
+INSTANTIATE_TEST_SUITE_P(InputDeviceMetricsSourceDeviceClassificationTest,
+                         DeviceClassificationFixture,
+                         ::testing::ValuesIn(ALL_USAGE_SOURCES.begin(), ALL_USAGE_SOURCES.end()),
+                         [](const testing::TestParamInfo<InputDeviceUsageSource>& testParamInfo) {
+                             return ftl::enum_string(testParamInfo.param);
+                         });
+
+TEST(InputDeviceMetricsSourceDeviceClassificationTest, MixedClassificationTouchscreenStylus) {
+    std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHSCREEN,
+                                          InputDeviceUsageSource::STYLUS_DIRECT};
+    ASSERT_EQ(srcs,
+              getUsageSourcesForMotionArgs(
+                      MotionArgsBuilder(POINTER_1_DOWN, TOUCHSCREEN | STYLUS)
+                              .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(100).y(200))
+                              .pointer(PointerBuilder(/*id=*/2, ToolType::STYLUS).x(300).y(400))
+                              .build()));
+}
+
+} // namespace android