Log InputDeviceUsageReported atom from the new metrics collector
Bug: 275726706
Test: atest inputflinger_tests
Test: statsd_testdrive
Change-Id: I3da4beefe161891ca9188e70ff58114d46801902
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 1a40fdb..ffb2cb1 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -68,6 +68,9 @@
* while conforming to the filename limitations.
*/
std::string getCanonicalName() const;
+
+ bool operator==(const InputDeviceIdentifier&) const = default;
+ bool operator!=(const InputDeviceIdentifier&) const = default;
};
/* Types of input device sensors. Keep sync with core/java/android/hardware/Sensor.java */
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 02bc201..0ca6fa3 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -88,6 +88,20 @@
}
/**
+ * Convert map keys to string. The keys of the map should be integral type.
+ */
+template <typename K, typename V>
+std::string dumpMapKeys(const std::map<K, V>& map,
+ std::string (*keyToString)(const K&) = constToString) {
+ std::string out;
+ for (const auto& [k, _] : map) {
+ out += out.empty() ? "{" : ", ";
+ out += keyToString(k);
+ }
+ return out.empty() ? "{}" : (out + "}");
+}
+
+/**
* Convert a vector to a string. The values of the vector should be of a type supported by
* constToString.
*/
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index c6089c8..50f336b 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -17,52 +17,202 @@
#define LOG_TAG "InputDeviceMetricsCollector"
#include "InputDeviceMetricsCollector.h"
+#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
+#include <linux/input.h>
+#include <statslog.h>
+
namespace android {
+using android::base::StringPrintf;
+using std::chrono::nanoseconds;
+
+namespace {
+
+constexpr nanoseconds DEFAULT_USAGE_SESSION_TIMEOUT = std::chrono::seconds(5);
+
+/**
+ * Log debug messages about metrics events logged to statsd.
+ * Enable this via "adb shell setprop log.tag.InputDeviceMetricsCollector DEBUG" (requires restart)
+ */
+const bool DEBUG = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
+
+int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus) {
+ switch (linuxBus) {
+ case BUS_USB:
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USB;
+ case BUS_BLUETOOTH:
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__BLUETOOTH;
+ default:
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__OTHER;
+ }
+}
+
+class : public InputDeviceMetricsLogger {
+ nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); }
+
+ void logInputDeviceUsageReported(const InputDeviceIdentifier& identifier,
+ nanoseconds sessionDuration) override {
+ const int32_t durationMillis =
+ std::chrono::duration_cast<std::chrono::milliseconds>(sessionDuration).count();
+ const static std::vector<int32_t> empty;
+
+ ALOGD_IF(DEBUG, "Usage session reported for device: %s", identifier.name.c_str());
+ ALOGD_IF(DEBUG, " Total duration: %dms", durationMillis);
+
+ util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
+ identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus),
+ durationMillis, /*usage_sources=*/empty,
+ /*usage_durations_per_source=*/empty, /*uids=*/empty,
+ /*usage_durations_per_uid=*/empty);
+ }
+} sStatsdLogger;
+
+bool isIgnoredInputDeviceId(int32_t deviceId) {
+ switch (deviceId) {
+ case INVALID_INPUT_DEVICE_ID:
+ case VIRTUAL_KEYBOARD_ID:
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace
+
InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener)
- : mNextListener(listener){};
+ : InputDeviceMetricsCollector(listener, sStatsdLogger, DEFAULT_USAGE_SESSION_TIMEOUT) {}
+
+InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener,
+ InputDeviceMetricsLogger& logger,
+ nanoseconds usageSessionTimeout)
+ : mNextListener(listener), mLogger(logger), mUsageSessionTimeout(usageSessionTimeout) {}
void InputDeviceMetricsCollector::notifyInputDevicesChanged(
const NotifyInputDevicesChangedArgs& args) {
+ processUsages();
+ onInputDevicesChanged(args.inputDeviceInfos);
mNextListener.notify(args);
}
void InputDeviceMetricsCollector::notifyConfigurationChanged(
const NotifyConfigurationChangedArgs& args) {
+ processUsages();
mNextListener.notify(args);
}
void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
+ processUsages();
+ onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime));
+
mNextListener.notify(args);
}
void InputDeviceMetricsCollector::notifyMotion(const NotifyMotionArgs& args) {
+ processUsages();
+ onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime));
+
mNextListener.notify(args);
}
void InputDeviceMetricsCollector::notifySwitch(const NotifySwitchArgs& args) {
+ processUsages();
mNextListener.notify(args);
}
void InputDeviceMetricsCollector::notifySensor(const NotifySensorArgs& args) {
+ processUsages();
mNextListener.notify(args);
}
void InputDeviceMetricsCollector::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+ processUsages();
mNextListener.notify(args);
}
void InputDeviceMetricsCollector::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+ processUsages();
mNextListener.notify(args);
}
void InputDeviceMetricsCollector::notifyPointerCaptureChanged(
const NotifyPointerCaptureChangedArgs& args) {
+ processUsages();
mNextListener.notify(args);
}
void InputDeviceMetricsCollector::dump(std::string& dump) {
dump += "InputDeviceMetricsCollector:\n";
+
+ dump += " Logged device IDs: " + dumpMapKeys(mLoggedDeviceInfos, &toString) + "\n";
+ dump += " Devices with active usage sessions: " +
+ dumpMapKeys(mActiveUsageSessions, &toString) + "\n";
+}
+
+void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) {
+ std::map<DeviceId, InputDeviceIdentifier> newDeviceIds;
+
+ for (const InputDeviceInfo& info : infos) {
+ if (isIgnoredInputDeviceId(info.getId())) {
+ continue;
+ }
+ newDeviceIds.emplace(info.getId(), info.getIdentifier());
+ }
+
+ for (auto [deviceId, identifier] : mLoggedDeviceInfos) {
+ if (newDeviceIds.count(deviceId) != 0) {
+ continue;
+ }
+ onInputDeviceRemoved(deviceId, identifier);
+ }
+
+ std::swap(newDeviceIds, mLoggedDeviceInfos);
+}
+
+void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId,
+ const InputDeviceIdentifier& identifier) {
+ // Report usage for that device if there is an active session.
+ auto it = mActiveUsageSessions.find(deviceId);
+ if (it != mActiveUsageSessions.end()) {
+ mLogger.logInputDeviceUsageReported(identifier, it->second.end - it->second.start);
+ mActiveUsageSessions.erase(it);
+ }
+ // We don't remove this from mLoggedDeviceInfos because it will be updated in
+ // onInputDevicesChanged().
+}
+
+void InputDeviceMetricsCollector::onInputDeviceUsage(DeviceId deviceId, nanoseconds eventTime) {
+ if (mLoggedDeviceInfos.count(deviceId) == 0) {
+ // Do not track usage for devices that are not logged.
+ return;
+ }
+
+ auto [it, inserted] = mActiveUsageSessions.try_emplace(deviceId, eventTime, eventTime);
+ if (!inserted) {
+ it->second.end = eventTime;
+ }
+}
+
+void InputDeviceMetricsCollector::processUsages() {
+ const auto usageSessionExpiryTime = mLogger.getCurrentTime() - mUsageSessionTimeout;
+
+ std::vector<DeviceId> completedUsageSessions;
+
+ for (const auto& [deviceId, usageSession] : mActiveUsageSessions) {
+ if (usageSession.end <= usageSessionExpiryTime) {
+ completedUsageSessions.emplace_back(deviceId);
+ }
+ }
+
+ for (DeviceId deviceId : completedUsageSessions) {
+ const auto it = mLoggedDeviceInfos.find(deviceId);
+ LOG_ALWAYS_FATAL_IF(it == mLoggedDeviceInfos.end());
+
+ const auto& session = mActiveUsageSessions[deviceId];
+ mLogger.logInputDeviceUsageReported(it->second, session.end - session.start);
+
+ mActiveUsageSessions.erase(deviceId);
+ }
}
} // namespace android
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index c959075..c9daa92 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -18,6 +18,12 @@
#include "InputListener.h"
+#include <ftl/mixins.h>
+#include <input/InputDevice.h>
+#include <chrono>
+#include <map>
+#include <vector>
+
namespace android {
/**
@@ -34,11 +40,24 @@
virtual void dump(std::string& dump) = 0;
};
+/** The logging interface for the metrics collector, injected for testing. */
+class InputDeviceMetricsLogger {
+public:
+ virtual std::chrono::nanoseconds getCurrentTime() = 0;
+ virtual void logInputDeviceUsageReported(const InputDeviceIdentifier&,
+ std::chrono::nanoseconds duration) = 0;
+ virtual ~InputDeviceMetricsLogger() = default;
+};
+
class InputDeviceMetricsCollector : public InputDeviceMetricsCollectorInterface {
public:
explicit InputDeviceMetricsCollector(InputListenerInterface& listener);
~InputDeviceMetricsCollector() override = default;
+ // Test constructor
+ InputDeviceMetricsCollector(InputListenerInterface& listener, InputDeviceMetricsLogger& logger,
+ std::chrono::nanoseconds usageSessionTimeout);
+
void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
void notifyKey(const NotifyKeyArgs& args) override;
@@ -53,6 +72,32 @@
private:
InputListenerInterface& mNextListener;
+ InputDeviceMetricsLogger& mLogger;
+ const std::chrono::nanoseconds mUsageSessionTimeout;
+
+ // Type-safe wrapper for input device id.
+ struct DeviceId : ftl::Constructible<DeviceId, std::int32_t>,
+ ftl::Equatable<DeviceId>,
+ ftl::Orderable<DeviceId> {
+ using Constructible::Constructible;
+ };
+ static std::string toString(const DeviceId& id) {
+ return std::to_string(ftl::to_underlying(id));
+ }
+
+ std::map<DeviceId, InputDeviceIdentifier> mLoggedDeviceInfos;
+
+ struct UsageSession {
+ std::chrono::nanoseconds start;
+ std::chrono::nanoseconds end;
+ };
+ // The input devices that currently have active usage sessions.
+ std::map<DeviceId, UsageSession> mActiveUsageSessions;
+
+ void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos);
+ void onInputDeviceRemoved(DeviceId deviceId, const InputDeviceIdentifier& identifier);
+ void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime);
+ void processUsages();
};
} // namespace android
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 52277ff..ec67a1d 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -47,6 +47,7 @@
"FocusResolver_test.cpp",
"GestureConverter_test.cpp",
"HardwareStateConverter_test.cpp",
+ "InputDeviceMetricsCollector_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
new file mode 100644
index 0000000..d82e426
--- /dev/null
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -0,0 +1,264 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <gui/constants.h>
+#include <linux/input.h>
+#include <array>
+#include <tuple>
+
+#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";
+
+InputDeviceIdentifier getIdentifier(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) {
+ auto info = InputDeviceInfo();
+ info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, getIdentifier(id), "alias",
+ /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+ info.addSource(AINPUT_SOURCE_TOUCHSCREEN);
+ return info;
+}
+
+} // namespace
+
+// --- InputDeviceMetricsCollectorTest ---
+
+class InputDeviceMetricsCollectorTest : public testing::Test, InputDeviceMetricsLogger {
+protected:
+ TestInputListener mTestListener;
+ InputDeviceMetricsCollector mMetricsCollector{mTestListener, *this, USAGE_TIMEOUT};
+
+ void assertUsageLogged(InputDeviceIdentifier identifier, nanoseconds duration) {
+ ASSERT_GE(mLoggedUsageSessions.size(), 1u);
+ const auto& session = *mLoggedUsageSessions.begin();
+ ASSERT_EQ(identifier, std::get<InputDeviceIdentifier>(session));
+ ASSERT_EQ(duration, std::get<nanoseconds>(session));
+ mLoggedUsageSessions.erase(mLoggedUsageSessions.begin());
+ }
+
+ void assertUsageNotLogged() { ASSERT_TRUE(mLoggedUsageSessions.empty()); }
+
+ void setCurrentTime(nanoseconds time) { mCurrentTime = time; }
+
+ NotifyMotionArgs generateMotionArgs(int32_t deviceId) {
+ PointerProperties pointerProperties{};
+ pointerProperties.id = 0;
+ pointerProperties.toolType = ToolType::FINGER;
+
+ PointerCoords pointerCoords{};
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, 100);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, 200);
+
+ return {/*id=*/0,
+ mCurrentTime.count(),
+ /*readTime=*/0,
+ deviceId,
+ AINPUT_SOURCE_TOUCHSCREEN,
+ /*displayId=*/0,
+ POLICY_FLAG_PASS_TO_USER,
+ AMOTION_EVENT_ACTION_MOVE,
+ /*actionButton=*/0,
+ /*flags=*/0,
+ AMETA_NONE,
+ /*buttonState=*/0,
+ MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ /*pointerCount=*/1,
+ &pointerProperties,
+ &pointerCoords,
+ /*xPrecision=*/0,
+ /*yPrecision=*/0,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ mCurrentTime.count(),
+ /*videoFrames=*/{}};
+ }
+
+private:
+ std::vector<std::tuple<InputDeviceIdentifier, nanoseconds>> mLoggedUsageSessions;
+ nanoseconds mCurrentTime{TIME};
+
+ nanoseconds getCurrentTime() override { return mCurrentTime; }
+
+ void logInputDeviceUsageReported(const InputDeviceIdentifier& identifier,
+ nanoseconds duration) override {
+ mLoggedUsageSessions.emplace_back(identifier, duration);
+ }
+};
+
+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<int32_t, 2> 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();
+ 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, {generateTestDeviceInfo()}});
+
+ // 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(getIdentifier(), 0ns));
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, LogsMultipleEventUsageSession) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+
+ // 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(getIdentifier(), 42ns));
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, RemovingDeviceEndsUsageSession) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+
+ // 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(getIdentifier(), 21ns));
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, TracksUsageFromDifferentDevicesIndependently) {
+ mMetricsCollector.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(), generateTestDeviceInfo(DEVICE_ID_2)}});
+
+ // 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(getIdentifier(DEVICE_ID), 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(getIdentifier(DEVICE_ID_2), 150ns + USAGE_TIMEOUT));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+} // namespace android