1/*
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "avoid_area_controller.h"
17
18#include <hitrace_meter.h>
19
20#include "display_manager_service_inner.h"
21#include "window_helper.h"
22#include "window_manager_hilog.h"
23
24namespace OHOS {
25namespace Rosen {
26namespace {
27constexpr HiviewDFX::HiLogLabel LABEL = {LOG_CORE, HILOG_DOMAIN_WINDOW, "AvoidAreaController"};
28}
29
30void AvoidAreaController::UpdateAvoidAreaListener(sptr<WindowNode>& windowNode, bool isRegisterListener)
31{
32    WLOGFE("haveAvoidAreaListener %{public}d", isRegisterListener);
33    if (windowNode == nullptr) {
34        WLOGFE("windowNode is nullptr.");
35        return;
36    }
37    if (isRegisterListener) {
38        avoidAreaListenerNodes_.insert(windowNode);
39    } else {
40        lastUpdatedAvoidArea_.erase(windowNode->GetWindowId());
41        avoidAreaListenerNodes_.erase(windowNode);
42    }
43}
44
45void AvoidAreaController::ProcessWindowChange(const sptr<WindowNode>& windowNode, AvoidControlType avoidType,
46    const std::function<bool(sptr<WindowNode>)>& checkFunc)
47{
48    if (isForbidProcessingWindowChange_) {
49        WLOGI("do not process window change.");
50        return;
51    }
52    if (windowNode == nullptr || windowNode->GetWindowToken() == nullptr) {
53        WLOGE("invalid WindowNode.");
54        return;
55    }
56    switch (avoidType) {
57        case AvoidControlType::AVOID_NODE_ADD:
58        case AvoidControlType::AVOID_NODE_REMOVE:
59            AddOrRemoveOverlayWindowIfNeed(windowNode, avoidType == AvoidControlType::AVOID_NODE_ADD);
60            break;
61        case AvoidControlType::AVOID_NODE_UPDATE:
62            UpdateOverlayWindowIfNeed(windowNode, checkFunc);
63            break;
64        default:
65            break;
66    }
67}
68
69void AvoidAreaController::AddOrRemoveOverlayWindowIfNeed(const sptr<WindowNode>& overlayNode, bool isAdding)
70{
71    WindowGravity windowGravity;
72    uint32_t percent;
73    overlayNode->GetWindowGravity(windowGravity, percent);
74    if (!WindowHelper::IsOverlayWindow(overlayNode->GetWindowType()) ||
75        windowGravity == WindowGravity::WINDOW_GRAVITY_FLOAT) {
76        WLOGE("IsOverlayWindow Failed.");
77        return;
78    }
79    HITRACE_METER(HITRACE_TAG_WINDOW_MANAGER);
80
81    uint32_t overlayId = overlayNode->GetWindowId();
82    bool isRecorded = (overlayWindowMap_.find(overlayId) != overlayWindowMap_.end());
83    if (isAdding == isRecorded) {
84        WLOGE("error occured in overlay. overlayId %{public}u isAdding %{public}d record flag %{public}d",
85            overlayId, isAdding, isRecorded);
86        return;
87    }
88    if (isAdding) {
89        overlayWindowMap_.insert(std::make_pair(overlayId, overlayNode));
90    } else {
91        overlayWindowMap_.erase(overlayId);
92    }
93
94    if (overlayNode->GetWindowType() == WindowType::WINDOW_TYPE_INPUT_METHOD_FLOAT) {
95        AddOrRemoveKeyboard(overlayNode, isAdding);
96        return;
97    }
98
99    for (auto& node : avoidAreaListenerNodes_) {
100        AvoidArea systemAvoidArea = GetAvoidAreaByType(node, AvoidAreaType::TYPE_SYSTEM);
101        UpdateAvoidAreaIfNeed(systemAvoidArea, node, AvoidAreaType::TYPE_SYSTEM);
102    }
103}
104
105void AvoidAreaController::AddOrRemoveKeyboard(const sptr<WindowNode>& keyboardNode, bool isAdding)
106{
107    const uint32_t callingWindowId = keyboardNode->GetCallingWindow();
108    sptr<WindowNode> callingWindow = nullptr;
109    sptr<WindowNode> focusWindow = nullptr;
110    sptr<WindowNode> lastKeyboardAreaUpdatedWindow = nullptr;
111    for (auto window : avoidAreaListenerNodes_) {
112        if (window == nullptr || window->GetWindowToken() == nullptr) {
113            continue;
114        }
115        if (window->GetWindowId() == callingWindowId) {
116            callingWindow = window;
117        }
118        if (window->GetWindowId() == focusedWindow_) {
119            focusWindow = window;
120        }
121        if (window->GetWindowId() == lastSoftInputKeyboardAreaUpdatedWindowId_) {
122            lastKeyboardAreaUpdatedWindow = window;
123        }
124    }
125    if (callingWindow == nullptr) {
126        callingWindow = focusWindow;
127    }
128    if (lastKeyboardAreaUpdatedWindow != nullptr && lastKeyboardAreaUpdatedWindow != callingWindow) {
129        const WindowMode windowMode = lastKeyboardAreaUpdatedWindow->GetWindowMode();
130        if (windowMode == WindowMode::WINDOW_MODE_FULLSCREEN || windowMode == WindowMode::WINDOW_MODE_SPLIT_PRIMARY ||
131            windowMode == WindowMode::WINDOW_MODE_SPLIT_SECONDARY) {
132            auto avoidArea = GetAvoidAreaByType(lastKeyboardAreaUpdatedWindow, AvoidAreaType::TYPE_KEYBOARD);
133            UpdateAvoidAreaIfNeed(avoidArea, lastKeyboardAreaUpdatedWindow, AvoidAreaType::TYPE_KEYBOARD);
134        }
135    }
136    if (callingWindow == nullptr) {
137        WLOGFE("callingWindow: %{public}u is nullptr, focusWindow: %{public}u is nullptr.",
138            callingWindowId, focusedWindow_);
139        return;
140    }
141    const WindowMode callingWindowMode = callingWindow->GetWindowMode();
142    if (callingWindowMode == WindowMode::WINDOW_MODE_FULLSCREEN ||
143        callingWindowMode == WindowMode::WINDOW_MODE_SPLIT_PRIMARY ||
144        callingWindowMode == WindowMode::WINDOW_MODE_SPLIT_SECONDARY) {
145        auto avoidArea = GetAvoidAreaByType(callingWindow, AvoidAreaType::TYPE_KEYBOARD);
146        bool res = UpdateAvoidAreaIfNeed(avoidArea, callingWindow, AvoidAreaType::TYPE_KEYBOARD);
147        if (res) {
148            lastSoftInputKeyboardAreaUpdatedWindowId_ = callingWindow->GetWindowId();
149        }
150        return;
151    }
152    WLOGFE("does not have correct callingWindowMode for input method window");
153}
154
155void AvoidAreaController::UpdateOverlayWindowIfNeed(const sptr<WindowNode>& node,
156    const std::function<bool(sptr<WindowNode>)>& checkFunc)
157{
158    HITRACE_METER(HITRACE_TAG_WINDOW_MANAGER);
159    WindowGravity windowGravity;
160    uint32_t percent;
161    node->GetWindowGravity(windowGravity, percent);
162    if (WindowHelper::IsOverlayWindow(node->GetWindowType()) &&
163        windowGravity == WindowGravity::WINDOW_GRAVITY_BOTTOM) {
164        AvoidAreaType type = WindowHelper::IsSystemBarWindow(node->GetWindowType()) ?
165            AvoidAreaType::TYPE_SYSTEM : AvoidAreaType::TYPE_KEYBOARD;
166        for (auto& appNode : avoidAreaListenerNodes_) {
167            if (checkFunc != nullptr && checkFunc(appNode)) {
168                bool res = UpdateAvoidAreaIfNeed(GetAvoidAreaByType(appNode, type), appNode, type);
169                if (type == AvoidAreaType::TYPE_KEYBOARD && res) {
170                    lastSoftInputKeyboardAreaUpdatedWindowId_ = appNode->GetWindowId();
171                }
172            }
173        }
174    } else {
175        if (avoidAreaListenerNodes_.find(node) == avoidAreaListenerNodes_.end()) {
176            WLOGD("window: %{public}u is not in avoidAreaListenerNodes, don't update avoid area.", node->GetWindowId());
177            return;
178        }
179        uint32_t start = static_cast<uint32_t>(AvoidAreaType::TYPE_SYSTEM);
180        uint32_t end = static_cast<uint32_t>(AvoidAreaType::TYPE_KEYBOARD);
181        for (uint32_t type = start; type <= end; type++) {
182            AvoidArea systemAvoidArea = GetAvoidAreaByType(node, static_cast<AvoidAreaType>(type));
183            bool res = UpdateAvoidAreaIfNeed(systemAvoidArea, node, static_cast<AvoidAreaType>(type));
184            if (res && type == static_cast<uint32_t>(AvoidAreaType::TYPE_KEYBOARD)) {
185                lastSoftInputKeyboardAreaUpdatedWindowId_ = node->GetWindowId();
186            }
187        }
188    }
189}
190
191bool AvoidAreaController::UpdateAvoidAreaIfNeed(const AvoidArea& avoidArea, const sptr<WindowNode>& node,
192    AvoidAreaType avoidAreaType)
193{
194    auto iter = lastUpdatedAvoidArea_.find(node->GetWindowId());
195    bool needUpdate = true;
196    if (iter != lastUpdatedAvoidArea_.end()) {
197        auto avoidAreaIter = iter->second.find(avoidAreaType);
198        if (avoidAreaIter != iter->second.end()) {
199            needUpdate = avoidAreaIter->second != avoidArea;
200        } else {
201            if (avoidArea.isEmptyAvoidArea()) {
202                needUpdate = false;
203            }
204        }
205    } else {
206        if (avoidArea.isEmptyAvoidArea()) {
207            needUpdate = false;
208        }
209    }
210    if (needUpdate) {
211        lastUpdatedAvoidArea_[node->GetWindowId()][avoidAreaType] = avoidArea;
212        node->GetWindowToken()->UpdateAvoidArea(new AvoidArea(avoidArea), avoidAreaType);
213    }
214    return needUpdate;
215}
216
217AvoidPosType AvoidAreaController::CalculateOverlayRect(const sptr<WindowNode>& node,
218    const sptr<WindowNode>& overlayNode, Rect& overlayRect) const
219{
220    if (node->GetWindowId() == overlayNode->GetWindowId()) {
221        WLOGE("overlay not support self. windowId %{public}u", node->GetWindowId());
222        return AvoidPosType::AVOID_POS_UNKNOWN;
223    }
224    const Rect rect = node->GetWindowRect();
225    overlayRect = WindowHelper::GetOverlap(overlayNode->GetWindowRect(), rect, rect.posX_, rect.posY_);
226    return  GetAvoidPosType(rect, overlayRect);
227}
228
229AvoidPosType AvoidAreaController::GetAvoidPosType(const Rect& windowRect, const Rect& overlayRect) const
230{
231    if (windowRect.width_ == 0 || windowRect.height_ == 0) {
232        return AvoidPosType::AVOID_POS_UNKNOWN;
233    }
234    uint32_t centerX = overlayRect.posX_ + (overlayRect.width_ >> 1);
235    uint32_t centerY = overlayRect.posY_ + (overlayRect.height_ >> 1);
236    float res1 = float(centerY) - float(windowRect.height_) / float(windowRect.width_) * float(centerX);
237    float res2 = float(centerY) + float(windowRect.height_) / float(windowRect.width_)  * float(centerX) -
238        float(windowRect.height_);
239    if (res1 < 0) {
240        if (res2 < 0) {
241            return AvoidPosType::AVOID_POS_TOP;
242        }
243        return AvoidPosType::AVOID_POS_RIGHT;
244    }
245    if (res2 < 0) {
246        return AvoidPosType::AVOID_POS_LEFT;
247    }
248    return AvoidPosType::AVOID_POS_BOTTOM;
249}
250
251void AvoidAreaController::SetAvoidAreaRect(AvoidArea& avoidArea, const Rect& rect, AvoidPosType type) const
252{
253    switch (type) {
254        case AvoidPosType::AVOID_POS_TOP : {
255            avoidArea.topRect_ = rect;
256            break;
257        }
258        case AvoidPosType::AVOID_POS_LEFT : {
259            avoidArea.leftRect_ = rect;
260            break;
261        }
262        case AvoidPosType::AVOID_POS_RIGHT : {
263            avoidArea.rightRect_ = rect;
264            break;
265        }
266        case AvoidPosType::AVOID_POS_BOTTOM : {
267            avoidArea.bottomRect_ = rect;
268            break;
269        }
270        default : {
271            WLOGFD("default type: %{public}u", type);
272        }
273    }
274}
275
276AvoidArea AvoidAreaController::GetAvoidAreaByType(const sptr<WindowNode>& node, AvoidAreaType avoidAreaType) const
277{
278    WLOGFD("avoidAreaType: %{public}u", avoidAreaType);
279    if (node == nullptr) {
280        WLOGFE("invalid WindowNode.");
281        return {};
282    }
283    WindowMode windowMode = node->GetWindowMode();
284    if (avoidAreaType != AvoidAreaType::TYPE_KEYBOARD &&
285        windowMode != WindowMode::WINDOW_MODE_FULLSCREEN &&
286        windowMode != WindowMode::WINDOW_MODE_SPLIT_PRIMARY &&
287        windowMode != WindowMode::WINDOW_MODE_SPLIT_SECONDARY) {
288        WLOGI("avoidAreaType: %{public}u, windowMode: %{public}u, return default avoid area.",
289            avoidAreaType, windowMode);
290        return {};
291    }
292    switch (avoidAreaType) {
293        case AvoidAreaType::TYPE_SYSTEM : {
294            return GetAvoidAreaSystemType(node);
295        }
296        case AvoidAreaType::TYPE_KEYBOARD : {
297            return GetAvoidAreaKeyboardType(node);
298        }
299        case AvoidAreaType::TYPE_CUTOUT : {
300            sptr<CutoutInfo> cutoutInfo = DisplayManagerServiceInner::GetInstance().GetCutoutInfo(node->GetDisplayId());
301            if (cutoutInfo == nullptr) {
302                WLOGFE("there is no cutoutInfo");
303                return {};
304            }
305            std::vector<DMRect> cutoutAreas = cutoutInfo->GetBoundingRects();
306            if (cutoutAreas.empty()) {
307                WLOGFE("there is no cutout");
308                return {};
309            }
310            // 0 means the index in the vector.
311            Rect cutoutAreaRect { cutoutAreas[0].posX_, cutoutAreas[0].posY_,
312                cutoutAreas[0].width_, cutoutAreas[0].height_ };
313            auto rect = node->GetWindowRect();
314            Rect overlayRect = WindowHelper::GetOverlap(cutoutAreaRect, rect, rect.posX_, rect.posY_);
315            auto type = GetAvoidPosType(rect, overlayRect);
316            AvoidArea avoidArea;
317            SetAvoidAreaRect(avoidArea, overlayRect, type);
318            return avoidArea;
319        }
320        default : {
321            WLOGFD("cannot find avoidAreaType: %{public}u", avoidAreaType);
322            return {};
323        }
324    }
325}
326
327AvoidArea AvoidAreaController::GetAvoidAreaSystemType(const sptr<WindowNode>& node) const
328{
329    AvoidArea systemAvoidArea;
330    Rect statusBarAvoidArea;
331    AvoidPosType statusBarAvoidPosType = AvoidPosType::AVOID_POS_UNKNOWN;
332    Rect navigationBarAvoidArea;
333    AvoidPosType navigationBarAvoidPosType = AvoidPosType::AVOID_POS_UNKNOWN;
334    for (auto& iter : overlayWindowMap_) {
335        if (iter.second != nullptr) {
336            if (iter.second->GetWindowType() == WindowType::WINDOW_TYPE_STATUS_BAR) {
337                statusBarAvoidPosType = CalculateOverlayRect(node, iter.second, statusBarAvoidArea);
338            }
339            if (iter.second->GetWindowType() == WindowType::WINDOW_TYPE_NAVIGATION_BAR) {
340                navigationBarAvoidPosType = CalculateOverlayRect(node, iter.second, navigationBarAvoidArea);
341            }
342        }
343    }
344    SetAvoidAreaRect(systemAvoidArea, statusBarAvoidArea, statusBarAvoidPosType);
345    SetAvoidAreaRect(systemAvoidArea, navigationBarAvoidArea, navigationBarAvoidPosType);
346    return systemAvoidArea;
347}
348
349AvoidArea AvoidAreaController::GetAvoidAreaKeyboardType(const sptr<WindowNode>& node) const
350{
351    for (auto& iter : overlayWindowMap_) {
352        if (iter.second != nullptr &&
353            iter.second->GetWindowType() == WindowType::WINDOW_TYPE_INPUT_METHOD_FLOAT) {
354            const uint32_t callingWindowId = iter.second->GetCallingWindow();
355            if (callingWindowId != node->GetWindowId() && focusedWindow_ != node->GetWindowId()) {
356                WLOGI("windowId: %{public}u is not focusedWindow: %{public}u or callingWindow: %{public}u",
357                    node->GetWindowId(), focusedWindow_, callingWindowId);
358                continue;
359            }
360            Rect avoidAreaRect { 0, 0, 0, 0 };
361            AvoidPosType avoidPosType = CalculateOverlayRect(node, iter.second, avoidAreaRect);
362            AvoidArea avoidArea;
363            SetAvoidAreaRect(avoidArea, avoidAreaRect, avoidPosType);
364            return avoidArea;
365        }
366    }
367    return {};
368}
369}
370}
371