/* * 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 "../InputDeviceMetricsCollector.h" #include #include #include #include #include #include #include "TestInputListener.h" namespace android { using std::chrono_literals::operator""ns; using std::chrono::nanoseconds; namespace { constexpr auto USAGE_TIMEOUT = 8765309ns; constexpr auto TIME = 999999ns; constexpr int32_t DEVICE_ID = 3; constexpr int32_t DEVICE_ID_2 = 4; constexpr int32_t VID = 0xFEED; constexpr int32_t PID = 0xDEAD; constexpr int32_t VERSION = 0xBEEF; const std::string DEVICE_NAME = "Half Dome"; const std::string LOCATION = "California"; const std::string UNIQUE_ID = "Yosemite"; constexpr uint32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN; constexpr uint32_t STYLUS = AINPUT_SOURCE_STYLUS; InputDeviceIdentifier generateTestIdentifier(int32_t id = DEVICE_ID) { InputDeviceIdentifier identifier; identifier.name = DEVICE_NAME + "_" + std::to_string(id); identifier.location = LOCATION; identifier.uniqueId = UNIQUE_ID; identifier.vendor = VID; identifier.product = PID; identifier.version = VERSION; identifier.bus = BUS_USB; return identifier; } InputDeviceInfo generateTestDeviceInfo(int32_t id = DEVICE_ID, uint32_t sources = TOUCHSCREEN | STYLUS) { auto info = InputDeviceInfo(); info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id), "alias", /*isExternal=*/false, /*hasMic=*/false, ui::LogicalDisplayId::INVALID); info.addSource(sources); return info; } const InputDeviceInfo TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID); const InputDeviceInfo SECOND_TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID_2); std::set uids(std::initializer_list vals) { std::set set; for (const auto val : vals) { set.emplace(val); } return set; } } // namespace // --- InputDeviceMetricsCollectorTest --- class InputDeviceMetricsCollectorTest : public testing::Test, public InputDeviceMetricsLogger { protected: TestInputListener mTestListener; InputDeviceMetricsCollector mMetricsCollector{mTestListener, *this, USAGE_TIMEOUT}; void assertUsageLogged(const InputDeviceInfo& info, nanoseconds duration, std::optional sourceBreakdown = {}, std::optional uidBreakdown = {}) { ASSERT_GE(mLoggedUsageSessions.size(), 1u); const auto& [loggedInfo, report] = *mLoggedUsageSessions.begin(); const auto& i = info.getIdentifier(); ASSERT_EQ(info.getId(), loggedInfo.deviceId); ASSERT_EQ(i.vendor, loggedInfo.vendor); ASSERT_EQ(i.product, loggedInfo.product); ASSERT_EQ(i.version, loggedInfo.version); ASSERT_EQ(i.bus, loggedInfo.bus); ASSERT_EQ(info.getUsiVersion().has_value(), loggedInfo.isUsiStylus); ASSERT_EQ(duration, report.usageDuration); if (sourceBreakdown) { ASSERT_EQ(sourceBreakdown, report.sourceBreakdown); } if (uidBreakdown) { ASSERT_EQ(uidBreakdown, report.uidBreakdown); } mLoggedUsageSessions.erase(mLoggedUsageSessions.begin()); } void assertUsageNotLogged() { ASSERT_TRUE(mLoggedUsageSessions.empty()); } void setCurrentTime(nanoseconds time) { mCurrentTime = time; } nsecs_t currentTime() const { return mCurrentTime.count(); } NotifyMotionArgs generateMotionArgs(int32_t deviceId, uint32_t source = AINPUT_SOURCE_TOUCHSCREEN, std::vector toolTypes = {ToolType::FINGER}) { MotionArgsBuilder builder(AMOTION_EVENT_ACTION_MOVE, source); for (size_t i = 0; i < toolTypes.size(); i++) { builder.pointer(PointerBuilder(i, toolTypes[i])); } return builder.deviceId(deviceId) .eventTime(mCurrentTime.count()) .downTime(mCurrentTime.count()) .build(); } private: std::vector> mLoggedUsageSessions; nanoseconds mCurrentTime{TIME}; nanoseconds getCurrentTime() override { return mCurrentTime; } void logInputDeviceUsageReported(const MetricsDeviceInfo& info, const DeviceUsageReport& report) override { mLoggedUsageSessions.emplace_back(info, report); } }; TEST_F(InputDeviceMetricsCollectorTest, DontLogUsageWhenDeviceNotRegistered) { // Device was used. mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mTestListener.assertNotifyMotionWasCalled(); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Device was used again after the usage timeout expired, but we still don't log usage. setCurrentTime(TIME + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mTestListener.assertNotifyMotionWasCalled(); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } TEST_F(InputDeviceMetricsCollectorTest, DontLogUsageForIgnoredDevices) { constexpr static std::array ignoredDevices{ {INVALID_INPUT_DEVICE_ID, VIRTUAL_KEYBOARD_ID}}; for (int32_t ignoredDeviceId : ignoredDevices) { mMetricsCollector.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(ignoredDeviceId)}}); // Device was used. mMetricsCollector.notifyMotion(generateMotionArgs(ignoredDeviceId)); mTestListener.assertNotifyMotionWasCalled(); mMetricsCollector.notifyDeviceInteraction(ignoredDeviceId, TIME.count(), uids({0, 1, 2})); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Device was used again after the usage timeout expired, but we still don't log usage. setCurrentTime(TIME + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(ignoredDeviceId)); mTestListener.assertNotifyMotionWasCalled(); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Remove the ignored device, and ensure we still don't log usage. mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {}}); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } } TEST_F(InputDeviceMetricsCollectorTest, LogsSingleEventUsageSession) { mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}}); // Device was used. mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Device was used again after the usage timeout. setCurrentTime(TIME + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); // The usage session has zero duration because it consisted of only one event. ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 0ns)); } TEST_F(InputDeviceMetricsCollectorTest, LogsMultipleEventUsageSession) { mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}}); // Device was used. mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Device was used again after some time. setCurrentTime(TIME + 21ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); setCurrentTime(TIME + 42ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); // Device was used again after the usage timeout. setCurrentTime(TIME + 42ns + 2 * USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 42ns)); } TEST_F(InputDeviceMetricsCollectorTest, RemovingDeviceEndsUsageSession) { mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}}); // Device was used. mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Device was used again after some time. setCurrentTime(TIME + 21ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); // The device was removed before the usage timeout expired. setCurrentTime(TIME + 42ns); mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {}}); ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 21ns)); } TEST_F(InputDeviceMetricsCollectorTest, TracksUsageFromDifferentDevicesIndependently) { mMetricsCollector.notifyInputDevicesChanged( {/*id=*/0, {TOUCHSCREEN_STYLUS_INFO, SECOND_TOUCHSCREEN_STYLUS_INFO}}); // Device 1 was used. setCurrentTime(TIME); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); setCurrentTime(TIME + 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Device 2 was used. setCurrentTime(TIME + 200ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2)); setCurrentTime(TIME + 400ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Device 1 was used after its usage timeout expired. Its usage session is reported. setCurrentTime(TIME + 300ns + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 100ns)); // Device 2 was used. setCurrentTime(TIME + 350ns + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Device 1 was used. setCurrentTime(TIME + 500ns + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Device 2 is not used for a while, but Device 1 is used again. setCurrentTime(TIME + 400ns + (2 * USAGE_TIMEOUT)); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); // Since Device 2's usage session ended, its usage should be reported. ASSERT_NO_FATAL_FAILURE( assertUsageLogged(SECOND_TOUCHSCREEN_STYLUS_INFO, 150ns + USAGE_TIMEOUT)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource) { mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}}); InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown; // Use touchscreen. mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN)); setCurrentTime(TIME + 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Use a stylus with the same input device. setCurrentTime(TIME + 200ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, STYLUS, {ToolType::STYLUS})); setCurrentTime(TIME + 400ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, STYLUS, {ToolType::STYLUS})); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Touchscreen was used again after its usage timeout expired. // This should be tracked as a separate usage of the source in the breakdown. setCurrentTime(TIME + 300ns + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::TOUCHSCREEN, 100ns); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Continue stylus and touchscreen usages. setCurrentTime(TIME + 350ns + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, STYLUS, {ToolType::STYLUS})); setCurrentTime(TIME + 450ns + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Touchscreen was used after the stylus's usage timeout expired. // The stylus usage should be tracked in the source breakdown. setCurrentTime(TIME + 400ns + USAGE_TIMEOUT + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN)); expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::STYLUS_DIRECT, 150ns + USAGE_TIMEOUT); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Remove all devices to force the usage session to be logged. setCurrentTime(TIME + 500ns + USAGE_TIMEOUT); mMetricsCollector.notifyInputDevicesChanged({}); expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::TOUCHSCREEN, 100ns + USAGE_TIMEOUT); // Verify that only one usage session was logged for the device, and that session was broken // down by source correctly. ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 400ns + USAGE_TIMEOUT + USAGE_TIMEOUT, expectedSourceBreakdown)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource_TrackSourceByDevice) { mMetricsCollector.notifyInputDevicesChanged( {/*id=*/0, {TOUCHSCREEN_STYLUS_INFO, SECOND_TOUCHSCREEN_STYLUS_INFO}}); InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown1; InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown2; // Use both devices, with different sources. mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN)); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2, STYLUS, {ToolType::STYLUS})); setCurrentTime(TIME + 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN)); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2, STYLUS, {ToolType::STYLUS})); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Remove all devices to force the usage session to be logged. mMetricsCollector.notifyInputDevicesChanged({}); expectedSourceBreakdown1.emplace_back(InputDeviceUsageSource::TOUCHSCREEN, 100ns); expectedSourceBreakdown2.emplace_back(InputDeviceUsageSource::STYLUS_DIRECT, 100ns); ASSERT_NO_FATAL_FAILURE( assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 100ns, expectedSourceBreakdown1)); ASSERT_NO_FATAL_FAILURE( assertUsageLogged(SECOND_TOUCHSCREEN_STYLUS_INFO, 100ns, expectedSourceBreakdown2)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource_MultiSourceEvent) { mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}}); InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown; mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, // {ToolType::STYLUS})); setCurrentTime(TIME + 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, // {ToolType::STYLUS, ToolType::FINGER})); setCurrentTime(TIME + 200ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, // {ToolType::STYLUS, ToolType::FINGER})); setCurrentTime(TIME + 300ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, // {ToolType::FINGER})); setCurrentTime(TIME + 400ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, // {ToolType::FINGER})); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); // Remove all devices to force the usage session to be logged. mMetricsCollector.notifyInputDevicesChanged({}); expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::STYLUS_DIRECT, 200ns); expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::TOUCHSCREEN, 300ns); ASSERT_NO_FATAL_FAILURE( assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 400ns, expectedSourceBreakdown)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } TEST_F(InputDeviceMetricsCollectorTest, UidsNotTrackedWhenThereIsNoActiveSession) { mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}}); // Notify interaction with UIDs before the device is used. mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1})); // Use the device. setCurrentTime(TIME + 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); setCurrentTime(TIME + 200ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); // Notify interaction for the wrong device. mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), uids({42})); // Notify interaction after usage session would have expired. // This interaction should not be tracked. setCurrentTime(TIME + 200ns + USAGE_TIMEOUT); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({2, 3})); // Use the device again, by starting a new usage session. setCurrentTime(TIME + 300ns + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); // The first usage session is logged. static const UidUsageBreakdown emptyBreakdown; ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 100ns, /*sourceBreakdown=*/{}, /*uidBreakdown=*/emptyBreakdown)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid) { mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}}); UidUsageBreakdown expectedUidBreakdown; mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1})); setCurrentTime(TIME + 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2})); setCurrentTime(TIME + 200ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2, 3})); expectedUidBreakdown.emplace_back(1, 200ns); expectedUidBreakdown.emplace_back(2, 100ns); expectedUidBreakdown.emplace_back(3, 0ns); // Remove the device to force the usage session to be logged. mMetricsCollector.notifyInputDevicesChanged({}); ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 200ns, /*sourceBreakdown=*/{}, expectedUidBreakdown)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksMultipleSessionsForUid) { mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}}); UidUsageBreakdown expectedUidBreakdown; mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2})); setCurrentTime(TIME + 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2})); setCurrentTime(TIME + 200ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1})); setCurrentTime(TIME + 300ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 3})); setCurrentTime(TIME + 400ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 3})); setCurrentTime(TIME + 200ns + USAGE_TIMEOUT); expectedUidBreakdown.emplace_back(2, 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({4})); setCurrentTime(TIME + 300ns + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 4})); setCurrentTime(TIME + 400ns + USAGE_TIMEOUT); expectedUidBreakdown.emplace_back(3, 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({2, 3})); setCurrentTime(TIME + 500ns + USAGE_TIMEOUT); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({3})); // Remove the device to force the usage session to be logged. mMetricsCollector.notifyInputDevicesChanged({}); expectedUidBreakdown.emplace_back(1, 300ns + USAGE_TIMEOUT); expectedUidBreakdown.emplace_back(2, 0ns); expectedUidBreakdown.emplace_back(3, 100ns); expectedUidBreakdown.emplace_back(4, 100ns); ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 500ns + USAGE_TIMEOUT, /*sourceBreakdown=*/{}, expectedUidBreakdown)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksUidsByDevice) { mMetricsCollector.notifyInputDevicesChanged( {/*id=*/0, {TOUCHSCREEN_STYLUS_INFO, SECOND_TOUCHSCREEN_STYLUS_INFO}}); UidUsageBreakdown expectedUidBreakdown1; UidUsageBreakdown expectedUidBreakdown2; mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2})); setCurrentTime(TIME + 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), uids({1, 3})); setCurrentTime(TIME + 200ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2})); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2)); mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), uids({1, 3})); setCurrentTime(TIME + 200ns + USAGE_TIMEOUT); expectedUidBreakdown1.emplace_back(1, 200ns); expectedUidBreakdown1.emplace_back(2, 200ns); expectedUidBreakdown2.emplace_back(1, 100ns); expectedUidBreakdown2.emplace_back(3, 100ns); mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID)); ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 200ns, /*sourceBreakdown=*/{}, expectedUidBreakdown1)); ASSERT_NO_FATAL_FAILURE(assertUsageLogged(SECOND_TOUCHSCREEN_STYLUS_INFO, 100ns, /*sourceBreakdown=*/{}, expectedUidBreakdown2)); ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } } // namespace android