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

Commit 3aedc6f8 authored by Vishnu Nair's avatar Vishnu Nair Committed by Android (Google) Code Review
Browse files

Merge "InputDispatcher: Refactor focus logic into FocusResolver"

parents 192d5a5e c519ff70
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ filegroup {
        "AnrTracker.cpp",
        "Connection.cpp",
        "Entry.cpp",
        "FocusResolver.cpp",
        "InjectionState.cpp",
        "InputDispatcher.cpp",
        "InputDispatcherFactory.cpp",
+1 −1
Original line number Diff line number Diff line
@@ -102,7 +102,7 @@ std::string DeviceResetEntry::getDescription() const {

// Focus notifications always go to apps, so set the flag POLICY_FLAG_PASS_TO_USER for all entries
FocusEntry::FocusEntry(int32_t id, nsecs_t eventTime, sp<IBinder> connectionToken, bool hasFocus,
                       std::string_view reason)
                       const std::string& reason)
      : EventEntry(id, Type::FOCUS, eventTime, POLICY_FLAG_PASS_TO_USER),
        connectionToken(connectionToken),
        hasFocus(hasFocus),
+2 −2
Original line number Diff line number Diff line
@@ -93,10 +93,10 @@ struct DeviceResetEntry : EventEntry {
struct FocusEntry : EventEntry {
    sp<IBinder> connectionToken;
    bool hasFocus;
    std::string_view reason;
    std::string reason;

    FocusEntry(int32_t id, nsecs_t eventTime, sp<IBinder> connectionToken, bool hasFocus,
               std::string_view reason);
               const std::string& reason);
    std::string getDescription() const override;

    virtual ~FocusEntry();
+225 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "FocusResolver"
#define ATRACE_TAG ATRACE_TAG_INPUT

#define INDENT "  "
#define INDENT2 "    "

// Log debug messages about input focus tracking.
static constexpr bool DEBUG_FOCUS = false;

#include <inttypes.h>

#include <android-base/stringprintf.h>
#include <binder/Binder.h>
#include <input/InputWindow.h>
#include <input/NamedEnum.h>
#include <log/log.h>

#include "FocusResolver.h"

namespace android::inputdispatcher {

sp<IBinder> FocusResolver::getFocusedWindowToken(int32_t displayId) const {
    auto it = mFocusedWindowTokenByDisplay.find(displayId);
    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<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);
        }
    }

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

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",
                 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);
    }

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

FocusResolver::FocusResult FocusResolver::isTokenFocusable(
        const sp<IBinder>& token, const std::vector<sp<InputWindowHandle>>& windows) {
    bool allWindowsAreFocusable = true;
    bool visibleWindowFound = false;
    bool windowFound = false;
    for (const sp<InputWindowHandle>& window : windows) {
        if (window->getToken() != token) {
            continue;
        }
        windowFound = true;
        if (window->getInfo()->visible) {
            // Check if at least a single window is visible.
            visibleWindowFound = true;
        }
        if (!window->getInfo()->focusable) {
            // Check if all windows with the window token are focusable.
            allWindowsAreFocusable = false;
            break;
        }
    }

    if (!windowFound) {
        return FocusResult::NO_WINDOW;
    }
    if (!allWindowsAreFocusable) {
        return FocusResult::NOT_FOCUSABLE;
    }
    if (!visibleWindowFound) {
        return FocusResult::NOT_VISIBLE;
    }

    return FocusResult::OK;
}

std::optional<FocusResolver::FocusChanges> FocusResolver::updateFocusedWindow(
        int32_t displayId, const std::string& reason, const sp<IBinder>& newFocus,
        const std::string& tokenName) {
    sp<IBinder> oldFocus = getFocusedWindowToken(displayId);
    if (newFocus == oldFocus) {
        return std::nullopt;
    }
    if (newFocus) {
        mFocusedWindowTokenByDisplay[displayId] = {tokenName, newFocus};
    } else {
        mFocusedWindowTokenByDisplay.erase(displayId);
    }

    return {{oldFocus, newFocus, displayId, reason}};
}

std::string FocusResolver::dumpFocusedWindows() const {
    if (mFocusedWindowTokenByDisplay.empty()) {
        return INDENT "FocusedWindows: <none>\n";
    }

    std::string dump;
    dump += INDENT "FocusedWindows:\n";
    for (const auto& [displayId, namedToken] : mFocusedWindowTokenByDisplay) {
        dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s'\n", displayId,
                                   namedToken.first.c_str());
    }
    return dump;
}

std::string FocusResolver::dump() const {
    std::string dump = dumpFocusedWindows();

    if (mPendingFocusRequests.empty()) {
        return dump + INDENT "PendingFocusRequests: <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());
    }
    return dump;
}

} // namespace android::inputdispatcher
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <stdint.h>
#include <optional>
#include <unordered_map>

#include <android/FocusRequest.h>
#include <binder/Binder.h>
#include <input/InputWindow.h>

namespace android::inputdispatcher {

// Keeps track of the focused window per display. The class listens to updates from input dispatcher
// and provides focus changes.
//
// Focus Policy
//   Window focusabilty - A window token can be focused if there is at least one window handle that
//   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.
//
//   Window handle updates - Focus is lost when the currently focused window becomes not focusable.
class FocusResolver {
public:
    // Returns the focused window token on the specified display.
    sp<IBinder> getFocusedWindowToken(int32_t displayId) const;

    struct FocusChanges {
        sp<IBinder> oldFocus;
        sp<IBinder> newFocus;
        int32_t displayId;
        std::string reason;
    };
    std::optional<FocusResolver::FocusChanges> setInputWindows(
            int32_t displayId, const std::vector<sp<InputWindowHandle>>& windows);
    std::optional<FocusResolver::FocusChanges> setFocusedWindow(
            const FocusRequest& request, const std::vector<sp<InputWindowHandle>>& windows);

    // exposed for debugging
    bool hasFocusedWindowTokens() const { return !mFocusedWindowTokenByDisplay.empty(); }
    std::string dumpFocusedWindows() const;
    std::string dump() const;

private:
    enum class FocusResult {
        OK,
        NO_WINDOW,
        NOT_FOCUSABLE,
        NOT_VISIBLE,
    };

    // Checks if the window token can be focused on a display. The token can be focused if there is
    // at least one window handle that is visible with the same token and all window handles with
    // the same token are focusable.
    //
    // In the case of mirroring, two windows may share the same window token and their visibility
    // might be different. Example, the mirrored window can cover the window its mirroring. However,
    // 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);

    // 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
    // the same token. Focus is tracked by the token per display and the events are dispatched
    // to the channel associated by this token.
    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;

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

} // namespace android::inputdispatcher
 No newline at end of file
Loading