1 /*
2 * Copyright (c) 2025 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 "core/common/force_split/force_split_utils.h"
17
18 #include <string>
19 #include <vector>
20
21 #include "base/geometry/dimension.h"
22 #include "base/json/json_util.h"
23 #include "core/common/container.h"
24 #include "core/components_ng/pattern/image/image_pattern.h"
25 #include "core/components_ng/pattern/linear_layout/linear_layout_pattern.h"
26 #include "core/components_ng/pattern/navigation/nav_bar_node.h"
27 #include "core/components_ng/pattern/navrouter/navdestination_group_node.h"
28 #include "core/components_ng/pattern/navrouter/navdestination_pattern.h"
29 #include "core/components_ng/pattern/stack/stack_pattern.h"
30 #include "core/pipeline/base/element_register.h"
31
32 namespace OHOS::Ace::NG {
33
34 namespace {
35 constexpr Dimension APP_ICON_SIZE = 64.0_vp;
36 constexpr Dimension APP_ICON_BORDER_RADIUS = 18.0_vp;
37 constexpr char BG_COLOR_SYS_RES_NAME[] = "sys.color.ohos_id_color_sub_background";
38 const std::vector<std::string> HOME_NAME_KEYWORDS = {"main", "home", "index", "root"};
39 const std::vector<std::string> EXCLUDE_NAME_KEYWORDS = {"guide", "load", "splash", "login", "privacy"};
40 constexpr int32_t HOME_PAGE_CHILD_NODE_DEPTH_THRESHOLD = 30;
41 constexpr int32_t HOME_PAGE_CHILD_NODE_COUNT_THRESHOLD = 100;
42 constexpr char ENABLE_HOOK_KEY[] = "enableHook";
43 constexpr char NAVIGATION_OPTIONS_KEY[] = "navigationOptions";
44 constexpr char NAVIGATION_OPTIONS_ID_KEY[] = "id";
45 constexpr char NAVIGATION_OPTIONS_DEPTH_KEY[] = "depth";
46 }
47
CreatePlaceHolderContent(const RefPtr<PipelineContext> & context)48 RefPtr<FrameNode> ForceSplitUtils::CreatePlaceHolderContent(const RefPtr<PipelineContext>& context)
49 {
50 CHECK_NULL_RETURN(context, nullptr);
51 auto themeManager = context->GetThemeManager();
52 CHECK_NULL_RETURN(themeManager, nullptr);
53 auto windowManager = context->GetWindowManager();
54 CHECK_NULL_RETURN(windowManager, nullptr);
55 auto stackNode = FrameNode::GetOrCreateFrameNode(
56 V2::STACK_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
57 []() { return AceType::MakeRefPtr<StackPattern>(); });
58 CHECK_NULL_RETURN(stackNode, nullptr);
59 auto stackLayoutProperty = stackNode->GetLayoutProperty();
60 CHECK_NULL_RETURN(stackLayoutProperty, nullptr);
61 stackLayoutProperty->UpdateMeasureType(MeasureType::MATCH_PARENT);
62
63 auto imageNode = FrameNode::GetOrCreateFrameNode(
64 V2::IMAGE_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
65 []() { return AceType::MakeRefPtr<ImagePattern>(); });
66 CHECK_NULL_RETURN(imageNode, nullptr);
67 auto themeConstants = themeManager->GetThemeConstants();
68 CHECK_NULL_RETURN(themeConstants, nullptr);
69 auto id = windowManager->GetAppIconId();
70 auto pixelMap = themeConstants->GetPixelMap(id);
71 auto imageLayoutProperty = imageNode->GetLayoutProperty<ImageLayoutProperty>();
72 CHECK_NULL_RETURN(imageLayoutProperty, nullptr);
73 CalcSize imageCalcSize((CalcLength(APP_ICON_SIZE)), CalcLength(APP_ICON_SIZE));
74 imageLayoutProperty->UpdateUserDefinedIdealSize(imageCalcSize);
75 imageLayoutProperty->UpdateImageSourceInfo(ImageSourceInfo(PixelMap::CreatePixelMap(&pixelMap)));
76 auto imageRenderContext = imageNode->GetRenderContext();
77 CHECK_NULL_RETURN(imageRenderContext, nullptr);
78 BorderRadiusProperty borderRadius;
79 borderRadius.SetRadius(APP_ICON_BORDER_RADIUS);
80 borderRadius.multiValued = false;
81 imageRenderContext->UpdateBorderRadius(borderRadius);
82 auto paintProperty = imageNode->GetPaintPropertyPtr<ImageRenderProperty>();
83 CHECK_NULL_RETURN(paintProperty, nullptr);
84 paintProperty->UpdateNeedBorderRadius(true);
85 paintProperty->UpdateBorderRadius(borderRadius);
86 auto imagePattern = imageNode->GetPattern<ImagePattern>();
87 CHECK_NULL_RETURN(imagePattern, nullptr);
88 imagePattern->SetNeedBorderRadius(true);
89
90 imageNode->MountToParent(stackNode);
91 imageNode->MarkModifyDone();
92
93 return stackNode;
94 }
95
CreateNavDestinationProxyNode()96 RefPtr<NavDestinationGroupNode> ForceSplitUtils::CreateNavDestinationProxyNode()
97 {
98 auto nodeId = ElementRegister::GetInstance()->MakeUniqueId();
99 auto proxyNode = NavDestinationGroupNode::GetOrCreateGroupNode(
100 V2::NAVDESTINATION_VIEW_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr<NavDestinationPattern>(); });
101 CHECK_NULL_RETURN(proxyNode, nullptr);
102 proxyNode->SetNavDestinationType(NavDestinationType::PROXY);
103 auto pattern = proxyNode->GetPattern<NavDestinationPattern>();
104 CHECK_NULL_RETURN(pattern, nullptr);
105 pattern->SetName("__NavDestination_proxy__");
106 auto eventHub = proxyNode->GetOrCreateEventHub<EventHub>();
107 if (eventHub) {
108 eventHub->SetEnabled(false);
109 }
110 auto focusHub = proxyNode->GetOrCreateFocusHub();
111 if (focusHub) {
112 focusHub->SetFocusable(false);
113 }
114 auto property = proxyNode->GetLayoutProperty<NavDestinationLayoutProperty>();
115 CHECK_NULL_RETURN(property, nullptr);
116 property->UpdateHideTitleBar(true);
117 property->UpdateIsAnimatedTitleBar(false);
118 property->UpdateHideToolBar(true);
119 property->UpdateIsAnimatedToolBar(false);
120 property->UpdateVisibility(VisibleType::INVISIBLE);
121 auto contentNode = FrameNode::GetOrCreateFrameNode(
122 V2::NAVDESTINATION_CONTENT_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
123 []() { return AceType::MakeRefPtr<LinearLayoutPattern>(true); });
124 CHECK_NULL_RETURN(contentNode, nullptr);
125 proxyNode->AddChild(contentNode);
126 proxyNode->SetContentNode(contentNode);
127 return proxyNode;
128 }
129
IsHomePageNavBar(const RefPtr<NavBarNode> & navBar)130 bool ForceSplitUtils::IsHomePageNavBar(const RefPtr<NavBarNode>& navBar)
131 {
132 CHECK_NULL_RETURN(navBar, false);
133 int32_t count = 0;
134 int32_t depth = 0;
135 navBar->GetPageNodeCountAndDepth(&count, &depth);
136 if (count > HOME_PAGE_CHILD_NODE_COUNT_THRESHOLD &&
137 depth > HOME_PAGE_CHILD_NODE_DEPTH_THRESHOLD) {
138 TAG_LOGI(AceLogTag::ACE_NAVIGATION, "find NavBar as homePage for node count:%{public}d and depth:%{public}d",
139 count, depth);
140 return true;
141 }
142 return false;
143 }
144
IsHomePageNavDestination(const RefPtr<NavDestinationGroupNode> & node)145 bool ForceSplitUtils::IsHomePageNavDestination(const RefPtr<NavDestinationGroupNode>& node)
146 {
147 CHECK_NULL_RETURN(node, false);
148 if (node->GetNavDestinationMode() == NavDestinationMode::DIALOG) {
149 return false;
150 }
151
152 auto context = node->GetContext();
153 CHECK_NULL_RETURN(context, false);
154 auto navManager = context->GetNavigationManager();
155 CHECK_NULL_RETURN(navManager, false);
156 auto pattern = node->GetPattern<NavDestinationPattern>();
157 CHECK_NULL_RETURN(pattern, false);
158 const auto& expectedHomeName = navManager->GetHomePageName();
159 std::string name = pattern->GetName();
160 if (!expectedHomeName.empty()) {
161 if (expectedHomeName == name) {
162 TAG_LOGI(AceLogTag::ACE_NAVIGATION, "find homePage[%{public}s] with expectedName", name.c_str());
163 return true;
164 }
165 return false;
166 }
167 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
168 for (auto it = EXCLUDE_NAME_KEYWORDS.begin(); it != EXCLUDE_NAME_KEYWORDS.end(); it++) {
169 std::string keyword = *it;
170 if (name.find(keyword) != std::string::npos) {
171 return false;
172 }
173 }
174 for (auto it = HOME_NAME_KEYWORDS.begin(); it != HOME_NAME_KEYWORDS.end(); it++) {
175 std::string keyword = *it;
176 if (name.find(keyword) != std::string::npos) {
177 TAG_LOGI(AceLogTag::ACE_NAVIGATION, "find homePage[%{public}s] with primary page keyword", name.c_str());
178 return true;
179 }
180 }
181 int32_t count = 0;
182 int32_t depth = 0;
183 node->GetPageNodeCountAndDepth(&count, &depth);
184 if (count > HOME_PAGE_CHILD_NODE_COUNT_THRESHOLD &&
185 depth > HOME_PAGE_CHILD_NODE_DEPTH_THRESHOLD) {
186 TAG_LOGI(AceLogTag::ACE_NAVIGATION, "find homePage[%{public}s] for node count:%{public}d and depth:%{public}d",
187 name.c_str(), count, depth);
188 return true;
189 }
190 return false;
191 }
192
CreatePlaceHolderNode()193 RefPtr<FrameNode> ForceSplitUtils::CreatePlaceHolderNode()
194 {
195 int32_t phId = ElementRegister::GetInstance()->MakeUniqueId();
196 auto phNode = FrameNode::GetOrCreateFrameNode(
197 V2::SPLIT_PLACEHOLDER_CONTENT_ETS_TAG, phId, []() { return AceType::MakeRefPtr<Pattern>(); });
198 CHECK_NULL_RETURN(phNode, nullptr);
199 auto context = phNode->GetContextRefPtr();
200 CHECK_NULL_RETURN(context, nullptr);
201 auto navManager = context->GetNavigationManager();
202 CHECK_NULL_RETURN(navManager, nullptr);
203 auto renderContext = phNode->GetRenderContext();
204 CHECK_NULL_RETURN(renderContext, nullptr);
205 Color bgColor;
206 if (navManager->GetSystemColor(BG_COLOR_SYS_RES_NAME, bgColor)) {
207 renderContext->UpdateBackgroundColor(bgColor);
208 }
209 auto property = phNode->GetLayoutProperty();
210 CHECK_NULL_RETURN(property, nullptr);
211 property->UpdateVisibility(VisibleType::INVISIBLE);
212 property->UpdateAlignment(Alignment::TOP_LEFT);
213 SafeAreaExpandOpts opts = { .type = SAFE_AREA_TYPE_SYSTEM | SAFE_AREA_TYPE_CUTOUT,
214 .edges = SAFE_AREA_EDGE_ALL };
215 property->UpdateSafeAreaExpandOpts(opts);
216 auto eventHub = phNode->GetOrCreateEventHub<EventHub>();
217 if (eventHub) {
218 eventHub->SetEnabled(false);
219 }
220 auto focusHub = phNode->GetOrCreateFocusHub();
221 if (focusHub) {
222 focusHub->SetFocusable(false);
223 }
224 auto phContent = ForceSplitUtils::CreatePlaceHolderContent(context);
225 if (phContent) {
226 phContent->MountToParent(phNode);
227 } else {
228 TAG_LOGE(AceLogTag::ACE_NAVIGATION, "failed to create PlaceHolder content");
229 }
230 return phNode;
231 }
232
ParseForceSplitConfig(const std::string & configJsonStr,ForceSplitConfig & config)233 bool ForceSplitUtils::ParseForceSplitConfig(const std::string& configJsonStr, ForceSplitConfig& config)
234 {
235 auto configJson = JsonUtil::ParseJsonString(configJsonStr);
236 if (!configJson || !configJson->IsObject()) {
237 TAG_LOGE(AceLogTag::ACE_NAVIGATION, "Error, arkUIOptions is an invalid json object!");
238 return false;
239 }
240 config.isArkUIHookEnabled = configJson->GetBool(ENABLE_HOOK_KEY, false);
241 if (!configJson->Contains(NAVIGATION_OPTIONS_KEY)) {
242 return true;
243 }
244 auto navOptions = configJson->GetValue(NAVIGATION_OPTIONS_KEY);
245 if (!navOptions || !navOptions->IsObject()) {
246 TAG_LOGE(AceLogTag::ACE_NAVIGATION, "Error, navigationOptions is an invalid json object!");
247 return false;
248 }
249 if (navOptions->Contains(NAVIGATION_OPTIONS_ID_KEY)) {
250 auto idJson = navOptions->GetValue(NAVIGATION_OPTIONS_ID_KEY);
251 if (!idJson->IsString()) {
252 TAG_LOGE(AceLogTag::ACE_NAVIGATION, "Error, navigationOptions.id is not string!");
253 return false;
254 }
255 auto idStr = idJson->GetString();
256 if (!idStr.empty()) {
257 config.navigationId = idStr;
258 }
259 }
260 if (navOptions->Contains(NAVIGATION_OPTIONS_DEPTH_KEY)) {
261 auto depthJson = navOptions->GetValue(NAVIGATION_OPTIONS_DEPTH_KEY);
262 if (!depthJson->IsNumber()) {
263 TAG_LOGE(AceLogTag::ACE_NAVIGATION, "Error, navigationOptions.depth is not number!");
264 return false;
265 }
266 config.navigationDepth = navOptions->GetInt(NAVIGATION_OPTIONS_DEPTH_KEY);
267 }
268 return true;
269 }
270 } // namespace OHOS::Ace::NG
271
272