1 /*
2 * Copyright (C) 2024 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/ai/data_detector_adapter.h"
17
18 #include "interfaces/inner_api/ace/modal_ui_extension_config.h"
19 #include "want.h"
20
21 #include "base/log/log_wrapper.h"
22 #include "core/common/ai/data_detector_mgr.h"
23 #include "core/components_ng/base/view_stack_processor.h"
24 #include "core/components_ng/pattern/ui_extension/ui_extension_model_ng.h"
25 #include "core/components_ng/pattern/ui_extension/ui_extension_pattern.h"
26 #include "core/pipeline_ng/pipeline_context.h"
27
28 namespace OHOS::Ace {
29
30 constexpr int32_t AI_TEXT_MAX_LENGTH = 300;
31 constexpr int32_t AI_TEXT_GAP = 100;
32 constexpr int32_t AI_DELAY_TIME = 100;
33 const std::pair<std::string, std::string> UI_EXTENSION_TYPE = { "ability.want.params.uiExtensionType", "sys/commonUI" };
34 const std::unordered_map<TextDataDetectType, std::string> TEXT_DETECT_MAP = {
35 { TextDataDetectType::PHONE_NUMBER, "phoneNum" }, { TextDataDetectType::URL, "url" },
36 { TextDataDetectType::EMAIL, "email" }, { TextDataDetectType::ADDRESS, "location" }
37 };
38
ShowUIExtensionMenu(const AISpan & aiSpan,NG::RectF aiRect,const RefPtr<NG::FrameNode> & targetNode)39 bool DataDetectorAdapter::ShowUIExtensionMenu(
40 const AISpan& aiSpan, NG::RectF aiRect, const RefPtr<NG::FrameNode>& targetNode)
41 {
42 ModalUIExtensionCallbacks callbacks;
43 callbacks.onResult = [onClickMenu = onClickMenu_](int32_t code, const AAFwk::Want& want) {
44 TAG_LOGI(AceLogTag::ACE_UIEXTENSIONCOMPONENT, "UIExtension Ability onResult, code: %{public}d", code);
45 auto action = want.GetStringParam("action");
46 if (!action.empty() && onClickMenu) {
47 onClickMenu(action);
48 }
49 };
50 callbacks.onDestroy = []() { TAG_LOGI(AceLogTag::ACE_UIEXTENSIONCOMPONENT, "UIExtension Ability Destroy"); };
51 callbacks.onError = [](int32_t code, const std::string& name, const std::string& message) {
52 TAG_LOGI(AceLogTag::ACE_UIEXTENSIONCOMPONENT,
53 "UIExtension Ability Error, code: %{public}d, name: %{public}s, message: %{public}s", code, name.c_str(),
54 message.c_str());
55 };
56 callbacks.onRelease = [](int32_t code) {
57 TAG_LOGI(AceLogTag::ACE_UIEXTENSIONCOMPONENT, "UIExtension Ability Release, code: %{public}d", code);
58 };
59 AAFwk::Want want;
60 want.SetElementName(uiExtensionBundleName_, uiExtensionAbilityName_);
61 SetWantParamaters(aiSpan, want);
62
63 auto uiExtNode = NG::UIExtensionModelNG::Create(want, callbacks);
64 CHECK_NULL_RETURN(uiExtNode, false);
65 auto onReceive = GetOnReceive(uiExtNode, aiRect, targetNode);
66 auto pattern = uiExtNode->GetPattern<NG::UIExtensionPattern>();
67 CHECK_NULL_RETURN(pattern, false);
68 pattern->SetOnReceiveCallback(std::move(onReceive));
69 uiExtNode->MarkModifyDone();
70 return true;
71 }
72
GetOnReceive(const RefPtr<NG::FrameNode> & uiExtNode,NG::RectF aiRect,const RefPtr<NG::FrameNode> & targetNode)73 std::function<void(const AAFwk::WantParams&)> DataDetectorAdapter::GetOnReceive(
74 const RefPtr<NG::FrameNode>& uiExtNode, NG::RectF aiRect, const RefPtr<NG::FrameNode>& targetNode)
75 {
76 return [uiExtNode, aiRect, onClickMenu = onClickMenu_,
77 targetNodeWeak = AceType::WeakClaim(AceType::RawPtr(targetNode))](const AAFwk::WantParams& wantParams) {
78 TAG_LOGI(AceLogTag::ACE_UIEXTENSIONCOMPONENT, "UIExtension Ability onReceive");
79 CHECK_NULL_VOID(uiExtNode);
80 auto targetNode = targetNodeWeak.Upgrade();
81 CHECK_NULL_VOID(targetNode);
82 auto pipeline = NG::PipelineContext::GetCurrentContext();
83 CHECK_NULL_VOID(pipeline);
84 auto overlayManager = pipeline->GetOverlayManager();
85 CHECK_NULL_VOID(overlayManager);
86 std::string action = wantParams.GetStringParam("action");
87 if (!action.empty() && onClickMenu) {
88 onClickMenu(action);
89 }
90 std::string closeMenu = wantParams.GetStringParam("closeMenu");
91 if (closeMenu == "true") {
92 int32_t targetId = targetNode->GetId();
93 auto menuNode = overlayManager->GetMenuNode(targetId);
94 CHECK_NULL_VOID(menuNode);
95 overlayManager->HideMenu(menuNode, targetId);
96 return;
97 }
98 std::string longestContent = wantParams.GetStringParam("longestContent");
99 std::string menuSizeString = wantParams.GetStringParam("menuSize");
100 if (longestContent.empty() || menuSizeString.empty()) {
101 return;
102 }
103 int32_t menuSize = static_cast<int32_t>(atoi(menuSizeString.c_str()));
104 overlayManager->ShowUIExtensionMenu(uiExtNode, aiRect, longestContent, menuSize, targetNode);
105 };
106 }
107
ResponseBestMatchItem(const AISpan & aiSpan)108 void DataDetectorAdapter::ResponseBestMatchItem(const AISpan& aiSpan)
109 {
110 ModalUIExtensionCallbacks callbacks;
111 AAFwk::Want want;
112 want.SetElementName(uiExtensionBundleName_, uiExtensionAbilityName_);
113 SetWantParamaters(aiSpan, want);
114 want.SetParam(std::string("clickType"), std::string("leftMouse"));
115 auto uiExtNode = NG::UIExtensionModelNG::Create(want, callbacks);
116 CHECK_NULL_VOID(uiExtNode);
117
118 // Extend the lifecycle of the uiExtNode with callback
119 auto onReceive = [uiExtNode] (const AAFwk::WantParams& wantParams) {};
120 auto pattern = uiExtNode->GetPattern<NG::UIExtensionPattern>();
121 CHECK_NULL_VOID(pattern);
122 pattern->SetOnReceiveCallback(std::move(onReceive));
123 }
124
SetWantParamaters(const AISpan & aiSpan,AAFwk::Want & want)125 void DataDetectorAdapter::SetWantParamaters(const AISpan& aiSpan, AAFwk::Want& want)
126 {
127 want.SetParam("entityType", TEXT_DETECT_MAP.at(aiSpan.type));
128 want.SetParam("entityText", aiSpan.content);
129 want.SetParam(UI_EXTENSION_TYPE.first, UI_EXTENSION_TYPE.second);
130 }
131
SetTextDetectTypes(const std::string & types)132 void DataDetectorAdapter::SetTextDetectTypes(const std::string& types)
133 {
134 textDetectTypes_ = types;
135
136 std::set<std::string> newTypesSet;
137 std::istringstream iss(types);
138 std::string type;
139 while (std::getline(iss, type, ',')) {
140 newTypesSet.insert(type);
141 }
142 if (newTypesSet != textDetectTypesSet_) {
143 textDetectTypesSet_ = newTypesSet;
144 aiDetectInitialized_ = false;
145 auto host = GetHost();
146 CHECK_NULL_VOID(host);
147 host->MarkDirtyNode(NG::PROPERTY_UPDATE_MEASURE);
148 }
149 }
150
InitTextDetect(int32_t startPos,std::string detectText)151 void DataDetectorAdapter::InitTextDetect(int32_t startPos, std::string detectText)
152 {
153 TextDataDetectInfo info;
154 info.text = detectText;
155 info.module = textDetectTypes_;
156
157 auto context = PipelineContext::GetCurrentContext();
158 CHECK_NULL_VOID(context);
159 int32_t instanceID = context->GetInstanceId();
160 auto textFunc = [weak = WeakClaim(this), instanceID, startPos, info](const TextDataDetectResult result) {
161 ContainerScope scope(instanceID);
162 auto dataDetectorAdapter = weak.Upgrade();
163 CHECK_NULL_VOID(dataDetectorAdapter);
164 auto host = dataDetectorAdapter->GetHost();
165 CHECK_NULL_VOID(host);
166 auto context = host->GetContext();
167 CHECK_NULL_VOID(context);
168 auto uiTaskExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::UI);
169 uiTaskExecutor.PostTask([result, weak, instanceID, startPos, info] {
170 ContainerScope scope(instanceID);
171 auto dataDetectorAdapter = weak.Upgrade();
172 CHECK_NULL_VOID(dataDetectorAdapter);
173 if (info.module != dataDetectorAdapter->textDetectTypes_) {
174 return;
175 }
176 dataDetectorAdapter->SetTextDetectResult(result);
177 dataDetectorAdapter->FireOnResult(result);
178 if (result.code != 0) {
179 TAG_LOGD(AceLogTag::ACE_TEXT, "Data detect error, error code: %{public}d", result.code);
180 return;
181 }
182 dataDetectorAdapter->ParseAIResult(result, startPos);
183 auto host = dataDetectorAdapter->GetHost();
184 CHECK_NULL_VOID(host);
185 host->MarkDirtyNode(NG::PROPERTY_UPDATE_MEASURE);
186 });
187 };
188
189 TAG_LOGI(AceLogTag::ACE_TEXT, "Start entity detect using AI");
190 DataDetectorMgr::GetInstance().DataDetect(info, textFunc);
191 }
192
ParseAIResult(const TextDataDetectResult & result,int32_t startPos)193 void DataDetectorAdapter::ParseAIResult(const TextDataDetectResult& result, int32_t startPos)
194 {
195 auto entityMenuServiceInfoJson = JsonUtil::ParseJsonString(result.entityMenuServiceInfo);
196 CHECK_NULL_VOID(entityMenuServiceInfoJson);
197 if (uiExtensionBundleName_.empty()) {
198 uiExtensionBundleName_ = entityMenuServiceInfoJson->GetString("bundlename");
199 }
200 if (uiExtensionAbilityName_.empty()) {
201 uiExtensionAbilityName_ = entityMenuServiceInfoJson->GetString("abilityname");
202 }
203 auto entityJson = JsonUtil::ParseJsonString(result.entity);
204 CHECK_NULL_VOID(entityJson);
205 for (const auto& type : TEXT_DETECT_MAP) {
206 auto jsonValue = entityJson->GetValue(type.second);
207 ParseAIJson(jsonValue, type.first, startPos);
208 }
209
210 if (startPos + AI_TEXT_MAX_LENGTH >= static_cast<int32_t>(StringUtils::ToWstring(textForAI_).length())) {
211 aiDetectInitialized_ = true;
212 // process with overlapping entities, leaving only the earlier ones
213 int32_t preEnd = 0;
214 auto aiSpanIterator = aiSpanMap_.begin();
215 while (aiSpanIterator != aiSpanMap_.end()) {
216 auto aiSpan = aiSpanIterator->second;
217 if (aiSpan.start < preEnd) {
218 aiSpanIterator = aiSpanMap_.erase(aiSpanIterator);
219 } else {
220 preEnd = aiSpan.end;
221 ++aiSpanIterator;
222 }
223 }
224 }
225 }
226
ParseAIJson(const std::unique_ptr<JsonValue> & jsonValue,TextDataDetectType type,int32_t startPos)227 void DataDetectorAdapter::ParseAIJson(
228 const std::unique_ptr<JsonValue>& jsonValue, TextDataDetectType type, int32_t startPos)
229 {
230 if (!jsonValue || !jsonValue->IsArray()) {
231 TAG_LOGI(AceLogTag::ACE_TEXT, "Error AI Result.");
232 return;
233 }
234
235 for (int32_t i = 0; i < jsonValue->GetArraySize(); ++i) {
236 auto item = jsonValue->GetArrayItem(i);
237 auto charOffset = item->GetInt("charOffset");
238 auto oriText = item->GetString("oriText");
239 auto wTextForAI = StringUtils::ToWstring(textForAI_);
240 auto wOriText = StringUtils::ToWstring(oriText);
241 int32_t end = startPos + charOffset + static_cast<int32_t>(wOriText.length());
242 if (charOffset < 0 || startPos + charOffset >= static_cast<int32_t>(wTextForAI.length()) ||
243 end >= startPos + AI_TEXT_MAX_LENGTH || oriText.empty()) {
244 TAG_LOGI(AceLogTag::ACE_TEXT, "The result of AI is error");
245 continue;
246 }
247 if (oriText !=
248 StringUtils::ToString(wTextForAI.substr(startPos + charOffset, static_cast<int32_t>(wOriText.length())))) {
249 TAG_LOGI(AceLogTag::ACE_TEXT, "The charOffset is error");
250 continue;
251 }
252 int32_t start = startPos + charOffset;
253 auto iter = aiSpanMap_.find(start);
254 if (iter != aiSpanMap_.end() && iter->second.content.length() >= oriText.length()) {
255 // both entities start at the same position, leaving the longer one
256 continue;
257 }
258
259 AISpan aiSpan;
260 aiSpan.start = start;
261 aiSpan.end = end;
262 aiSpan.content = oriText;
263 aiSpan.type = type;
264 aiSpanMap_[aiSpan.start] = aiSpan;
265 }
266 }
267
StartAITask()268 void DataDetectorAdapter::StartAITask()
269 {
270 aiSpanMap_.clear();
271
272 auto context = PipelineContext::GetCurrentContext();
273 CHECK_NULL_VOID(context);
274 auto taskExecutor = context->GetTaskExecutor();
275 CHECK_NULL_VOID(taskExecutor);
276 aiDetectDelayTask_.Cancel();
277 aiDetectDelayTask_.Reset([weak = WeakClaim(this)]() {
278 auto dataDetectorAdapter = weak.Upgrade();
279 CHECK_NULL_VOID(dataDetectorAdapter);
280 if (dataDetectorAdapter->textForAI_.empty()) {
281 return;
282 }
283 int32_t startPos = 0;
284 auto wTextForAI = StringUtils::ToWstring(dataDetectorAdapter->textForAI_);
285 auto wTextForAILength = static_cast<int32_t>(wTextForAI.length());
286 while (startPos < wTextForAILength) {
287 std::string detectText = StringUtils::ToString(wTextForAI.substr(startPos, AI_TEXT_MAX_LENGTH));
288 dataDetectorAdapter->InitTextDetect(startPos, detectText);
289 startPos += AI_TEXT_MAX_LENGTH - AI_TEXT_GAP;
290 }
291 });
292 taskExecutor->PostDelayedTask(aiDetectDelayTask_, TaskExecutor::TaskType::UI, AI_DELAY_TIME);
293 }
294 } // namespace OHOS::Ace
295