Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 44293795 authored by Prabir Pradhan's avatar Prabir Pradhan
Browse files

Break down input device usage by uid

When InputDispatcher notifies that there is an interaction between a
device and UIDs, track the usage of the input device by UID.

Since Dispatcher calls the policy from its own thread, we use an
AtomicQueue to hold the requests until we process the usages again from
the Reader thread.

Bug: 275726706
Test: atest inputflinger_tests
Test: statsd_testdrive
Change-Id: Ic4aae4bce7d3779adf23309973b964461bd92e7e
parent 8ede1d12
Loading
Loading
Loading
Loading
+72 −5
Original line number Original line Diff line number Diff line
@@ -27,6 +27,7 @@ namespace android {


using android::base::StringPrintf;
using android::base::StringPrintf;
using std::chrono::nanoseconds;
using std::chrono::nanoseconds;
using std::chrono_literals::operator""ns;


namespace {
namespace {


@@ -72,10 +73,19 @@ class : public InputDeviceMetricsLogger {
                     ftl::enum_string(src).c_str(), durMillis);
                     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,
        util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
                          identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus),
                          identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus),
                          durationMillis, sources, durationsPerSource, /*uids=*/empty,
                          durationMillis, sources, durationsPerSource, uids, durationsPerUid);
                          /*usage_durations_per_uid=*/empty);
    }
    }
} sStatsdLogger;
} sStatsdLogger;


@@ -248,7 +258,11 @@ void InputDeviceMetricsCollector::notifyPointerCaptureChanged(


void InputDeviceMetricsCollector::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
void InputDeviceMetricsCollector::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
                                                          const std::set<int32_t>& uids) {
                                                          const std::set<int32_t>& uids) {
    // TODO: Implement.
    std::set<Uid> typeSafeUids;
    for (auto uid : uids) {
        typeSafeUids.emplace(uid);
    }
    mInteractionsQueue.push(DeviceId{deviceId}, timestamp, typeSafeUids);
}
}


void InputDeviceMetricsCollector::dump(std::string& dump) {
void InputDeviceMetricsCollector::dump(std::string& dump) {
@@ -309,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() {
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;
    std::vector<DeviceId> completedUsageSessions;


    // Process usages for all active session to determine if any sessions have expired.
    for (auto& [deviceId, activeSession] : mActiveUsageSessions) {
    for (auto& [deviceId, activeSession] : mActiveUsageSessions) {
        if (activeSession.checkIfCompletedAt(currentTime)) {
        if (activeSession.checkIfCompletedAt(currentTime)) {
            completedUsageSessions.emplace_back(deviceId);
            completedUsageSessions.emplace_back(deviceId);
        }
        }
    }
    }


    // Close out and log all expired usage sessions.
    for (DeviceId deviceId : completedUsageSessions) {
    for (DeviceId deviceId : completedUsageSessions) {
        const auto infoIt = mLoggedDeviceInfos.find(deviceId);
        const auto infoIt = mLoggedDeviceInfos.find(deviceId);
        LOG_ALWAYS_FATAL_IF(infoIt == mLoggedDeviceInfos.end());
        LOG_ALWAYS_FATAL_IF(infoIt == mLoggedDeviceInfos.end());
@@ -351,6 +381,23 @@ void InputDeviceMetricsCollector::ActiveSession::recordUsage(nanoseconds eventTi
    mDeviceSession.end = eventTime;
    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) {
bool InputDeviceMetricsCollector::ActiveSession::checkIfCompletedAt(nanoseconds timestamp) {
    const auto sessionExpiryTime = timestamp - mUsageSessionTimeout;
    const auto sessionExpiryTime = timestamp - mUsageSessionTimeout;
    std::vector<InputDeviceUsageSource> completedSourceSessionsForDevice;
    std::vector<InputDeviceUsageSource> completedSourceSessionsForDevice;
@@ -365,6 +412,21 @@ bool InputDeviceMetricsCollector::ActiveSession::checkIfCompletedAt(nanoseconds
        mSourceUsageBreakdown.emplace_back(source, session.end - session.start);
        mSourceUsageBreakdown.emplace_back(source, session.end - session.start);
        mActiveSessionsBySource.erase(it);
        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();
    return mActiveSessionsBySource.empty();
}
}


@@ -377,7 +439,12 @@ InputDeviceMetricsCollector::ActiveSession::finishSession() {
    }
    }
    mActiveSessionsBySource.clear();
    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
} // namespace android
+22 −0
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@


#include "InputListener.h"
#include "InputListener.h"
#include "NotifyArgs.h"
#include "NotifyArgs.h"
#include "SyncQueue.h"


#include <ftl/mixins.h>
#include <ftl/mixins.h>
#include <input/InputDevice.h>
#include <input/InputDevice.h>
@@ -98,9 +99,14 @@ public:
    using SourceUsageBreakdown =
    using SourceUsageBreakdown =
            std::vector<std::pair<InputDeviceUsageSource, std::chrono::nanoseconds /*duration*/>>;
            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 {
    struct DeviceUsageReport {
        std::chrono::nanoseconds usageDuration;
        std::chrono::nanoseconds usageDuration;
        SourceUsageBreakdown sourceBreakdown;
        SourceUsageBreakdown sourceBreakdown;
        UidUsageBreakdown uidBreakdown;
    };
    };


    virtual void logInputDeviceUsageReported(const InputDeviceIdentifier&,
    virtual void logInputDeviceUsageReported(const InputDeviceIdentifier&,
@@ -146,13 +152,25 @@ private:
        return std::to_string(ftl::to_underlying(id));
        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;
    std::map<DeviceId, InputDeviceInfo> mLoggedDeviceInfos;


    using Interaction = std::tuple<DeviceId, std::chrono::nanoseconds, std::set<Uid>>;
    SyncQueue<Interaction> mInteractionsQueue;

    class ActiveSession {
    class ActiveSession {
    public:
    public:
        explicit ActiveSession(std::chrono::nanoseconds usageSessionTimeout,
        explicit ActiveSession(std::chrono::nanoseconds usageSessionTimeout,
                               std::chrono::nanoseconds startTime);
                               std::chrono::nanoseconds startTime);
        void recordUsage(std::chrono::nanoseconds eventTime, InputDeviceUsageSource source);
        void recordUsage(std::chrono::nanoseconds eventTime, InputDeviceUsageSource source);
        void recordInteraction(const Interaction&);
        bool checkIfCompletedAt(std::chrono::nanoseconds timestamp);
        bool checkIfCompletedAt(std::chrono::nanoseconds timestamp);
        InputDeviceMetricsLogger::DeviceUsageReport finishSession();
        InputDeviceMetricsLogger::DeviceUsageReport finishSession();


@@ -167,6 +185,9 @@ private:


        std::map<InputDeviceUsageSource, UsageSession> mActiveSessionsBySource{};
        std::map<InputDeviceUsageSource, UsageSession> mActiveSessionsBySource{};
        InputDeviceMetricsLogger::SourceUsageBreakdown mSourceUsageBreakdown{};
        InputDeviceMetricsLogger::SourceUsageBreakdown mSourceUsageBreakdown{};

        std::map<Uid, UsageSession> mActiveSessionsByUid{};
        InputDeviceMetricsLogger::UidUsageBreakdown mUidUsageBreakdown{};
    };
    };


    // The input devices that currently have active usage sessions.
    // The input devices that currently have active usage sessions.
@@ -177,6 +198,7 @@ private:
    using SourceProvider = std::function<std::set<InputDeviceUsageSource>(const InputDeviceInfo&)>;
    using SourceProvider = std::function<std::set<InputDeviceUsageSource>(const InputDeviceInfo&)>;
    void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
    void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
                            const SourceProvider& getSources);
                            const SourceProvider& getSources);
    void onInputDeviceInteraction(const Interaction&);
    void reportCompletedSessions();
    void reportCompletedSessions();
};
};


+150 −2
Original line number Original line Diff line number Diff line
@@ -341,8 +341,9 @@ protected:
    TestInputListener mTestListener;
    TestInputListener mTestListener;
    InputDeviceMetricsCollector mMetricsCollector{mTestListener, *this, USAGE_TIMEOUT};
    InputDeviceMetricsCollector mMetricsCollector{mTestListener, *this, USAGE_TIMEOUT};


    void assertUsageLogged(const InputDeviceIdentifier& identifier, nanoseconds duration,
    void assertUsageLogged(InputDeviceIdentifier identifier, nanoseconds duration,
                           std::optional<SourceUsageBreakdown> sourceBreakdown = {}) {
                           std::optional<SourceUsageBreakdown> sourceBreakdown = {},
                           std::optional<UidUsageBreakdown> uidBreakdown = {}) {
        ASSERT_GE(mLoggedUsageSessions.size(), 1u);
        ASSERT_GE(mLoggedUsageSessions.size(), 1u);
        const auto& [loggedIdentifier, report] = *mLoggedUsageSessions.begin();
        const auto& [loggedIdentifier, report] = *mLoggedUsageSessions.begin();
        ASSERT_EQ(identifier, loggedIdentifier);
        ASSERT_EQ(identifier, loggedIdentifier);
@@ -350,6 +351,9 @@ protected:
        if (sourceBreakdown) {
        if (sourceBreakdown) {
            ASSERT_EQ(sourceBreakdown, report.sourceBreakdown);
            ASSERT_EQ(sourceBreakdown, report.sourceBreakdown);
        }
        }
        if (uidBreakdown) {
            ASSERT_EQ(uidBreakdown, report.uidBreakdown);
        }
        mLoggedUsageSessions.erase(mLoggedUsageSessions.begin());
        mLoggedUsageSessions.erase(mLoggedUsageSessions.begin());
    }
    }


@@ -357,6 +361,8 @@ protected:


    void setCurrentTime(nanoseconds time) { mCurrentTime = time; }
    void setCurrentTime(nanoseconds time) { mCurrentTime = time; }


    nsecs_t currentTime() const { return mCurrentTime.count(); }

    NotifyMotionArgs generateMotionArgs(int32_t deviceId,
    NotifyMotionArgs generateMotionArgs(int32_t deviceId,
                                        uint32_t source = AINPUT_SOURCE_TOUCHSCREEN,
                                        uint32_t source = AINPUT_SOURCE_TOUCHSCREEN,
                                        std::vector<ToolType> toolTypes = {ToolType::FINGER}) {
                                        std::vector<ToolType> toolTypes = {ToolType::FINGER}) {
@@ -622,4 +628,146 @@ TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource_MultiSourceEvent)
    ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
    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
} // namespace android