Merge "SF: add IEventThreadCallback to EventThread" into main
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index 7ffdac7..ed3c616 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -48,9 +48,8 @@
 void freeHotCacheEntry(android::MultifileHotCache& entry) {
     if (entry.entryFd != -1) {
         // If we have an fd, then this entry was added to hot cache via INIT or GET
-        // We need to unmap and close the entry
+        // We need to unmap the entry
         munmap(entry.entryBuffer, entry.entrySize);
-        close(entry.entryFd);
     } else {
         // Otherwise, this was added to hot cache during SET, so it was never mapped
         // and fd was only on the deferred thread.
@@ -143,6 +142,7 @@
                 if (result != sizeof(MultifileHeader)) {
                     ALOGE("Error reading MultifileHeader from cache entry (%s): %s",
                           fullPath.c_str(), std::strerror(errno));
+                    close(fd);
                     return;
                 }
 
@@ -152,6 +152,7 @@
                     if (remove(fullPath.c_str()) != 0) {
                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
                     }
+                    close(fd);
                     continue;
                 }
 
@@ -161,6 +162,10 @@
                 // Memory map the file
                 uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
                         mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+
+                // We can close the file now and the mmap will remain
+                close(fd);
+
                 if (mappedEntry == MAP_FAILED) {
                     ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
                     return;
@@ -206,13 +211,11 @@
                     if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
                         ALOGE("INIT Failed to add %u to hot cache", entryHash);
                         munmap(mappedEntry, fileSize);
-                        close(fd);
                         return;
                     }
                 } else {
                     // If we're not keeping it in hot cache, unmap it now
                     munmap(mappedEntry, fileSize);
-                    close(fd);
                 }
             }
             closedir(dir);
@@ -401,9 +404,12 @@
         // Memory map the file
         cacheEntry =
                 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+
+        // We can close the file now and the mmap will remain
+        close(fd);
+
         if (cacheEntry == MAP_FAILED) {
             ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
-            close(fd);
             return 0;
         }
 
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
index dbee13b..1639be6 100644
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -42,6 +42,8 @@
 
     virtual void TearDown() { mMBC.reset(); }
 
+    int getFileDescriptorCount();
+
     std::unique_ptr<TemporaryFile> mTempFile;
     std::unique_ptr<MultifileBlobCache> mMBC;
 };
@@ -216,4 +218,56 @@
     ASSERT_EQ('y', buf[0]);
 }
 
+int MultifileBlobCacheTest::getFileDescriptorCount() {
+    DIR* directory = opendir("/proc/self/fd");
+
+    int fileCount = 0;
+    struct dirent* entry;
+    while ((entry = readdir(directory)) != NULL) {
+        fileCount++;
+        // printf("File: %s\n", entry->d_name);
+    }
+
+    closedir(directory);
+    return fileCount;
+}
+
+TEST_F(MultifileBlobCacheTest, EnsureFileDescriptorsClosed) {
+    // Populate the cache with a bunch of entries
+    size_t kLargeNumberOfEntries = 1024;
+    for (int i = 0; i < kLargeNumberOfEntries; i++) {
+        // printf("Caching: %i", i);
+
+        // Use the index as the key and value
+        mMBC->set(&i, sizeof(i), &i, sizeof(i));
+
+        int result = 0;
+        ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
+        ASSERT_EQ(i, result);
+    }
+
+    // Ensure we don't have a bunch of open fds
+    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Now open it again and ensure we still don't have a bunch of open fds
+    mMBC.reset(
+            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &mTempFile->path[0]));
+
+    // Check after initialization
+    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+
+    for (int i = 0; i < kLargeNumberOfEntries; i++) {
+        int result = 0;
+        ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
+        ASSERT_EQ(i, result);
+    }
+
+    // And again after we've actually used it
+    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+}
+
 } // namespace android
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index 56a3fb4..8e04150 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -64,14 +64,13 @@
 class : public InputDeviceMetricsLogger {
     nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); }
 
-    void logInputDeviceUsageReported(const InputDeviceInfo& info,
+    void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
                                      const DeviceUsageReport& report) override {
         const int32_t durationMillis =
                 std::chrono::duration_cast<std::chrono::milliseconds>(report.usageDuration).count();
         const static std::vector<int32_t> empty;
-        const auto& identifier = info.getIdentifier();
 
-        ALOGD_IF(DEBUG, "Usage session reported for device: %s", identifier.name.c_str());
+        ALOGD_IF(DEBUG, "Usage session reported for device id: %d", info.deviceId);
         ALOGD_IF(DEBUG, "    Total duration: %dms", durationMillis);
         ALOGD_IF(DEBUG, "    Source breakdown:");
 
@@ -96,11 +95,9 @@
             ALOGD_IF(DEBUG, "        - uid: %s\t duration: %dms", uid.toString().c_str(),
                      durMillis);
         }
-        util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
-                          identifier.version,
-                          linuxBusToInputDeviceBusEnum(identifier.bus,
-                                                       info.getUsiVersion().has_value()),
-                          durationMillis, sources, durationsPerSource, uids, durationsPerUid);
+        util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, info.vendor, info.product, info.version,
+                          linuxBusToInputDeviceBusEnum(info.bus, info.isUsiStylus), durationMillis,
+                          sources, durationsPerSource, uids, durationsPerUid);
     }
 } sStatsdLogger;
 
@@ -116,7 +113,7 @@
 
 } // namespace
 
-InputDeviceUsageSource getUsageSourceForKeyArgs(const InputDeviceInfo& info,
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType,
                                                 const NotifyKeyArgs& keyArgs) {
     if (!isFromSource(keyArgs.source, AINPUT_SOURCE_KEYBOARD)) {
         return InputDeviceUsageSource::UNKNOWN;
@@ -132,7 +129,7 @@
         return InputDeviceUsageSource::GAMEPAD;
     }
 
-    if (info.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
+    if (keyboardType == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
         return InputDeviceUsageSource::KEYBOARD;
     }
 
@@ -232,8 +229,8 @@
 
 void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
     reportCompletedSessions();
-    const SourceProvider getSources = [&args](const InputDeviceInfo& info) {
-        return std::set{getUsageSourceForKeyArgs(info, args)};
+    const SourceProvider getSources = [&args](const MetricsDeviceInfo& info) {
+        return std::set{getUsageSourceForKeyArgs(info.keyboardType, args)};
     };
     onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime), getSources);
 
@@ -291,13 +288,23 @@
 }
 
 void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) {
-    std::map<DeviceId, InputDeviceInfo> newDeviceInfos;
+    std::map<DeviceId, MetricsDeviceInfo> newDeviceInfos;
 
     for (const InputDeviceInfo& info : infos) {
         if (isIgnoredInputDeviceId(info.getId())) {
             continue;
         }
-        newDeviceInfos.emplace(info.getId(), info);
+        const auto& i = info.getIdentifier();
+        newDeviceInfos.emplace(info.getId(),
+                               MetricsDeviceInfo{
+                                       .deviceId = info.getId(),
+                                       .vendor = i.vendor,
+                                       .product = i.product,
+                                       .version = i.version,
+                                       .bus = i.bus,
+                                       .isUsiStylus = info.getUsiVersion().has_value(),
+                                       .keyboardType = info.getKeyboardType(),
+                               });
     }
 
     for (auto [deviceId, info] : mLoggedDeviceInfos) {
@@ -311,7 +318,7 @@
 }
 
 void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId,
-                                                       const InputDeviceInfo& info) {
+                                                       const MetricsDeviceInfo& info) {
     auto it = mActiveUsageSessions.find(deviceId);
     if (it == mActiveUsageSessions.end()) {
         return;
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index 1f7c5d9..7775087 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -79,7 +79,7 @@
 };
 
 /** Returns the InputDeviceUsageSource that corresponds to the key event. */
-InputDeviceUsageSource getUsageSourceForKeyArgs(const InputDeviceInfo&, const NotifyKeyArgs&);
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType, const NotifyKeyArgs&);
 
 /** Returns the InputDeviceUsageSources that correspond to the motion event. */
 std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs&);
@@ -110,7 +110,19 @@
         UidUsageBreakdown uidBreakdown;
     };
 
-    virtual void logInputDeviceUsageReported(const InputDeviceInfo&, const DeviceUsageReport&) = 0;
+    // A subset of information from the InputDeviceInfo class that is used for metrics collection,
+    // used to avoid copying and storing all of the fields and strings in InputDeviceInfo.
+    struct MetricsDeviceInfo {
+        int32_t deviceId;
+        int32_t vendor;
+        int32_t product;
+        int32_t version;
+        int32_t bus;
+        bool isUsiStylus;
+        int32_t keyboardType;
+    };
+    virtual void logInputDeviceUsageReported(const MetricsDeviceInfo&,
+                                             const DeviceUsageReport&) = 0;
     virtual ~InputDeviceMetricsLogger() = default;
 };
 
@@ -153,8 +165,9 @@
     }
 
     using Uid = gui::Uid;
+    using MetricsDeviceInfo = InputDeviceMetricsLogger::MetricsDeviceInfo;
 
-    std::map<DeviceId, InputDeviceInfo> mLoggedDeviceInfos;
+    std::map<DeviceId, MetricsDeviceInfo> mLoggedDeviceInfos;
 
     using Interaction = std::tuple<DeviceId, std::chrono::nanoseconds, std::set<Uid>>;
     SyncQueue<Interaction> mInteractionsQueue;
@@ -188,8 +201,9 @@
     std::map<DeviceId, ActiveSession> mActiveUsageSessions;
 
     void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos);
-    void onInputDeviceRemoved(DeviceId deviceId, const InputDeviceInfo& info);
-    using SourceProvider = std::function<std::set<InputDeviceUsageSource>(const InputDeviceInfo&)>;
+    void onInputDeviceRemoved(DeviceId deviceId, const MetricsDeviceInfo& info);
+    using SourceProvider =
+            std::function<std::set<InputDeviceUsageSource>(const MetricsDeviceInfo&)>;
     void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
                             const SourceProvider& getSources);
     void onInputDeviceInteraction(const Interaction&);
diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
index 7ccfaca..fdf9ed1 100644
--- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -66,21 +66,14 @@
 }
 
 InputDeviceInfo generateTestDeviceInfo(int32_t id = DEVICE_ID,
-                                       uint32_t sources = TOUCHSCREEN | STYLUS,
-                                       bool isAlphabetic = false) {
+                                       uint32_t sources = TOUCHSCREEN | STYLUS) {
     auto info = InputDeviceInfo();
     info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id),
                     "alias", /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
     info.addSource(sources);
-    info.setKeyboardType(isAlphabetic ? AINPUT_KEYBOARD_TYPE_ALPHABETIC
-                                      : AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
     return info;
 }
 
-const InputDeviceInfo ALPHABETIC_KEYBOARD_INFO =
-        generateTestDeviceInfo(DEVICE_ID, KEY_SOURCES, /*isAlphabetic=*/true);
-const InputDeviceInfo NON_ALPHABETIC_KEYBOARD_INFO =
-        generateTestDeviceInfo(DEVICE_ID, KEY_SOURCES, /*isAlphabetic=*/false);
 const InputDeviceInfo TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID);
 const InputDeviceInfo SECOND_TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID_2);
 
@@ -106,7 +99,7 @@
     switch (usageSource) {
         case InputDeviceUsageSource::UNKNOWN: {
             ASSERT_EQ(InputDeviceUsageSource::UNKNOWN,
-                      getUsageSourceForKeyArgs(generateTestDeviceInfo(),
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NONE,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, TOUCHSCREEN)
                                                        .build()));
 
@@ -123,7 +116,7 @@
 
         case InputDeviceUsageSource::BUTTONS: {
             ASSERT_EQ(InputDeviceUsageSource::BUTTONS,
-                      getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .keyCode(AKEYCODE_STYLUS_BUTTON_TAIL)
                                                        .build()));
@@ -132,7 +125,7 @@
 
         case InputDeviceUsageSource::KEYBOARD: {
             ASSERT_EQ(InputDeviceUsageSource::KEYBOARD,
-                      getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .build()));
             break;
@@ -140,13 +133,13 @@
 
         case InputDeviceUsageSource::DPAD: {
             ASSERT_EQ(InputDeviceUsageSource::DPAD,
-                      getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .keyCode(AKEYCODE_DPAD_CENTER)
                                                        .build()));
 
             ASSERT_EQ(InputDeviceUsageSource::DPAD,
-                      getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .keyCode(AKEYCODE_DPAD_CENTER)
                                                        .build()));
@@ -155,13 +148,13 @@
 
         case InputDeviceUsageSource::GAMEPAD: {
             ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
-                      getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .keyCode(AKEYCODE_BUTTON_A)
                                                        .build()));
 
             ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
-                      getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
+                      getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
                                                KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
                                                        .keyCode(AKEYCODE_BUTTON_A)
                                                        .build()));
@@ -358,7 +351,13 @@
                            std::optional<UidUsageBreakdown> uidBreakdown = {}) {
         ASSERT_GE(mLoggedUsageSessions.size(), 1u);
         const auto& [loggedInfo, report] = *mLoggedUsageSessions.begin();
-        ASSERT_EQ(info.getIdentifier(), loggedInfo.getIdentifier());
+        const auto& i = info.getIdentifier();
+        ASSERT_EQ(info.getId(), loggedInfo.deviceId);
+        ASSERT_EQ(i.vendor, loggedInfo.vendor);
+        ASSERT_EQ(i.product, loggedInfo.product);
+        ASSERT_EQ(i.version, loggedInfo.version);
+        ASSERT_EQ(i.bus, loggedInfo.bus);
+        ASSERT_EQ(info.getUsiVersion().has_value(), loggedInfo.isUsiStylus);
         ASSERT_EQ(duration, report.usageDuration);
         if (sourceBreakdown) {
             ASSERT_EQ(sourceBreakdown, report.sourceBreakdown);
@@ -389,12 +388,12 @@
     }
 
 private:
-    std::vector<std::tuple<InputDeviceInfo, DeviceUsageReport>> mLoggedUsageSessions;
+    std::vector<std::tuple<MetricsDeviceInfo, DeviceUsageReport>> mLoggedUsageSessions;
     nanoseconds mCurrentTime{TIME};
 
     nanoseconds getCurrentTime() override { return mCurrentTime; }
 
-    void logInputDeviceUsageReported(const InputDeviceInfo& info,
+    void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
                                      const DeviceUsageReport& report) override {
         mLoggedUsageSessions.emplace_back(info, report);
     }
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index 7062a4e..effbfdb 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -56,31 +56,35 @@
         ATRACE_NAME("WindowInfosListenerInvoker::removeWindowInfosListener");
         sp<IBinder> asBinder = IInterface::asBinder(listener);
         asBinder->unlinkToDeath(sp<DeathRecipient>::fromExisting(this));
-        mWindowInfosListeners.erase(asBinder);
+        eraseListenerAndAckMessages(asBinder);
     }});
 }
 
 void WindowInfosListenerInvoker::binderDied(const wp<IBinder>& who) {
     BackgroundExecutor::getInstance().sendCallbacks({[this, who]() {
         ATRACE_NAME("WindowInfosListenerInvoker::binderDied");
-        auto it = mWindowInfosListeners.find(who);
-        int64_t listenerId = it->second.first;
-        mWindowInfosListeners.erase(who);
-
-        std::vector<int64_t> vsyncIds;
-        for (auto& [vsyncId, state] : mUnackedState) {
-            if (std::find(state.unackedListenerIds.begin(), state.unackedListenerIds.end(),
-                          listenerId) != state.unackedListenerIds.end()) {
-                vsyncIds.push_back(vsyncId);
-            }
-        }
-
-        for (int64_t vsyncId : vsyncIds) {
-            ackWindowInfosReceived(vsyncId, listenerId);
-        }
+        eraseListenerAndAckMessages(who);
     }});
 }
 
+void WindowInfosListenerInvoker::eraseListenerAndAckMessages(const wp<IBinder>& binder) {
+    auto it = mWindowInfosListeners.find(binder);
+    int64_t listenerId = it->second.first;
+    mWindowInfosListeners.erase(binder);
+
+    std::vector<int64_t> vsyncIds;
+    for (auto& [vsyncId, state] : mUnackedState) {
+        if (std::find(state.unackedListenerIds.begin(), state.unackedListenerIds.end(),
+                      listenerId) != state.unackedListenerIds.end()) {
+            vsyncIds.push_back(vsyncId);
+        }
+    }
+
+    for (int64_t vsyncId : vsyncIds) {
+        ackWindowInfosReceived(vsyncId, listenerId);
+    }
+}
+
 void WindowInfosListenerInvoker::windowInfosChanged(
         gui::WindowInfosUpdate update, WindowInfosReportedListenerSet reportedListeners,
         bool forceImmediateCall) {
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h
index f36b0ed..261fd0f 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -67,6 +67,7 @@
 
     std::optional<gui::WindowInfosUpdate> mDelayedUpdate;
     WindowInfosReportedListenerSet mReportedListeners;
+    void eraseListenerAndAckMessages(const wp<IBinder>&);
 
     struct UnackedState {
         ftl::SmallVector<int64_t, kStaticCapacity> unackedListenerIds;
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index bfc03aa..d4ab786 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -11,8 +11,8 @@
 flag {
   name: "connected_display"
   namespace: "core_graphics"
-  description: "Controls SurfaceFlinger support for Connected Displays"
-  bug: "278199093"
+  description: "Controls SurfaceFlinger support for Connected Displays in 24Q1"
+  bug: "299486625"
   is_fixed_read_only: true
 }
 
diff --git a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
index c7b845e..cfb047c 100644
--- a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
@@ -245,4 +245,42 @@
     EXPECT_EQ(callCount, 1);
 }
 
+// Test that WindowInfosListenerInvoker#removeWindowInfosListener acks any unacked messages for
+// the removed listener.
+TEST_F(WindowInfosListenerInvokerTest, removeListenerAcks) {
+    // Don't ack in this listener to ensure there's an unacked message when the listener is later
+    // removed.
+    gui::WindowInfosListenerInfo listenerToBeRemovedInfo;
+    auto listenerToBeRemoved = sp<Listener>::make([](const gui::WindowInfosUpdate&) {});
+    mInvoker->addWindowInfosListener(listenerToBeRemoved, &listenerToBeRemovedInfo);
+
+    std::mutex mutex;
+    std::condition_variable cv;
+    int callCount = 0;
+    gui::WindowInfosListenerInfo listenerInfo;
+    mInvoker->addWindowInfosListener(sp<Listener>::make([&](const gui::WindowInfosUpdate& update) {
+                                         std::scoped_lock lock{mutex};
+                                         callCount++;
+                                         cv.notify_one();
+                                         listenerInfo.windowInfosPublisher
+                                                 ->ackWindowInfosReceived(update.vsyncId,
+                                                                          listenerInfo.listenerId);
+                                     }),
+                                     &listenerInfo);
+
+    BackgroundExecutor::getInstance().sendCallbacks(
+            {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+    mInvoker->removeWindowInfosListener(listenerToBeRemoved);
+    BackgroundExecutor::getInstance().sendCallbacks(
+            {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+
+    // Verify that the second listener is called twice. If unacked messages aren't removed when the
+    // first listener is removed, this will fail.
+    {
+        std::unique_lock lock{mutex};
+        cv.wait(lock, [&]() { return callCount == 2; });
+    }
+    EXPECT_EQ(callCount, 2);
+}
+
 } // namespace android