diff options
| author | 2023-06-09 16:16:03 +0000 | |
|---|---|---|
| committer | 2023-06-09 16:16:03 +0000 | |
| commit | e632c7cbd2ee0483b48bd4924aff2ff1375a7b10 (patch) | |
| tree | d78f99b79fc9b97e3ed62712489418953e22bcde | |
| parent | c66c24e84864fcae8d2c728bfefca819e42d0ba6 (diff) | |
| parent | 442937958b2291470148b675ae58c4a44e880e14 (diff) | |
Merge changes from topics "breakdown-usage-by-uid", "notify-device-interaction" into udc-qpr-dev
* changes:
Break down input device usage by uid
Notify MetricsCollector of device interaction from Dispatcher
11 files changed, 437 insertions, 25 deletions
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp index 3e25cc3d66..f92c4f47f1 100644 --- a/services/inputflinger/InputDeviceMetricsCollector.cpp +++ b/services/inputflinger/InputDeviceMetricsCollector.cpp @@ -27,6 +27,7 @@ namespace android { using android::base::StringPrintf; using std::chrono::nanoseconds; +using std::chrono_literals::operator""ns; namespace { @@ -72,10 +73,19 @@ class : public InputDeviceMetricsLogger { ftl::enum_string(src).c_str(), durMillis); } + ALOGD_IF(DEBUG, " Uid breakdown:"); + + std::vector<int32_t> uids; + std::vector<int32_t> durationsPerUid; + for (auto& [uid, dur] : report.uidBreakdown) { + uids.push_back(uid); + int32_t durMillis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count(); + durationsPerUid.push_back(durMillis); + ALOGD_IF(DEBUG, " - uid: %d\t duration: %dms", uid, durMillis); + } util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product, identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus), - durationMillis, sources, durationsPerSource, /*uids=*/empty, - /*usage_durations_per_uid=*/empty); + durationMillis, sources, durationsPerSource, uids, durationsPerUid); } } sStatsdLogger; @@ -246,6 +256,15 @@ void InputDeviceMetricsCollector::notifyPointerCaptureChanged( mNextListener.notify(args); } +void InputDeviceMetricsCollector::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, + const std::set<int32_t>& uids) { + std::set<Uid> typeSafeUids; + for (auto uid : uids) { + typeSafeUids.emplace(uid); + } + mInteractionsQueue.push(DeviceId{deviceId}, timestamp, typeSafeUids); +} + void InputDeviceMetricsCollector::dump(std::string& dump) { dump += "InputDeviceMetricsCollector:\n"; @@ -304,17 +323,33 @@ void InputDeviceMetricsCollector::onInputDeviceUsage(DeviceId deviceId, nanoseco } } +void InputDeviceMetricsCollector::onInputDeviceInteraction(const Interaction& interaction) { + auto activeSessionIt = mActiveUsageSessions.find(std::get<DeviceId>(interaction)); + if (activeSessionIt == mActiveUsageSessions.end()) { + return; + } + + activeSessionIt->second.recordInteraction(interaction); +} + void InputDeviceMetricsCollector::reportCompletedSessions() { - const auto currentTime = mLogger.getCurrentTime(); + // Process all pending interactions. + for (auto interaction = mInteractionsQueue.pop(); interaction; + interaction = mInteractionsQueue.pop()) { + onInputDeviceInteraction(*interaction); + } + const auto currentTime = mLogger.getCurrentTime(); std::vector<DeviceId> completedUsageSessions; + // Process usages for all active session to determine if any sessions have expired. for (auto& [deviceId, activeSession] : mActiveUsageSessions) { if (activeSession.checkIfCompletedAt(currentTime)) { completedUsageSessions.emplace_back(deviceId); } } + // Close out and log all expired usage sessions. for (DeviceId deviceId : completedUsageSessions) { const auto infoIt = mLoggedDeviceInfos.find(deviceId); LOG_ALWAYS_FATAL_IF(infoIt == mLoggedDeviceInfos.end()); @@ -346,6 +381,23 @@ void InputDeviceMetricsCollector::ActiveSession::recordUsage(nanoseconds eventTi mDeviceSession.end = eventTime; } +void InputDeviceMetricsCollector::ActiveSession::recordInteraction(const Interaction& interaction) { + const auto sessionExpiryTime = mDeviceSession.end + mUsageSessionTimeout; + const auto timestamp = std::get<nanoseconds>(interaction); + if (timestamp >= sessionExpiryTime) { + // This interaction occurred after the device's current active session is set to expire. + // Ignore it. + return; + } + + for (Uid uid : std::get<std::set<Uid>>(interaction)) { + auto [activeUidIt, inserted] = mActiveSessionsByUid.try_emplace(uid, timestamp, timestamp); + if (!inserted) { + activeUidIt->second.end = timestamp; + } + } +} + bool InputDeviceMetricsCollector::ActiveSession::checkIfCompletedAt(nanoseconds timestamp) { const auto sessionExpiryTime = timestamp - mUsageSessionTimeout; std::vector<InputDeviceUsageSource> completedSourceSessionsForDevice; @@ -360,6 +412,21 @@ bool InputDeviceMetricsCollector::ActiveSession::checkIfCompletedAt(nanoseconds mSourceUsageBreakdown.emplace_back(source, session.end - session.start); mActiveSessionsBySource.erase(it); } + + std::vector<Uid> completedUidSessionsForDevice; + for (auto& [uid, session] : mActiveSessionsByUid) { + if (session.end <= sessionExpiryTime) { + completedUidSessionsForDevice.emplace_back(uid); + } + } + for (Uid uid : completedUidSessionsForDevice) { + auto it = mActiveSessionsByUid.find(uid); + const auto& [_, session] = *it; + mUidUsageBreakdown.emplace_back(uid, session.end - session.start); + mActiveSessionsByUid.erase(it); + } + + // This active session has expired if there are no more active source sessions tracked. return mActiveSessionsBySource.empty(); } @@ -372,7 +439,12 @@ InputDeviceMetricsCollector::ActiveSession::finishSession() { } mActiveSessionsBySource.clear(); - return {deviceUsageDuration, mSourceUsageBreakdown}; + for (const auto& [uid, uidSession] : mActiveSessionsByUid) { + mUidUsageBreakdown.emplace_back(uid, uidSession.end - uidSession.start); + } + mActiveSessionsByUid.clear(); + + return {deviceUsageDuration, mSourceUsageBreakdown, mUidUsageBreakdown}; } } // namespace android diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h index e2e79e45ee..387786fe10 100644 --- a/services/inputflinger/InputDeviceMetricsCollector.h +++ b/services/inputflinger/InputDeviceMetricsCollector.h @@ -18,6 +18,7 @@ #include "InputListener.h" #include "NotifyArgs.h" +#include "SyncQueue.h" #include <ftl/mixins.h> #include <input/InputDevice.h> @@ -33,11 +34,17 @@ namespace android { /** * Logs metrics about registered input devices and their usages. * - * Not thread safe. Must be called from a single thread. + * All methods in the InputListenerInterface must be called from a single thread. */ class InputDeviceMetricsCollectorInterface : public InputListenerInterface { public: /** + * Notify the metrics collector that there was an input device interaction with apps. + * Called from the InputDispatcher thread. + */ + virtual void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, + const std::set<int32_t>& uids) = 0; + /** * Dump the state of the interaction blocker. * This method may be called on any thread (usually by the input manager on a binder thread). */ @@ -92,9 +99,14 @@ public: using SourceUsageBreakdown = std::vector<std::pair<InputDeviceUsageSource, std::chrono::nanoseconds /*duration*/>>; + // Describes the breakdown of an input device usage session by the UIDs that it interacted with. + using UidUsageBreakdown = + std::vector<std::pair<int32_t /*uid*/, std::chrono::nanoseconds /*duration*/>>; + struct DeviceUsageReport { std::chrono::nanoseconds usageDuration; SourceUsageBreakdown sourceBreakdown; + UidUsageBreakdown uidBreakdown; }; virtual void logInputDeviceUsageReported(const InputDeviceIdentifier&, @@ -121,6 +133,8 @@ public: void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; + void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, + const std::set<int32_t>& uids) override; void dump(std::string& dump) override; private: @@ -138,13 +152,25 @@ private: return std::to_string(ftl::to_underlying(id)); } + // Type-safe wrapper for a UID. + struct Uid : ftl::Constructible<Uid, std::int32_t>, ftl::Equatable<Uid>, ftl::Orderable<Uid> { + using Constructible::Constructible; + }; + static inline std::string toString(const Uid& src) { + return std::to_string(ftl::to_underlying(src)); + } + std::map<DeviceId, InputDeviceInfo> mLoggedDeviceInfos; + using Interaction = std::tuple<DeviceId, std::chrono::nanoseconds, std::set<Uid>>; + SyncQueue<Interaction> mInteractionsQueue; + class ActiveSession { public: explicit ActiveSession(std::chrono::nanoseconds usageSessionTimeout, std::chrono::nanoseconds startTime); void recordUsage(std::chrono::nanoseconds eventTime, InputDeviceUsageSource source); + void recordInteraction(const Interaction&); bool checkIfCompletedAt(std::chrono::nanoseconds timestamp); InputDeviceMetricsLogger::DeviceUsageReport finishSession(); @@ -159,6 +185,9 @@ private: std::map<InputDeviceUsageSource, UsageSession> mActiveSessionsBySource{}; InputDeviceMetricsLogger::SourceUsageBreakdown mSourceUsageBreakdown{}; + + std::map<Uid, UsageSession> mActiveSessionsByUid{}; + InputDeviceMetricsLogger::UidUsageBreakdown mUidUsageBreakdown{}; }; // The input devices that currently have active usage sessions. @@ -169,6 +198,7 @@ private: using SourceProvider = std::function<std::set<InputDeviceUsageSource>(const InputDeviceInfo&)>; void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime, const SourceProvider& getSources); + void onInputDeviceInteraction(const Interaction&); void reportCompletedSessions(); }; diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp index 863b4839be..1a9f47d934 100644 --- a/services/inputflinger/InputManager.cpp +++ b/services/inputflinger/InputManager.cpp @@ -127,6 +127,10 @@ InputProcessorInterface& InputManager::getProcessor() { return *mProcessor; } +InputDeviceMetricsCollectorInterface& InputManager::getMetricsCollector() { + return *mCollector; +} + InputDispatcherInterface& InputManager::getDispatcher() { return *mDispatcher; } diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h index 0f0d8eab49..9dc285f2c0 100644 --- a/services/inputflinger/InputManager.h +++ b/services/inputflinger/InputManager.h @@ -86,6 +86,9 @@ public: /* Gets the input processor. */ virtual InputProcessorInterface& getProcessor() = 0; + /* Gets the metrics collector. */ + virtual InputDeviceMetricsCollectorInterface& getMetricsCollector() = 0; + /* Gets the input dispatcher. */ virtual InputDispatcherInterface& getDispatcher() = 0; @@ -109,6 +112,7 @@ public: InputReaderInterface& getReader() override; InputProcessorInterface& getProcessor() override; + InputDeviceMetricsCollectorInterface& getMetricsCollector() override; InputDispatcherInterface& getDispatcher() override; void monitor() override; void dump(std::string& dump) override; diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index fbe8482ac4..06a7352d3c 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -111,6 +111,9 @@ private: void notifyDropWindow(const sp<IBinder>&, float x, float y) override {} + void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, + const std::set<int32_t>& uids) override {} + InputDispatcherConfiguration mConfig; }; diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index dc9a5fa65c..f18265fe02 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1955,7 +1955,7 @@ void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, ALOGD("dispatchEventToCurrentInputTargets"); } - updateInteractionTokensLocked(*eventEntry, inputTargets); + processInteractionsLocked(*eventEntry, inputTargets); ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true @@ -2830,6 +2830,7 @@ std::optional<InputTarget> InputDispatcher::createInputTargetLocked( } InputTarget inputTarget; inputTarget.inputChannel = inputChannel; + inputTarget.windowHandle = windowHandle; inputTarget.flags = targetFlags; inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor; inputTarget.firstDownTimeInTarget = firstDownTimeInTarget; @@ -3402,38 +3403,49 @@ void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connectio } /** - * This function is purely for debugging. It helps us understand where the user interaction - * was taking place. For example, if user is touching launcher, we will see a log that user - * started interacting with launcher. In that example, the event would go to the wallpaper as well. - * We will see both launcher and wallpaper in that list. - * Once the interaction with a particular set of connections starts, no new logs will be printed - * until the set of interacted connections changes. + * This function is for debugging and metrics collection. It has two roles. * - * The following items are skipped, to reduce the logspam: - * ACTION_OUTSIDE: any windows that are receiving ACTION_OUTSIDE are not logged - * ACTION_UP: any windows that receive ACTION_UP are not logged (for both keys and motions). - * This includes situations like the soft BACK button key. When the user releases (lifts up the - * finger) the back button, then navigation bar will inject KEYCODE_BACK with ACTION_UP. - * Both of those ACTION_UP events would not be logged + * The first role is to log input interaction with windows, which helps determine what the user was + * interacting with. For example, if user is touching launcher, we will see an input_interaction log + * that user started interacting with launcher window, as well as any other window that received + * that gesture, such as the wallpaper or other spy windows. A new input_interaction is only logged + * when the set of tokens that received the event changes. It is not logged again as long as the + * user is interacting with the same windows. + * + * The second role is to track input device activity for metrics collection. For each input event, + * we report the set of UIDs that the input device interacted with to the policy. Unlike for the + * input_interaction logs, the device interaction is reported even when the set of interaction + * tokens do not change. + * + * For these purposes, we do not count ACTION_OUTSIDE, ACTION_UP and ACTION_CANCEL actions as + * interaction. This includes up and cancel events for both keys and motions. */ -void InputDispatcher::updateInteractionTokensLocked(const EventEntry& entry, - const std::vector<InputTarget>& targets) { +void InputDispatcher::processInteractionsLocked(const EventEntry& entry, + const std::vector<InputTarget>& targets) { + int32_t deviceId; + nsecs_t eventTime; // Skip ACTION_UP events, and all events other than keys and motions if (entry.type == EventEntry::Type::KEY) { const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry); if (keyEntry.action == AKEY_EVENT_ACTION_UP) { return; } + deviceId = keyEntry.deviceId; + eventTime = keyEntry.eventTime; } else if (entry.type == EventEntry::Type::MOTION) { const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry); if (motionEntry.action == AMOTION_EVENT_ACTION_UP || - motionEntry.action == AMOTION_EVENT_ACTION_CANCEL) { + motionEntry.action == AMOTION_EVENT_ACTION_CANCEL || + MotionEvent::getActionMasked(motionEntry.action) == AMOTION_EVENT_ACTION_POINTER_UP) { return; } + deviceId = motionEntry.deviceId; + eventTime = motionEntry.eventTime; } else { return; // Not a key or a motion } + std::set<int32_t> interactionUids; std::unordered_set<sp<IBinder>, StrongPointerHash<IBinder>> newConnectionTokens; std::vector<std::shared_ptr<Connection>> newConnections; for (const InputTarget& target : targets) { @@ -3448,7 +3460,18 @@ void InputDispatcher::updateInteractionTokensLocked(const EventEntry& entry, } newConnectionTokens.insert(std::move(token)); newConnections.emplace_back(connection); + if (target.windowHandle) { + interactionUids.emplace(target.windowHandle->getInfo()->ownerUid); + } } + + auto command = [this, deviceId, eventTime, uids = std::move(interactionUids)]() + REQUIRES(mLock) { + scoped_unlock unlock(mLock); + mPolicy.notifyDeviceInteraction(deviceId, eventTime, uids); + }; + postCommandLocked(std::move(command)); + if (newConnectionTokens == mInteractionConnectionTokens) { return; // no change } diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 1468f2c322..20fe0cabc4 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -447,8 +447,8 @@ private: // when switching touch mode state). std::unordered_set<sp<IBinder>, StrongPointerHash<IBinder>> mInteractionConnectionTokens GUARDED_BY(mLock); - void updateInteractionTokensLocked(const EventEntry& entry, - const std::vector<InputTarget>& targets) REQUIRES(mLock); + void processInteractionsLocked(const EventEntry& entry, const std::vector<InputTarget>& targets) + REQUIRES(mLock); // Dispatch inbound events. bool dispatchConfigurationChangedLocked(nsecs_t currentTime, diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h index 7b12f81c4e..3bf8b68f0e 100644 --- a/services/inputflinger/dispatcher/InputTarget.h +++ b/services/inputflinger/dispatcher/InputTarget.h @@ -17,6 +17,7 @@ #pragma once #include <ftl/flags.h> +#include <gui/WindowInfo.h> #include <gui/constants.h> #include <input/InputTransport.h> #include <ui/Transform.h> @@ -114,6 +115,10 @@ struct InputTarget { // Transform per pointerId. ui::Transform pointerTransforms[MAX_POINTERS]; + // The window that this input target is being dispatched to. It is possible for this to be + // null for cases like global monitors. + sp<gui::WindowInfoHandle> windowHandle; + void addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds, const ui::Transform& transform); void setDefaultPointerTransform(const ui::Transform& transform); diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index 5539915694..69caa99957 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -22,6 +22,7 @@ #include <gui/InputApplication.h> #include <input/Input.h> #include <utils/RefBase.h> +#include <set> namespace android { @@ -136,6 +137,10 @@ public: /* Notifies the policy that the drag window has moved over to another window */ virtual void notifyDropWindow(const sp<IBinder>& token, float x, float y) = 0; + + /* Notifies the policy that there was an input device interaction with apps. */ + virtual void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, + const std::set<int32_t>& uids) = 0; }; } // namespace android diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp index e38f88c046..c555d95f75 100644 --- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp +++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp @@ -341,8 +341,9 @@ protected: TestInputListener mTestListener; InputDeviceMetricsCollector mMetricsCollector{mTestListener, *this, USAGE_TIMEOUT}; - void assertUsageLogged(const InputDeviceIdentifier& identifier, nanoseconds duration, - std::optional<SourceUsageBreakdown> sourceBreakdown = {}) { + void assertUsageLogged(InputDeviceIdentifier identifier, nanoseconds duration, + std::optional<SourceUsageBreakdown> sourceBreakdown = {}, + std::optional<UidUsageBreakdown> uidBreakdown = {}) { ASSERT_GE(mLoggedUsageSessions.size(), 1u); const auto& [loggedIdentifier, report] = *mLoggedUsageSessions.begin(); ASSERT_EQ(identifier, loggedIdentifier); @@ -350,6 +351,9 @@ protected: if (sourceBreakdown) { ASSERT_EQ(sourceBreakdown, report.sourceBreakdown); } + if (uidBreakdown) { + ASSERT_EQ(uidBreakdown, report.uidBreakdown); + } mLoggedUsageSessions.erase(mLoggedUsageSessions.begin()); } @@ -357,6 +361,8 @@ protected: 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<ToolType> toolTypes = {ToolType::FINGER}) { @@ -622,4 +628,146 @@ TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource_MultiSourceEvent) ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); } +TEST_F(InputDeviceMetricsCollectorTest, UidsNotTrackedWhenThereIsNoActiveSession) { + mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); + + // 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(getIdentifier(), 100ns, /*sourceBreakdown=*/{}, + /*uidBreakdown=*/emptyBreakdown)); + + ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); +} + +TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid) { + mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); + 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(getIdentifier(), 200ns, /*sourceBreakdown=*/{}, + expectedUidBreakdown)); + + ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); +} + +TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksMultipleSessionsForUid) { + mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}}); + 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(getIdentifier(), 500ns + USAGE_TIMEOUT, + /*sourceBreakdown=*/{}, expectedUidBreakdown)); + + ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); +} + +TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksUidsByDevice) { + mMetricsCollector.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID), generateTestDeviceInfo(DEVICE_ID_2)}}); + 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(getIdentifier(DEVICE_ID), 200ns, + /*sourceBreakdown=*/{}, expectedUidBreakdown1)); + ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(DEVICE_ID_2), 100ns, + /*sourceBreakdown=*/{}, expectedUidBreakdown2)); + + ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged()); +} + } // namespace android diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 905331cc95..92e7f431ab 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -15,6 +15,7 @@ */ #include "../dispatcher/InputDispatcher.h" +#include "../BlockingQueue.h" #include "EventBuilders.h" #include <android-base/properties.h> @@ -59,6 +60,9 @@ static constexpr int32_t SECOND_DEVICE_ID = 2; static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; static constexpr int32_t SECOND_DISPLAY_ID = 1; +// Ensure common actions are interchangeable between keys and motions for convenience. +static_assert(AMOTION_EVENT_ACTION_DOWN == AKEY_EVENT_ACTION_DOWN); +static_assert(AMOTION_EVENT_ACTION_UP == AKEY_EVENT_ACTION_UP); static constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; static constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP; @@ -413,6 +417,14 @@ public: ASSERT_FALSE(mPokedUserActivity) << "Expected user activity not to have been poked"; } + void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set<int32_t> uids) { + ASSERT_EQ(std::make_pair(deviceId, uids), mNotifiedInteractions.popWithTimeout(100ms)); + } + + void assertNotifyDeviceInteractionWasNotCalled() { + ASSERT_FALSE(mNotifiedInteractions.popWithTimeout(10ms)); + } + private: std::mutex mLock; std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock); @@ -438,6 +450,8 @@ private: std::chrono::milliseconds mInterceptKeyTimeout = 0ms; + BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<int32_t /*uid*/>>> mNotifiedInteractions; + // All three ANR-related callbacks behave the same way, so we use this generic function to wait // for a specific container to become non-empty. When the container is non-empty, return the // first entry from the container and erase it. @@ -609,6 +623,11 @@ private: mDropTargetWindowToken = token; } + void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, + const std::set<int32_t>& uids) override { + ASSERT_TRUE(mNotifiedInteractions.emplace(deviceId, uids)); + } + void assertFilterInputEventWasCalledInternal( const std::function<void(const InputEvent&)>& verify) { std::scoped_lock lock(mLock); @@ -5565,6 +5584,105 @@ TEST_F(InputDispatcherTest, TouchSlippingIntoWindowThatDropsTouches) { rightDropTouchesWindow->assertNoEvents(); } +TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithMotions) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 100, 100)); + leftWindow->setOwnerInfo(1, 101); + + sp<FakeWindowHandle> rightSpy = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right spy", ADISPLAY_ID_DEFAULT); + rightSpy->setFrame(Rect(100, 0, 200, 100)); + rightSpy->setOwnerInfo(2, 102); + rightSpy->setSpy(true); + rightSpy->setTrustedOverlay(true); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(100, 0, 200, 100)); + rightWindow->setOwnerInfo(3, 103); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {rightSpy, rightWindow, leftWindow}}}); + + // Touch in the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + ASSERT_NO_FATAL_FAILURE(leftWindow->consumeMotionDown()); + mDispatcher->waitForIdle(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {101})); + + // Touch another finger over the right windows + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50)) + .build()); + ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionDown()); + ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionDown()); + ASSERT_NO_FATAL_FAILURE(leftWindow->consumeMotionMove()); + mDispatcher->waitForIdle(); + ASSERT_NO_FATAL_FAILURE( + mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {101, 102, 103})); + + // Release finger over left window. The UP actions are not treated as device interaction. + // The windows that did not receive the UP pointer will receive MOVE events, but since this + // is part of the UP action, we do not treat this as device interaction. + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50)) + .build()); + ASSERT_NO_FATAL_FAILURE(leftWindow->consumeMotionUp()); + ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionMove()); + ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionMove()); + mDispatcher->waitForIdle(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled()); + + // Move remaining finger + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50)) + .build()); + ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionMove()); + ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionMove()); + mDispatcher->waitForIdle(); + ASSERT_NO_FATAL_FAILURE( + mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {102, 103})); + + // Release all fingers + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50)) + .build()); + ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionUp()); + ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionUp()); + mDispatcher->waitForIdle(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled()); +} + +TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithKeys) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 100, 100)); + window->setOwnerInfo(1, 101); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + setFocusedWindow(window); + ASSERT_NO_FATAL_FAILURE(window->consumeFocusEvent(true)); + + mDispatcher->notifyKey(KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build()); + ASSERT_NO_FATAL_FAILURE(window->consumeKeyDown(ADISPLAY_ID_DEFAULT)); + mDispatcher->waitForIdle(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {101})); + + // The UP actions are not treated as device interaction. + mDispatcher->notifyKey(KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build()); + ASSERT_NO_FATAL_FAILURE(window->consumeKeyUp(ADISPLAY_ID_DEFAULT)); + mDispatcher->waitForIdle(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled()); +} + class InputDispatcherKeyRepeatTest : public InputDispatcherTest { protected: static constexpr nsecs_t KEY_REPEAT_TIMEOUT = 40 * 1000000; // 40 ms |