1/*
2 * Copyright (c) 2022-2023 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 "window_layout_policy_tile.h"
17#include <ability_manager_client.h>
18#include <hitrace_meter.h>
19
20#include "minimize_app.h"
21#include "window_helper.h"
22#include "window_inner_manager.h"
23#include "window_manager_hilog.h"
24#include "window_system_effect.h"
25
26namespace OHOS {
27namespace Rosen {
28namespace {
29constexpr HiviewDFX::HiLogLabel LABEL = {LOG_CORE, HILOG_DOMAIN_WINDOW, "Tile"};
30constexpr uint32_t EDGE_INTERVAL = 48;
31constexpr uint32_t MID_INTERVAL = 24;
32}
33
34WindowLayoutPolicyTile::WindowLayoutPolicyTile(DisplayGroupWindowTree& displayGroupWindowTree)
35    : WindowLayoutPolicy(displayGroupWindowTree)
36{
37}
38
39void WindowLayoutPolicyTile::Launch()
40{
41    for (auto& iter : DisplayGroupInfo::GetInstance().GetAllDisplayRects()) {
42        const auto& displayId = iter.first;
43        /*
44         * Init tile rects and layout tile queue
45         */
46        InitTileRects(displayId);
47        InitTileQueue(displayId);
48        LayoutTileQueue(displayId);
49
50        /*
51         * Layout above and below nodes, it is necessary when display rotatation or size change
52         */
53        auto& displayWindowTree = displayGroupWindowTree_[displayId];
54        LayoutWindowNodesByRootType(*(displayWindowTree[WindowRootNodeType::ABOVE_WINDOW_NODE]));
55        LayoutWindowNodesByRootType(*(displayWindowTree[WindowRootNodeType::BELOW_WINDOW_NODE]));
56        WLOGFD("[Launch TileLayout], displayId: %{public}" PRIu64"", displayId);
57    }
58    WLOGI("[Launch TileLayout Finished]");
59}
60
61uint32_t WindowLayoutPolicyTile::GetMaxTileWinNum(DisplayId displayId) const
62{
63    float virtualPixelRatio = DisplayGroupInfo::GetInstance().GetDisplayVirtualPixelRatio(displayId);
64    constexpr uint32_t half = 2;
65    uint32_t edgeIntervalVp = static_cast<uint32_t>(EDGE_INTERVAL * half * virtualPixelRatio);
66    uint32_t midIntervalVp = static_cast<uint32_t>(MID_INTERVAL * virtualPixelRatio);
67    uint32_t minFloatingW = static_cast<uint32_t>(MIN_FLOATING_WIDTH * virtualPixelRatio);
68    uint32_t drawableW = limitRectMap_[displayId].width_ - edgeIntervalVp + midIntervalVp;
69    return static_cast<uint32_t>(drawableW / (minFloatingW + midIntervalVp));
70}
71
72void WindowLayoutPolicyTile::InitTileRects(DisplayId displayId)
73{
74    // TileLayout don't consider limitRect yet, limitDisplay equals to displayRect
75    const auto& displayRect = DisplayGroupInfo::GetInstance().GetDisplayRect(displayId);
76    if (WindowHelper::IsEmptyRect(displayRect)) {
77        WLOGFE("DisplayRect is empty, displayRect: %{public}" PRIu64"", displayId);
78        return;
79    }
80
81    limitRectMap_[displayId] = displayRect;
82    float virtualPixelRatio = DisplayGroupInfo::GetInstance().GetDisplayVirtualPixelRatio(displayId);
83    uint32_t edgeIntervalVp = static_cast<uint32_t>(EDGE_INTERVAL * virtualPixelRatio);
84    uint32_t midIntervalVp = static_cast<uint32_t>(MID_INTERVAL * virtualPixelRatio);
85
86    constexpr float ratio = DEFAULT_ASPECT_RATIO;
87    const Rect& limitRect = limitRectMap_[displayId];
88    constexpr int half = 2;
89    maxTileWinNumMap_[displayId] = GetMaxTileWinNum(displayId);
90    WLOGFD("set max tile window num %{public}u", maxTileWinNumMap_[displayId]);
91    auto& presetRectsForAllLevel = presetRectsMap_[displayId];
92    presetRectsForAllLevel.clear();
93    uint32_t w = displayRect.width_ * ratio;
94    uint32_t h = displayRect.height_ * ratio;
95    w = w > limitRect.width_ ? limitRect.width_ : w;
96    h = h > limitRect.height_ ? limitRect.height_ : h;
97    int32_t x = limitRect.posX_ + (static_cast<int32_t>(limitRect.width_ - w) / half);
98    int32_t y = limitRect.posY_ + (static_cast<int32_t>(limitRect.height_ - h) / half);
99
100    std::vector<Rect> single = {{ x, y, w, h }};
101    presetRectsForAllLevel.emplace_back(single);
102    for (uint32_t num = 2; num <= maxTileWinNumMap_[displayId]; num++) { // start calc preset with 2 windows
103        w = (limitRect.width_ - edgeIntervalVp * half - midIntervalVp * (num - 1)) / num;
104        std::vector<Rect> curLevel;
105        for (uint32_t i = 0; i < num; i++) {
106            int32_t curX = static_cast<int32_t>(limitRect.posX_ + edgeIntervalVp + i * (w + midIntervalVp));
107            Rect curRect = { curX, y, w, h };
108            WLOGFD("presetRectsForAllLevel: level %{public}u, id %{public}u, tileRect: [%{public}d %{public}d "
109                "%{public}u %{public}u]", num, i, curX, y, w, h);
110            curLevel.emplace_back(curRect);
111        }
112        presetRectsForAllLevel.emplace_back(curLevel);
113    }
114}
115
116void WindowLayoutPolicyTile::InitTileQueue(DisplayId displayId)
117{
118    foregroundNodesMap_[displayId].clear();
119    const auto& appWindowNodes = *(displayGroupWindowTree_[displayId][WindowRootNodeType::APP_WINDOW_NODE]);
120    for (auto& node : appWindowNodes) {
121        if (WindowHelper::IsMainWindow(node->GetWindowType())) {
122            PushBackNodeInTileQueue(node, displayId);
123        }
124    }
125}
126
127bool WindowLayoutPolicyTile::IsTileRectSatisfiedWithSizeLimits(const sptr<WindowNode>& node)
128{
129    if (!WindowHelper::IsMainWindow(node->GetWindowType())) {
130        return true;
131    }
132    const auto& displayId = node->GetDisplayId();
133    auto& foregroundNodes = foregroundNodesMap_[displayId];
134    auto num = foregroundNodes.size();
135    if (num > maxTileWinNumMap_[displayId] || maxTileWinNumMap_[displayId] == 0) {
136        return false;
137    }
138
139    // find if node already exits in foreground nodes map
140    if (IsWindowAlreadyInTileQueue(node)) {
141        return true;
142    }
143
144    UpdateWindowSizeLimits(node);
145    const auto& presetRectsForAllLevel = presetRectsMap_[displayId];
146    Rect tileRect;
147    // if size of foreground nodes is equal to or more than max tile window number
148    if (num == maxTileWinNumMap_[displayId]) {
149        tileRect = *(presetRectsForAllLevel[num - 1].begin());
150    } else {  // if size of foreground nodes is less than max tile window number
151        tileRect = *(presetRectsForAllLevel[num].begin());
152    }
153    WLOGFD("id %{public}u, tileRect: [%{public}d %{public}d %{public}u %{public}u]",
154        node->GetWindowId(), tileRect.posX_, tileRect.posY_, tileRect.width_, tileRect.height_);
155    return WindowHelper::IsRectSatisfiedWithSizeLimits(tileRect, node->GetWindowUpdatedSizeLimits());
156}
157
158void WindowLayoutPolicyTile::PerformWindowLayout(const sptr<WindowNode>& node, WindowUpdateType updateType)
159{
160    HITRACE_METER(HITRACE_TAG_WINDOW_MANAGER);
161    const auto& windowType = node->GetWindowType();
162    const auto& requestRect = node->GetRequestRect();
163    WLOGI("[PerformWindowLayout] windowId: %{public}u, windowType: %{public}u, updateType: %{public}u, requestRect: "
164        "requestRect: [%{public}d, %{public}d, %{public}u, %{public}u]", node->GetWindowId(), windowType, updateType,
165        requestRect.posX_, requestRect.posY_, requestRect.width_, requestRect.height_);
166    FixWindowRectWithinDisplay(node);
167    switch (updateType) {
168        case WindowUpdateType::WINDOW_UPDATE_ADDED: {
169            if (WindowHelper::IsMainWindow(windowType)) {
170                PushBackNodeInTileQueue(node, node->GetDisplayId());
171                LayoutTileQueue(node->GetDisplayId());
172                return;
173            }
174            break;
175        }
176        case WindowUpdateType::WINDOW_UPDATE_REMOVED: {
177            if (WindowHelper::IsMainWindow(windowType)) {
178                RemoveNodeFromTileQueue(node);
179                LayoutTileQueue(node->GetDisplayId());
180            }
181            NotifyClientAndAnimation(node, node->GetRequestRect(), WindowSizeChangeReason::HIDE);
182            return;
183        }
184        default:
185            WLOGFD("Update type is not add or remove");
186    }
187    LayoutWindowNode(node);
188}
189
190void WindowLayoutPolicyTile::LayoutTileQueue(DisplayId displayId)
191{
192    ApplyPresetRectForTileWindows(displayId);
193    for (auto& node : foregroundNodesMap_[displayId]) {
194        LayoutWindowNode(node);
195    }
196}
197
198bool WindowLayoutPolicyTile::IsWindowAlreadyInTileQueue(const sptr<WindowNode>& node)
199{
200    auto& foregroundNodes = foregroundNodesMap_[node->GetDisplayId()];
201    auto iter = std::find_if(foregroundNodes.begin(), foregroundNodes.end(),
202                             [node](sptr<WindowNode> foregroundNode) {
203                                 return foregroundNode->GetWindowId() == node->GetWindowId();
204                             });
205    if (iter != foregroundNodes.end()) {
206        WLOGFD("Window is already in tile queue, windowId: %{public}d", node->GetWindowId());
207        return true;
208    }
209    return false;
210}
211
212void WindowLayoutPolicyTile::PushBackNodeInTileQueue(const sptr<WindowNode>& node, DisplayId displayId)
213{
214    if (node == nullptr) {
215        return;
216    }
217    if (IsWindowAlreadyInTileQueue(node)) {
218        return;
219    }
220
221    if (!WindowHelper::IsWindowModeSupported(node->GetModeSupportInfo(), WindowMode::WINDOW_MODE_FLOATING)) {
222        WLOGFD("Window don't support floating mode that should be minimized, winId: %{public}u, "
223            "modeSupportInfo: %{public}u", node->GetWindowId(), node->GetModeSupportInfo());
224        MinimizeApp::AddNeedMinimizeApp(node, MinimizeReason::INVALID_MODE_OR_SIZE_IN_TILE);
225        return;
226    }
227    auto& foregroundNodes = foregroundNodesMap_[displayId];
228    while (!foregroundNodes.empty() && foregroundNodes.size() >= maxTileWinNumMap_[displayId]) {
229        auto removeNode = foregroundNodes.front();
230        foregroundNodes.pop_front();
231        WLOGFD("Minimize win in queue head for add new win, windowId: %{public}d", removeNode->GetWindowId());
232        MinimizeApp::AddNeedMinimizeApp(removeNode, MinimizeReason::LAYOUT_TILE);
233    }
234    foregroundNodes.push_back(node);
235    WLOGFD("Pusk back win in tile queue, displayId: %{public}" PRIu64", winId: %{public}d",
236        displayId, node->GetWindowId());
237}
238
239void WindowLayoutPolicyTile::RemoveNodeFromTileQueue(const sptr<WindowNode>& node)
240{
241    if (node == nullptr) {
242        return;
243    }
244    DisplayId displayId = node->GetDisplayId();
245    auto& foregroundNodes = foregroundNodesMap_[displayId];
246    auto iter = std::find(foregroundNodes.begin(), foregroundNodes.end(), node);
247    if (iter != foregroundNodes.end()) {
248        WLOGFD("Remove win in tile for win id: %{public}d", node->GetWindowId());
249        foregroundNodes.erase(iter);
250    }
251}
252
253bool WindowLayoutPolicyTile::IsValidTileQueueAndPresetRects(DisplayId displayId)
254{
255    auto& foregroundNodes = foregroundNodesMap_[displayId];
256    uint32_t num = foregroundNodes.size();
257    auto& presetRectsForAllLevel = presetRectsMap_[displayId];
258    if (num > maxTileWinNumMap_[displayId] || num > presetRectsForAllLevel.size() || num == 0) {
259        WLOGE("Invalid tile queue, foreground tileNum: %{public}u, maxTileNum: %{public}u, presetRectsForAllLevel: "
260            "%{public}u", num, maxTileWinNumMap_[displayId], static_cast<uint32_t>(presetRectsForAllLevel.size()));
261        return false;
262    }
263    auto& presetRect = presetRectsForAllLevel[num - 1];
264    if (presetRect.size() != num) {
265        WLOGE("Invalid preset rects, foreground tileNum: %{public}u, presetRect.size(): %{public}u",
266            num, static_cast<uint32_t>(presetRect.size()));
267        return false;
268    }
269    return true;
270}
271
272void WindowLayoutPolicyTile::RefreshTileQueue(DisplayId displayId,
273    std::vector<sptr<WindowNode>>& needMinimizeNodes, std::vector<sptr<WindowNode>>& needRecoverNodes)
274{
275    /*
276     * Usually, needMinimizeNodes and needRecoverNodes will be empty, there is no need to refresh tile queue
277     */
278    auto& foregroundNodes = foregroundNodesMap_[displayId];
279    if (needMinimizeNodes.empty() && needRecoverNodes.empty()) {
280        WLOGD("No need to refresh tileQueue");
281        return;
282    }
283
284    WLOGD("Update tile queue for the nodes which should be minimized or recovered");
285    for (auto& miniNode : needMinimizeNodes) {
286        auto iter = std::find(foregroundNodes.begin(), foregroundNodes.end(), miniNode);
287        if (iter != foregroundNodes.end()) {
288            foregroundNodes.erase(iter);
289        }
290    }
291    for (auto& recNode : needRecoverNodes) {
292        auto iter = std::find(foregroundNodes.begin(), foregroundNodes.end(), recNode);
293        if (iter == foregroundNodes.end()) {
294            foregroundNodes.push_back(recNode);
295        }
296    }
297    ApplyPresetRectForTileWindows(displayId);
298    needMinimizeNodes.clear();
299    needRecoverNodes.clear();
300}
301
302void WindowLayoutPolicyTile::ApplyPresetRectForTileWindows(DisplayId displayId)
303{
304    if (!(IsValidTileQueueAndPresetRects(displayId))) {
305        return;
306    }
307
308    auto& foregroundNodes = foregroundNodesMap_[displayId];
309    uint32_t num = foregroundNodes.size();
310    auto rectIt = presetRectsMap_[displayId][num - 1].begin();
311    std::vector<sptr<WindowNode>> needMinimizeNodes;
312    std::vector<sptr<WindowNode>> needRecoverNodes;
313    for (auto node : foregroundNodes) {
314        auto& rect = (*rectIt);
315        if (WindowHelper::IsRectSatisfiedWithSizeLimits(rect, node->GetWindowUpdatedSizeLimits())) {
316            node->SetWindowMode(WindowMode::WINDOW_MODE_FLOATING);
317            // when change mode, need to reset shadow and radius
318            WindowSystemEffect::SetWindowEffect(node);
319            if (node->GetWindowToken()) {
320                node->GetWindowToken()->UpdateWindowMode(WindowMode::WINDOW_MODE_FLOATING);
321            }
322            node->SetRequestRect(rect);
323            node->SetDecoStatus(true);
324            rectIt++;
325            WLOGFD("Set preset rect for tileWin, id: %{public}d [%{public}d %{public}d %{public}d %{public}d]",
326                node->GetWindowId(), rect.posX_, rect.posY_, rect.width_, rect.height_);
327        } else {
328            WLOGFD("Minimize node which can't be applied to tileRect, winId: %{public}u", node->GetWindowId());
329            if (num == maxTileWinNumMap_[displayId]) {
330                // if foreground nodes equal to max tileWinNum, means need to recover one node before minimize curNode
331                auto recoverNode = MinimizeApp::GetRecoverdNodeFromMinimizeList();
332                if (recoverNode != nullptr) {
333                    needRecoverNodes.push_back(recoverNode);
334                    WLOGFD("Cancel minimize node from minimizeList, winId: %{public}u", node->GetWindowId());
335                }
336            }
337            needMinimizeNodes.push_back(node);
338            MinimizeApp::AddNeedMinimizeApp(node, MinimizeReason::INVALID_MODE_OR_SIZE_IN_TILE);
339            break;
340        }
341    }
342
343    RefreshTileQueue(displayId, needMinimizeNodes, needRecoverNodes);
344}
345
346void WindowLayoutPolicyTile::UpdateLayoutRect(const sptr<WindowNode>& node)
347{
348    UpdateWindowSizeLimits(node);
349    bool floatingWindow = (node->GetWindowMode() == WindowMode::WINDOW_MODE_FLOATING);
350    bool needAvoid = (node->GetWindowFlags() & static_cast<uint32_t>(WindowFlag::WINDOW_FLAG_NEED_AVOID));
351    Rect lastRect = node->GetWindowRect();
352    Rect winRect = node->GetRequestRect();
353    WLOGI("[Before TileLayout] windowId: %{public}u, mode: %{public}u, type: %{public}u requestRect: [%{public}d, "
354        "%{public}d, %{public}u, %{public}u]", node->GetWindowId(), node->GetWindowMode(), node->GetWindowType(),
355        winRect.posX_, winRect.posY_, winRect.width_, winRect.height_);
356
357    if (!floatingWindow) { // fullscreen window
358        const auto& displayRect = DisplayGroupInfo::GetInstance().GetDisplayRect(node->GetDisplayId());
359        const auto& limitDisplayRect = limitRectMap_[node->GetDisplayId()];
360        winRect = needAvoid ? limitDisplayRect : displayRect;
361    }
362
363    WLOGI("[After TileLayout] windowId: %{public}u, isDecor: %{public}u, winRect: [%{public}d, %{public}d, "
364        "%{public}u, %{public}u]", node->GetWindowId(), node->GetDecoStatus(), winRect.posX_, winRect.posY_,
365        winRect.width_, winRect.height_);
366    node->SetWindowRect(winRect);
367
368    // postProcess after update winRect
369    CalcAndSetNodeHotZone(winRect, node);
370    UpdateSurfaceBounds(node, winRect, lastRect);
371    NotifyClientAndAnimation(node, winRect, node->GetWindowSizeChangeReason());
372}
373} // Rosen
374} // OHOS
375