FocusResolver: Clean up focus requests and results when a display is removed

Prevents FocusResolver from leaking focus requests and results as
displays are added and removed.

Test: atest inputflinger_tests
Fixes: 188008394
Change-Id: I635b9becf91056fd540ab9d9546dd67a09324fc6
diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp
index 2db8c13..fb19435 100644
--- a/services/inputflinger/dispatcher/FocusResolver.cpp
+++ b/services/inputflinger/dispatcher/FocusResolver.cpp
@@ -216,4 +216,9 @@
     return dump;
 }
 
+void FocusResolver::displayRemoved(int32_t displayId) {
+    mFocusRequestByDisplay.erase(displayId);
+    mLastFocusResultByDisplay.erase(displayId);
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h
index dc5eeeb..afe16b3 100644
--- a/services/inputflinger/dispatcher/FocusResolver.h
+++ b/services/inputflinger/dispatcher/FocusResolver.h
@@ -62,6 +62,9 @@
     std::optional<FocusResolver::FocusChanges> setFocusedWindow(
             const FocusRequest& request, const std::vector<sp<InputWindowHandle>>& windows);
 
+    // Display has been removed from the system, clean up old references.
+    void displayRemoved(int32_t displayId);
+
     // exposed for debugging
     bool hasFocusedWindowTokens() const { return !mFocusedWindowTokenByDisplay.empty(); }
     std::string dumpFocusedWindows() const;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index d2b8739..c0010ab 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -4613,30 +4613,34 @@
     }
     { // acquire lock
         std::scoped_lock _l(mLock);
-
-        std::shared_ptr<InputApplicationHandle> oldFocusedApplicationHandle =
-                getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
-
-        if (sharedPointersEqual(oldFocusedApplicationHandle, inputApplicationHandle)) {
-            return; // This application is already focused. No need to wake up or change anything.
-        }
-
-        // Set the new application handle.
-        if (inputApplicationHandle != nullptr) {
-            mFocusedApplicationHandlesByDisplay[displayId] = inputApplicationHandle;
-        } else {
-            mFocusedApplicationHandlesByDisplay.erase(displayId);
-        }
-
-        // No matter what the old focused application was, stop waiting on it because it is
-        // no longer focused.
-        resetNoFocusedWindowTimeoutLocked();
+        setFocusedApplicationLocked(displayId, inputApplicationHandle);
     } // release lock
 
     // Wake up poll loop since it may need to make new input dispatching choices.
     mLooper->wake();
 }
 
+void InputDispatcher::setFocusedApplicationLocked(
+        int32_t displayId, const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
+    std::shared_ptr<InputApplicationHandle> oldFocusedApplicationHandle =
+            getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
+
+    if (sharedPointersEqual(oldFocusedApplicationHandle, inputApplicationHandle)) {
+        return; // This application is already focused. No need to wake up or change anything.
+    }
+
+    // Set the new application handle.
+    if (inputApplicationHandle != nullptr) {
+        mFocusedApplicationHandlesByDisplay[displayId] = inputApplicationHandle;
+    } else {
+        mFocusedApplicationHandlesByDisplay.erase(displayId);
+    }
+
+    // No matter what the old focused application was, stop waiting on it because it is
+    // no longer focused.
+    resetNoFocusedWindowTimeoutLocked();
+}
+
 /**
  * Sets the focused display, which is responsible for receiving focus-dispatched input events where
  * the display not specified.
@@ -6208,4 +6212,19 @@
     mLock.lock();
 }
 
+void InputDispatcher::displayRemoved(int32_t displayId) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+        // Set an empty list to remove all handles from the specific display.
+        setInputWindowsLocked(/* window handles */ {}, displayId);
+        setFocusedApplicationLocked(displayId, nullptr);
+        // Call focus resolver to clean up stale requests. This must be called after input windows
+        // have been removed for the removed display.
+        mFocusResolver.displayRemoved(displayId);
+    } // release lock
+
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index bb3f3e6..9edf41c 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -139,6 +139,8 @@
 
     std::array<uint8_t, 32> sign(const VerifiedInputEvent& event) const;
 
+    void displayRemoved(int32_t displayId) override;
+
 private:
     enum class DropReason {
         NOT_DROPPED,
@@ -343,6 +345,9 @@
     std::unordered_map<int32_t, TouchState> mTouchStatesByDisplay GUARDED_BY(mLock);
     std::unique_ptr<DragState> mDragState GUARDED_BY(mLock);
 
+    void setFocusedApplicationLocked(
+            int32_t displayId,
+            const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) REQUIRES(mLock);
     // Focused applications.
     std::unordered_map<int32_t, std::shared_ptr<InputApplicationHandle>>
             mFocusedApplicationHandlesByDisplay GUARDED_BY(mLock);
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 7f85e53..43428a0 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -209,6 +209,11 @@
      * Returns true on success.
      */
     virtual bool flushSensor(int deviceId, InputDeviceSensorType sensorType) = 0;
+
+    /**
+     * Called when a display has been removed from the system.
+     */
+    virtual void displayRemoved(int32_t displayId) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index 17efb5b..9051ff1 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -256,5 +256,37 @@
     // dropped.
     ASSERT_FALSE(changes);
 }
+TEST(FocusResolverTest, FocusRequestsAreClearedWhenWindowIsRemoved) {
+    sp<IBinder> windowToken = new BBinder();
+    std::vector<sp<InputWindowHandle>> windows;
+
+    sp<FakeWindowHandle> window = new FakeWindowHandle("Test Window", windowToken,
+                                                       true /* focusable */, true /* visible */);
+    windows.push_back(window);
+
+    FocusRequest request;
+    request.displayId = 42;
+    request.token = windowToken;
+    FocusResolver focusResolver;
+    std::optional<FocusResolver::FocusChanges> changes =
+            focusResolver.setFocusedWindow(request, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
+    ASSERT_EQ(request.displayId, changes->displayId);
+
+    // Start with a focused window
+    window->setFocusable(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
+
+    // When a display is removed, all windows are removed from the display
+    // and our focused window loses focus
+    changes = focusResolver.setInputWindows(request.displayId, {});
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
+    focusResolver.displayRemoved(request.displayId);
+
+    // When a display is readded, the window does not get focus since the request was cleared.
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FALSE(changes);
+}
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index d51acce..77ca12c 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -2688,6 +2688,23 @@
     window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
 }
 
+TEST_F(InputDispatcherTest, DisplayRemoved) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "window", ADISPLAY_ID_DEFAULT);
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+    // window is granted focus.
+    window->setFocusable(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    // When a display is removed window loses focus.
+    mDispatcher->displayRemoved(ADISPLAY_ID_DEFAULT);
+    window->consumeFocusEvent(false);
+}
+
 /**
  * Launch two windows, with different owners. One window (slipperyExitWindow) has Flag::SLIPPERY,
  * and overlaps the other window, slipperyEnterWindow. The window 'slipperyExitWindow' is on top