diff options
| author | 2023-05-08 19:37:44 +0000 | |
|---|---|---|
| committer | 2023-06-08 17:18:50 +0000 | |
| commit | 8ede1d12a521b263c6d5a98a7ed21f548bbdfbd7 (patch) | |
| tree | 8a585493b9bad9ef78d97ce5ab90968021cc4ffb | |
| parent | 30f0d321c66ba665d9214ac71ca348b745f58093 (diff) | |
Notify MetricsCollector of device interaction from Dispatcher
InputDispatcher will notify the metrics collector whenever an
interaction occurs between an input device and a UID through its policy.
Bug: 275726706
Test: atest inputflinger_tests
Test: statsd_testdrive
Change-Id: Ic62b351263542577328db00c7feb5ff6042f6fe0
10 files changed, 194 insertions, 19 deletions
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp index 3e25cc3d66..397b1cd7c2 100644 --- a/services/inputflinger/InputDeviceMetricsCollector.cpp +++ b/services/inputflinger/InputDeviceMetricsCollector.cpp @@ -246,6 +246,11 @@ void InputDeviceMetricsCollector::notifyPointerCaptureChanged( mNextListener.notify(args); } +void InputDeviceMetricsCollector::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, + const std::set<int32_t>& uids) { + // TODO: Implement. +} + void InputDeviceMetricsCollector::dump(std::string& dump) { dump += "InputDeviceMetricsCollector:\n"; diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h index e2e79e45ee..55876988f8 100644 --- a/services/inputflinger/InputDeviceMetricsCollector.h +++ b/services/inputflinger/InputDeviceMetricsCollector.h @@ -33,11 +33,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). */ @@ -121,6 +127,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: 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 fbbb38835a..20543290df 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; @@ -3408,38 +3409,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) { @@ -3454,7 +3466,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 6b22f2f24f..ae365cd03f 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/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 6eb63bab17..4d7513837a 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); @@ -5486,6 +5505,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 |