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

Commit 1dcad985 authored by Vishnu Nair's avatar Vishnu Nair
Browse files

FocusResolver: Persist focus requests

When WM provides a focus request, the request is handled or
kept in a pending state until the window becomes visible. The request
is dropped in all other cases such as the window handle does not have
the focusable flag set. This might lead to cases where a focus request
is dropped as a result of state mismatch or synchronization issues
between wm and sf.

This fix makes the focus request persistent and better aligns with the
previous model where the focus bit was set as part of the window handle.
This means a request will be handled as long as the window is focusable
or replaced by another request. If the window toggles its focusability,
it will lose and gain focus.

Test: atest inputflinger_tests
Test: go/wm-smoke
Test: atest atest FlickerTests
Bug: 176451870
Change-Id: I790a4b69c15f595109118d49167cf9a450e67ec9
parent a8bad0e2
Loading
Loading
Loading
Loading
+83 −89
Original line number Diff line number Diff line
@@ -40,75 +40,56 @@ 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);
    if (currentFocus) {
        FocusResult result = isTokenFocusable(currentFocus, windows);
        if (result != FocusResult::OK) {
            return updateFocusedWindow(displayId, NamedEnum::string(result), nullptr);
        }
    }
    std::string removeFocusReason;

    // Check if any pending focus requests can be resolved.
    std::optional<FocusRequest> pendingRequest = getPendingRequest(displayId);
    if (!pendingRequest) {
    // Check if the currently focused window is still focusable.
    const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
    if (currentFocus) {
        Focusability result = isTokenFocusable(currentFocus, windows);
        if (result == Focusability::OK) {
            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;
        removeFocusReason = NamedEnum::string(result);
    }

    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);
    // 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);
        }

    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",
@@ -116,34 +97,45 @@ std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow(
        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);
        }

    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());
        return std::nullopt;
    }

    return std::nullopt;
    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);
    }

FocusResolver::FocusResult FocusResolver::isTokenFocusable(
    // 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::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;
}
+21 −11
Original line number Diff line number Diff line
@@ -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,7 +84,7 @@ 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,
    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
@@ -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
+107 −12
Original line number Diff line number Diff line
@@ -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