diff options
| -rw-r--r-- | services/inputflinger/dispatcher/FocusResolver.cpp | 172 | ||||
| -rw-r--r-- | services/inputflinger/dispatcher/FocusResolver.h | 32 | ||||
| -rw-r--r-- | services/inputflinger/tests/FocusResolver_test.cpp | 119 |
3 files changed, 211 insertions, 112 deletions
diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp index ee6b38b93f..2db8c13a67 100644 --- a/services/inputflinger/dispatcher/FocusResolver.cpp +++ b/services/inputflinger/dispatcher/FocusResolver.cpp @@ -40,110 +40,102 @@ sp<IBinder> FocusResolver::getFocusedWindowToken(int32_t displayId) const { return it != mFocusedWindowTokenByDisplay.end() ? it->second.second : nullptr; } -std::optional<FocusRequest> FocusResolver::getPendingRequest(int32_t displayId) { - auto it = mPendingFocusRequests.find(displayId); - return it != mPendingFocusRequests.end() ? std::make_optional<>(it->second) : std::nullopt; +std::optional<FocusRequest> FocusResolver::getFocusRequest(int32_t displayId) { + auto it = mFocusRequestByDisplay.find(displayId); + return it != mFocusRequestByDisplay.end() ? std::make_optional<>(it->second) : std::nullopt; } +/** + * 'setInputWindows' is called when the window properties change. Here we will check whether the + * currently focused window can remain focused. If the currently focused window remains eligible + * for focus ('isTokenFocusable' returns OK), then we will continue to grant it focus otherwise + * we will check if the previous focus request is eligible to receive focus. + */ std::optional<FocusResolver::FocusChanges> FocusResolver::setInputWindows( int32_t displayId, const std::vector<sp<InputWindowHandle>>& windows) { - // If the current focused window becomes unfocusable, remove focus. - sp<IBinder> currentFocus = getFocusedWindowToken(displayId); + std::string removeFocusReason; + + // Check if the currently focused window is still focusable. + const sp<IBinder> currentFocus = getFocusedWindowToken(displayId); if (currentFocus) { - FocusResult result = isTokenFocusable(currentFocus, windows); - if (result != FocusResult::OK) { - return updateFocusedWindow(displayId, NamedEnum::string(result), nullptr); + Focusability result = isTokenFocusable(currentFocus, windows); + if (result == Focusability::OK) { + return std::nullopt; + } + removeFocusReason = NamedEnum::string(result); + } + + // We don't have a focused window or the currently focused window is no longer focusable. Check + // to see if we can grant focus to the window that previously requested focus. + const std::optional<FocusRequest> request = getFocusRequest(displayId); + if (request) { + sp<IBinder> requestedFocus = request->token; + const Focusability result = isTokenFocusable(requestedFocus, windows); + const Focusability previousResult = mLastFocusResultByDisplay[displayId]; + mLastFocusResultByDisplay[displayId] = result; + if (result == Focusability::OK) { + return updateFocusedWindow(displayId, + "Window became focusable. Previous reason: " + + NamedEnum::string(previousResult), + requestedFocus, request->windowName); } } - // Check if any pending focus requests can be resolved. - std::optional<FocusRequest> pendingRequest = getPendingRequest(displayId); - if (!pendingRequest) { - return std::nullopt; - } - - sp<IBinder> requestedFocus = pendingRequest->token; - std::string windowName = pendingRequest->windowName; - if (currentFocus == requestedFocus) { - ALOGD_IF(DEBUG_FOCUS, - "setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused", - windowName.c_str(), displayId); - mPendingFocusRequests.erase(displayId); - return std::nullopt; - } - - FocusResult result = isTokenFocusable(requestedFocus, windows); - // If the window from the pending request is now visible, provide it focus. - if (result == FocusResult::OK) { - mPendingFocusRequests.erase(displayId); - return updateFocusedWindow(displayId, "Window became visible", requestedFocus, windowName); - } - - if (result != FocusResult::NOT_VISIBLE) { - // Drop the request if we are unable to change the focus for a reason other than visibility. - ALOGW("Focus request %s on display %" PRId32 " ignored, reason:%s", windowName.c_str(), - displayId, NamedEnum::string(result).c_str()); - mPendingFocusRequests.erase(displayId); - } - return std::nullopt; + // Focused window is no longer focusable and we don't have a suitable focus request to grant. + // Remove focus if needed. + return updateFocusedWindow(displayId, removeFocusReason, nullptr); } std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow( const FocusRequest& request, const std::vector<sp<InputWindowHandle>>& windows) { const int32_t displayId = request.displayId; const sp<IBinder> currentFocus = getFocusedWindowToken(displayId); - if (request.focusedToken && currentFocus != request.focusedToken) { - ALOGW("setFocusedWindow %s on display %" PRId32 - " ignored, reason: focusedToken %s is not focused", - request.windowName.c_str(), displayId, request.focusedWindowName.c_str()); - return std::nullopt; - } - - std::optional<FocusRequest> pendingRequest = getPendingRequest(displayId); - if (pendingRequest) { - ALOGW("Pending focus request %s on display %" PRId32 - " ignored, reason:replaced by new request", - pendingRequest->windowName.c_str(), displayId); - - // clear any pending focus requests - mPendingFocusRequests.erase(displayId); - } - if (currentFocus == request.token) { ALOGD_IF(DEBUG_FOCUS, - "setFocusedWindow %s on display %" PRId32 " ignored, reason:already focused", + "setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused", request.windowName.c_str(), displayId); return std::nullopt; } - FocusResult result = isTokenFocusable(request.token, windows); - if (result == FocusResult::OK) { - std::string reason = - (request.focusedToken) ? "setFocusedWindow with focus check" : "setFocusedWindow"; - return updateFocusedWindow(displayId, reason, request.token, request.windowName); + // Handle conditional focus requests, i.e. requests that have a focused token. These requests + // are not persistent. If the window is no longer focusable, we expect focus to go back to the + // previously focused window. + if (request.focusedToken) { + if (currentFocus != request.focusedToken) { + ALOGW("setFocusedWindow %s on display %" PRId32 + " ignored, reason: focusedToken %s is not focused", + request.windowName.c_str(), displayId, request.focusedWindowName.c_str()); + return std::nullopt; + } + Focusability result = isTokenFocusable(request.token, windows); + if (result == Focusability::OK) { + return updateFocusedWindow(displayId, "setFocusedWindow with focus check", + request.token, request.windowName); + } + ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason: %s", + request.windowName.c_str(), displayId, NamedEnum::string(result).c_str()); + return std::nullopt; } - if (result == FocusResult::NOT_VISIBLE) { - // The requested window is not currently visible. Wait for the window to become visible - // and then provide it focus. This is to handle situations where a user action triggers - // a new window to appear. We want to be able to queue any key events after the user - // action and deliver it to the newly focused window. In order for this to happen, we - // take focus from the currently focused window so key events can be queued. - ALOGD_IF(DEBUG_FOCUS, - "setFocusedWindow %s on display %" PRId32 - " pending, reason: window is not visible", - request.windowName.c_str(), displayId); - mPendingFocusRequests[displayId] = request; - return updateFocusedWindow(displayId, "Waiting for window to be visible", nullptr); - } else { - ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason:%s", - request.windowName.c_str(), displayId, NamedEnum::string(result).c_str()); + Focusability result = isTokenFocusable(request.token, windows); + // Update focus request. The focus resolver will always try to handle this request if there is + // no focused window on the display. + mFocusRequestByDisplay[displayId] = request; + mLastFocusResultByDisplay[displayId] = result; + + if (result == Focusability::OK) { + return updateFocusedWindow(displayId, "setFocusedWindow", request.token, + request.windowName); } - return std::nullopt; + // The requested window is not currently focusable. Wait for the window to become focusable + // but remove focus from the current window so that input events can go into a pending queue + // and be sent to the window when it becomes focused. + return updateFocusedWindow(displayId, "Waiting for window because " + NamedEnum::string(result), + nullptr); } -FocusResolver::FocusResult FocusResolver::isTokenFocusable( +FocusResolver::Focusability FocusResolver::isTokenFocusable( const sp<IBinder>& token, const std::vector<sp<InputWindowHandle>>& windows) { bool allWindowsAreFocusable = true; bool visibleWindowFound = false; @@ -165,16 +157,16 @@ FocusResolver::FocusResult FocusResolver::isTokenFocusable( } if (!windowFound) { - return FocusResult::NO_WINDOW; + return Focusability::NO_WINDOW; } if (!allWindowsAreFocusable) { - return FocusResult::NOT_FOCUSABLE; + return Focusability::NOT_FOCUSABLE; } if (!visibleWindowFound) { - return FocusResult::NOT_VISIBLE; + return Focusability::NOT_VISIBLE; } - return FocusResult::OK; + return Focusability::OK; } std::optional<FocusResolver::FocusChanges> FocusResolver::updateFocusedWindow( @@ -209,15 +201,17 @@ std::string FocusResolver::dumpFocusedWindows() const { std::string FocusResolver::dump() const { std::string dump = dumpFocusedWindows(); - - if (mPendingFocusRequests.empty()) { - return dump + INDENT "PendingFocusRequests: <none>\n"; + if (mFocusRequestByDisplay.empty()) { + return dump + INDENT "FocusRequests: <none>\n"; } - dump += INDENT "PendingFocusRequests:\n"; - for (const auto& [displayId, request] : mPendingFocusRequests) { - dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s'\n", displayId, - request.windowName.c_str()); + dump += INDENT "FocusRequests:\n"; + for (const auto& [displayId, request] : mFocusRequestByDisplay) { + auto it = mLastFocusResultByDisplay.find(displayId); + std::string result = + it != mLastFocusResultByDisplay.end() ? NamedEnum::string(it->second) : ""; + dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s' result='%s'\n", + displayId, request.windowName.c_str(), result.c_str()); } return dump; } diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h index e067ad9f37..dc5eeebb4e 100644 --- a/services/inputflinger/dispatcher/FocusResolver.h +++ b/services/inputflinger/dispatcher/FocusResolver.h @@ -34,11 +34,18 @@ namespace android::inputdispatcher { // is visible with the same token and all window handles with the same token are focusable. // See FocusResolver::isTokenFocusable // -// Focus request - Request will be granted if the window is focusable. If the window is not -// visible, then the request is kept in a pending state and granted when it becomes visible. -// If window becomes not focusable, or another request comes in, the pending request is dropped. +// Focus request - Request will be granted if the window is focusable. If it's not +// focusable, then the request is persisted and granted when it becomes focusable. The currently +// focused window will lose focus and any pending keys will be added to a queue so it can be sent +// to the window when it gets focus. +// +// Condition focus request - Request with a focus token specified. Request will be granted if the +// window is focusable and the focus token is the currently focused. Otherwise, the request is +// dropped. Conditional focus requests are not persisted. The window will lose focus and go back +// to the focus token if it becomes not focusable. // // Window handle updates - Focus is lost when the currently focused window becomes not focusable. +// If the previous focus request is focusable, then we will try to grant that window focus. class FocusResolver { public: // Returns the focused window token on the specified display. @@ -61,7 +68,7 @@ public: std::string dump() const; private: - enum class FocusResult { + enum class Focusability { OK, NO_WINDOW, NOT_FOCUSABLE, @@ -77,8 +84,8 @@ private: // we expect the focusability of the windows to match since its hard to reason why one window // can receive focus events and the other cannot when both are backed by the same input channel. // - static FocusResult isTokenFocusable(const sp<IBinder>& token, - const std::vector<sp<InputWindowHandle>>& windows); + static Focusability isTokenFocusable(const sp<IBinder>& token, + const std::vector<sp<InputWindowHandle>>& windows); // Focus tracking for keys, trackball, etc. A window token can be associated with one or // more InputWindowHandles. If a window is mirrored, the window and its mirror will share @@ -87,15 +94,18 @@ private: typedef std::pair<std::string /* name */, sp<IBinder>> NamedToken; std::unordered_map<int32_t /* displayId */, NamedToken> mFocusedWindowTokenByDisplay; - // This map will store a single pending focus request per display that cannot be currently - // processed. This can happen if the window requested to be focused is not currently visible. - // Such a window might become visible later, and these requests would be processed at that time. - std::unordered_map<int32_t /* displayId */, FocusRequest> mPendingFocusRequests; + // This map will store the focus request per display. When the input window handles are updated, + // the current request will be checked to see if it can be processed at that time. + std::unordered_map<int32_t /* displayId */, FocusRequest> mFocusRequestByDisplay; + + // Last reason for not granting a focus request. This is used to add more debug information + // in the event logs. + std::unordered_map<int32_t /* displayId */, Focusability> mLastFocusResultByDisplay; std::optional<FocusResolver::FocusChanges> updateFocusedWindow( int32_t displayId, const std::string& reason, const sp<IBinder>& token, const std::string& tokenName = ""); - std::optional<FocusRequest> getPendingRequest(int32_t displayId); + std::optional<FocusRequest> getFocusRequest(int32_t displayId); }; } // namespace android::inputdispatcher
\ No newline at end of file diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp index ef3dd65d46..17efb5b99f 100644 --- a/services/inputflinger/tests/FocusResolver_test.cpp +++ b/services/inputflinger/tests/FocusResolver_test.cpp @@ -18,6 +18,12 @@ #include "../FocusResolver.h" +#define ASSERT_FOCUS_CHANGE(_changes, _oldFocus, _newFocus) \ + { \ + ASSERT_EQ(_oldFocus, _changes->oldFocus); \ + ASSERT_EQ(_newFocus, _changes->newFocus); \ + } + // atest inputflinger_tests:FocusResolverTest namespace android::inputdispatcher { @@ -56,8 +62,7 @@ TEST(FocusResolverTest, SetFocusedWindow) { FocusResolver focusResolver; std::optional<FocusResolver::FocusChanges> changes = focusResolver.setFocusedWindow(request, windows); - ASSERT_EQ(nullptr, changes->oldFocus); - ASSERT_EQ(focusableWindowToken, changes->newFocus); + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken); ASSERT_EQ(request.displayId, changes->displayId); // invisible window cannot get focused @@ -65,6 +70,7 @@ TEST(FocusResolverTest, SetFocusedWindow) { changes = focusResolver.setFocusedWindow(request, windows); ASSERT_EQ(focusableWindowToken, changes->oldFocus); ASSERT_EQ(nullptr, changes->newFocus); + ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr); // unfocusableWindowToken window cannot get focused request.token = unfocusableWindowToken; @@ -99,19 +105,17 @@ TEST(FocusResolverTest, SetFocusedMirroredWindow) { FocusResolver focusResolver; std::optional<FocusResolver::FocusChanges> changes = focusResolver.setFocusedWindow(request, windows); - ASSERT_EQ(nullptr, changes->oldFocus); - ASSERT_EQ(focusableWindowToken, changes->newFocus); + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken); // mirrored window with one visible window can get focused request.token = invisibleWindowToken; changes = focusResolver.setFocusedWindow(request, windows); - ASSERT_EQ(focusableWindowToken, changes->oldFocus); - ASSERT_EQ(invisibleWindowToken, changes->newFocus); + ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ invisibleWindowToken); // mirrored window with one or more unfocusable window cannot get focused request.token = unfocusableWindowToken; changes = focusResolver.setFocusedWindow(request, windows); - ASSERT_FALSE(changes); + ASSERT_FOCUS_CHANGE(changes, /*from*/ invisibleWindowToken, /*to*/ nullptr); } TEST(FocusResolverTest, SetInputWindows) { @@ -130,11 +134,10 @@ TEST(FocusResolverTest, SetInputWindows) { focusResolver.setFocusedWindow(request, windows); ASSERT_EQ(focusableWindowToken, changes->newFocus); - // Window visibility changes and the window loses focused + // Window visibility changes and the window loses focus window->setVisible(false); changes = focusResolver.setInputWindows(request.displayId, windows); - ASSERT_EQ(nullptr, changes->newFocus); - ASSERT_EQ(focusableWindowToken, changes->oldFocus); + ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr); } TEST(FocusResolverTest, FocusRequestsCanBePending) { @@ -158,8 +161,100 @@ TEST(FocusResolverTest, FocusRequestsCanBePending) { // Window visibility changes and the window gets focused invisibleWindow->setVisible(true); changes = focusResolver.setInputWindows(request.displayId, windows); - ASSERT_EQ(nullptr, changes->oldFocus); - ASSERT_EQ(invisibleWindowToken, changes->newFocus); + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ invisibleWindowToken); +} + +TEST(FocusResolverTest, FocusRequestsArePersistent) { + sp<IBinder> windowToken = new BBinder(); + std::vector<sp<InputWindowHandle>> windows; + + sp<FakeWindowHandle> window = new FakeWindowHandle("Test Window", windowToken, + false /* focusable */, true /* visible */); + windows.push_back(window); + + // non-focusable window cannot get focused + FocusRequest request; + request.displayId = 42; + request.token = windowToken; + FocusResolver focusResolver; + std::optional<FocusResolver::FocusChanges> changes = + focusResolver.setFocusedWindow(request, windows); + ASSERT_FALSE(changes); + + // Focusability changes and the window gets focused + window->setFocusable(true); + changes = focusResolver.setInputWindows(request.displayId, windows); + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); + + // Visibility changes and the window loses focus + window->setVisible(false); + changes = focusResolver.setInputWindows(request.displayId, windows); + ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr); + + // Visibility changes and the window gets focused + window->setVisible(true); + changes = focusResolver.setInputWindows(request.displayId, windows); + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); + + // Window is gone and the window loses focus + changes = focusResolver.setInputWindows(request.displayId, {}); + ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr); + + // Window returns and the window gains focus + changes = focusResolver.setInputWindows(request.displayId, windows); + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); +} + +TEST(FocusResolverTest, ConditionalFocusRequestsAreNotPersistent) { + sp<IBinder> hostWindowToken = new BBinder(); + std::vector<sp<InputWindowHandle>> windows; + + sp<FakeWindowHandle> hostWindow = + new FakeWindowHandle("Host Window", hostWindowToken, true /* focusable */, + true /* visible */); + windows.push_back(hostWindow); + sp<IBinder> embeddedWindowToken = new BBinder(); + sp<FakeWindowHandle> embeddedWindow = + new FakeWindowHandle("Embedded Window", embeddedWindowToken, true /* focusable */, + true /* visible */); + windows.push_back(embeddedWindow); + + FocusRequest request; + request.displayId = 42; + request.token = hostWindowToken; + FocusResolver focusResolver; + std::optional<FocusResolver::FocusChanges> changes = + focusResolver.setFocusedWindow(request, windows); + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ hostWindowToken); + + request.focusedToken = hostWindow->getToken(); + request.token = embeddedWindowToken; + changes = focusResolver.setFocusedWindow(request, windows); + ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken); + + embeddedWindow->setFocusable(false); + changes = focusResolver.setInputWindows(request.displayId, windows); + // The embedded window is no longer focusable, provide focus back to the original focused + // window. + ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken); + + embeddedWindow->setFocusable(true); + changes = focusResolver.setInputWindows(request.displayId, windows); + // The embedded window is focusable again, but we it cannot gain focus unless there is another + // focus request. + ASSERT_FALSE(changes); + + embeddedWindow->setVisible(false); + changes = focusResolver.setFocusedWindow(request, windows); + // If the embedded window is not visible/focusable, then we do not grant it focus and the + // request is dropped. + ASSERT_FALSE(changes); + + embeddedWindow->setVisible(true); + changes = focusResolver.setInputWindows(request.displayId, windows); + // If the embedded window becomes visble/focusable, nothing changes since the request has been + // dropped. + ASSERT_FALSE(changes); } } // namespace android::inputdispatcher |