| /* | 
 |  * 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 |