248 lines
10 KiB
C++
248 lines
10 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
#include <optional>
|
|
#define LOG_TAG "InputDispatcher"
|
|
#define ATRACE_TAG ATRACE_TAG_INPUT
|
|
|
|
#define INDENT " "
|
|
#define INDENT2 " "
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <android-base/stringprintf.h>
|
|
#include <binder/Binder.h>
|
|
#include <ftl/enum.h>
|
|
#include <gui/WindowInfo.h>
|
|
#include <unordered_set>
|
|
|
|
#include "DebugConfig.h"
|
|
#include "FocusResolver.h"
|
|
|
|
using android::gui::FocusRequest;
|
|
using android::gui::WindowInfoHandle;
|
|
|
|
namespace android::inputdispatcher {
|
|
|
|
template <typename T>
|
|
struct SpHash {
|
|
size_t operator()(const sp<T>& k) const { return std::hash<T*>()(k.get()); }
|
|
};
|
|
|
|
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::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<WindowInfoHandle>>& windows) {
|
|
std::string removeFocusReason;
|
|
|
|
const std::optional<FocusRequest> request = getFocusRequest(displayId);
|
|
const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
|
|
|
|
// Find the next focused token based on the latest FocusRequest. If the requested focus window
|
|
// cannot be focused, focus will be removed.
|
|
if (request) {
|
|
sp<IBinder> requestedFocus = request->token;
|
|
sp<WindowInfoHandle> resolvedFocusWindow;
|
|
Focusability result = getResolvedFocusWindow(requestedFocus, windows, resolvedFocusWindow);
|
|
if (result == Focusability::OK && resolvedFocusWindow->getToken() == currentFocus) {
|
|
return std::nullopt;
|
|
}
|
|
const Focusability previousResult = mLastFocusResultByDisplay[displayId];
|
|
mLastFocusResultByDisplay[displayId] = result;
|
|
if (result == Focusability::OK) {
|
|
LOG_ALWAYS_FATAL_IF(!resolvedFocusWindow,
|
|
"Focused window should be non-null when result is OK!");
|
|
return updateFocusedWindow(displayId,
|
|
"Window became focusable. Previous reason: " +
|
|
ftl::enum_string(previousResult),
|
|
resolvedFocusWindow->getToken(),
|
|
resolvedFocusWindow->getName());
|
|
}
|
|
removeFocusReason = ftl::enum_string(result);
|
|
}
|
|
|
|
// 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<WindowInfoHandle>>& windows) {
|
|
const int32_t displayId = request.displayId;
|
|
const sp<IBinder> currentFocus = getFocusedWindowToken(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;
|
|
}
|
|
|
|
sp<WindowInfoHandle> resolvedFocusWindow;
|
|
Focusability result = getResolvedFocusWindow(request.token, windows, resolvedFocusWindow);
|
|
// 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) {
|
|
LOG_ALWAYS_FATAL_IF(!resolvedFocusWindow,
|
|
"Focused window should be non-null when result is OK!");
|
|
return updateFocusedWindow(displayId, "setFocusedWindow", resolvedFocusWindow->getToken(),
|
|
resolvedFocusWindow->getName());
|
|
}
|
|
|
|
// 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 " + ftl::enum_string(result),
|
|
nullptr);
|
|
}
|
|
|
|
FocusResolver::Focusability FocusResolver::getResolvedFocusWindow(
|
|
const sp<IBinder>& token, const std::vector<sp<WindowInfoHandle>>& windows,
|
|
sp<WindowInfoHandle>& outFocusableWindow) {
|
|
sp<IBinder> curFocusCandidate = token;
|
|
bool focusedWindowFound = false;
|
|
|
|
// Keep track of all windows reached to prevent a cyclical transferFocus request.
|
|
std::unordered_set<sp<IBinder>, SpHash<IBinder>> tokensReached;
|
|
|
|
while (curFocusCandidate != nullptr && tokensReached.count(curFocusCandidate) == 0) {
|
|
tokensReached.emplace(curFocusCandidate);
|
|
Focusability result = isTokenFocusable(curFocusCandidate, windows, outFocusableWindow);
|
|
if (result == Focusability::OK) {
|
|
LOG_ALWAYS_FATAL_IF(!outFocusableWindow,
|
|
"Focused window should be non-null when result is OK!");
|
|
focusedWindowFound = true;
|
|
// outFocusableWindow has been updated by isTokenFocusable to contain
|
|
// the window info for curFocusCandidate. See if we can grant focus
|
|
// to the token that it wants to transfer its focus to.
|
|
curFocusCandidate = outFocusableWindow->getInfo()->focusTransferTarget;
|
|
}
|
|
|
|
// If the initial token is not focusable, return early with the failed result.
|
|
if (!focusedWindowFound) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return focusedWindowFound ? Focusability::OK : Focusability::NO_WINDOW;
|
|
}
|
|
|
|
FocusResolver::Focusability FocusResolver::isTokenFocusable(
|
|
const sp<IBinder>& token, const std::vector<sp<WindowInfoHandle>>& windows,
|
|
sp<WindowInfoHandle>& outFocusableWindow) {
|
|
bool allWindowsAreFocusable = true;
|
|
bool windowFound = false;
|
|
sp<WindowInfoHandle> visibleWindowHandle = nullptr;
|
|
for (const sp<WindowInfoHandle>& window : windows) {
|
|
if (window->getToken() != token) {
|
|
continue;
|
|
}
|
|
windowFound = true;
|
|
if (!window->getInfo()->inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE)) {
|
|
// Check if at least a single window is visible.
|
|
visibleWindowHandle = window;
|
|
}
|
|
if (window->getInfo()->inputConfig.test(gui::WindowInfo::InputConfig::NOT_FOCUSABLE)) {
|
|
// Check if all windows with the window token are focusable.
|
|
allWindowsAreFocusable = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!windowFound) {
|
|
return Focusability::NO_WINDOW;
|
|
}
|
|
if (!allWindowsAreFocusable) {
|
|
return Focusability::NOT_FOCUSABLE;
|
|
}
|
|
if (!visibleWindowHandle) {
|
|
return Focusability::NOT_VISIBLE;
|
|
}
|
|
|
|
// Only set the outFoundWindow if the window can be focused
|
|
outFocusableWindow = visibleWindowHandle;
|
|
return Focusability::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 (mFocusRequestByDisplay.empty()) {
|
|
return dump + INDENT "FocusRequests: <none>\n";
|
|
}
|
|
|
|
dump += INDENT "FocusRequests:\n";
|
|
for (const auto& [displayId, request] : mFocusRequestByDisplay) {
|
|
auto it = mLastFocusResultByDisplay.find(displayId);
|
|
std::string result =
|
|
it != mLastFocusResultByDisplay.end() ? ftl::enum_string(it->second) : "";
|
|
dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s' result='%s'\n",
|
|
displayId, request.windowName.c_str(), result.c_str());
|
|
}
|
|
return dump;
|
|
}
|
|
|
|
void FocusResolver::displayRemoved(int32_t displayId) {
|
|
mFocusRequestByDisplay.erase(displayId);
|
|
mLastFocusResultByDisplay.erase(displayId);
|
|
}
|
|
|
|
} // namespace android::inputdispatcher
|