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 "base/utils/utf_helper.h"
17 #include "core/common/ai/data_detector_adapter.h"
18
19 #include "adapter/ohos/entrance/ace_container.h"
20 #include "core/common/ai/data_detector_mgr.h"
21 #include "core/common/ai/data_url_analyzer_mgr.h"
22 #include "core/pipeline_ng/pipeline_context.h"
23
24 namespace OHOS::Ace {
25
26 constexpr int32_t AI_TEXT_MAX_LENGTH = 500;
27 constexpr int32_t AI_TEXT_GAP = 100;
28 constexpr int32_t AI_DELAY_TIME = 100;
29 constexpr uint32_t SECONDS_TO_MILLISECONDS = 1000;
30 constexpr uint8_t URL_DETECT_FINISH = (1 << 0);
31 constexpr uint8_t OTHER_DETECT_FINISH = (1 << 1);
32 constexpr uint8_t ALL_DETECT_FINISH = URL_DETECT_FINISH | OTHER_DETECT_FINISH;
33
34 const std::string ALL_TEXT_DETECT_TYPES = "phoneNum,url,email,location,datetime";
35 const std::string TEXT_DETECT_TYPES_WITHOUT_URL = "phoneNum,email,location,datetime";
36
37 const std::unordered_map<TextDataDetectType, std::string> TEXT_DETECT_MAP = {
38 { TextDataDetectType::PHONE_NUMBER, "phoneNum" }, { TextDataDetectType::URL, "url" },
39 { TextDataDetectType::EMAIL, "email" }, { TextDataDetectType::ADDRESS, "location" },
40 { TextDataDetectType::DATE_TIME, "datetime" }
41 };
42 const std::unordered_map<std::string, TextDataDetectType> TEXT_DETECT_MAP_REVERSE = {
43 { "phoneNum", TextDataDetectType::PHONE_NUMBER }, { "url", TextDataDetectType::URL },
44 { "email", TextDataDetectType::EMAIL }, { "location", TextDataDetectType::ADDRESS },
45 { "datetime", TextDataDetectType::DATE_TIME }
46 };
47
GetAIEntityMenu()48 void DataDetectorAdapter::GetAIEntityMenu()
49 {
50 auto context = PipelineContext::GetCurrentContextSafely();
51 CHECK_NULL_VOID(context);
52 auto uiTaskExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::UI);
53 uiTaskExecutor.PostTask(
54 [weak = AceType::WeakClaim(this), instanceId = context->GetInstanceId()] {
55 ContainerScope scope(instanceId);
56 auto dataDetectorAdapter = weak.Upgrade();
57 CHECK_NULL_VOID(dataDetectorAdapter);
58 TAG_LOGI(AceLogTag::ACE_TEXT, "Get AI entity menu from ai_engine");
59 DataDetectorMgr::GetInstance().GetAIEntityMenu(dataDetectorAdapter->textDetectResult_);
60 },
61 "ArkUITextInitDataDetect", PriorityType::VIP);
62 }
63
ShowAIEntityMenu(const AISpan & aiSpan,const NG::RectF & aiRect,const RefPtr<NG::FrameNode> & targetNode,bool isShowCopy,bool isShowSelectText)64 bool DataDetectorAdapter::ShowAIEntityMenu(const AISpan& aiSpan, const NG::RectF& aiRect,
65 const RefPtr<NG::FrameNode>& targetNode, bool isShowCopy, bool isShowSelectText)
66 {
67 if (textDetectResult_.menuOptionAndAction.empty()) {
68 TAG_LOGW(AceLogTag::ACE_TEXT, "menu option is empty, please try again");
69 GetAIEntityMenu();
70 return false;
71 }
72
73 mainContainerId_ = Container::CurrentId();
74 std::vector<std::pair<std::string, std::function<void()>>> menuOptions;
75 auto menuOptionAndAction = textDetectResult_.menuOptionAndAction[TEXT_DETECT_MAP.at(aiSpan.type)];
76 if (menuOptionAndAction.empty()) {
77 return false;
78 }
79 if (!isShowSelectText) {
80 // delete the last option: selectText.
81 menuOptionAndAction.pop_back();
82 if (!isShowCopy) {
83 // delete the last option: copy.
84 menuOptionAndAction.pop_back();
85 }
86 }
87
88 for (auto menuOption : menuOptionAndAction) {
89 std::function<void()> onClickEvent = [aiSpan, menuOption, weak = AceType::WeakClaim(this),
90 targetNodeWeak = AceType::WeakClaim(AceType::RawPtr(targetNode))]() {
91 auto dataDetectorAdapter = weak.Upgrade();
92 CHECK_NULL_VOID(dataDetectorAdapter);
93 auto targetNode = targetNodeWeak.Upgrade();
94 CHECK_NULL_VOID(targetNode);
95 dataDetectorAdapter->OnClickAIMenuOption(aiSpan, menuOption, targetNode);
96 };
97 menuOptions.push_back(std::make_pair(menuOption.first, onClickEvent));
98 }
99 auto pipeline = NG::PipelineContext::GetCurrentContextSafely();
100 CHECK_NULL_RETURN(pipeline, false);
101 auto overlayManager = pipeline->GetOverlayManager();
102 CHECK_NULL_RETURN(overlayManager, false);
103 return overlayManager->ShowAIEntityMenu(menuOptions, aiRect, targetNode);
104 }
105
OnClickAIMenuOption(const AISpan & aiSpan,const std::pair<std::string,FuncVariant> & menuOption,const RefPtr<NG::FrameNode> & targetNode)106 void DataDetectorAdapter::OnClickAIMenuOption(const AISpan& aiSpan,
107 const std::pair<std::string, FuncVariant>& menuOption, const RefPtr<NG::FrameNode>& targetNode)
108 {
109 TAG_LOGI(AceLogTag::ACE_TEXT, "Click AI menu option: %{public}s", menuOption.first.c_str());
110 auto pipeline = NG::PipelineContext::GetCurrentContextSafely();
111 CHECK_NULL_VOID(pipeline);
112 auto overlayManager = pipeline->GetOverlayManager();
113 CHECK_NULL_VOID(overlayManager);
114 if (targetNode) {
115 overlayManager->CloseAIEntityMenu(targetNode->GetId());
116 }
117 if (mainContainerId_ == -1) {
118 mainContainerId_ = Container::CurrentId();
119 }
120 Container::UpdateCurrent(mainContainerId_);
121
122 auto runtimeContext = Platform::AceContainer::GetRuntimeContext(pipeline->GetInstanceId());
123 CHECK_NULL_VOID(runtimeContext);
124 auto token = runtimeContext->GetToken();
125 auto bundleName = runtimeContext->GetBundleName();
126
127 hasClickedMenuOption_ = true;
128 if (onClickMenu_ && std::holds_alternative<std::function<std::string()>>(menuOption.second)) {
129 onClickMenu_(std::get<std::function<std::string()>>(menuOption.second)());
130 } else if (std::holds_alternative<std::function<void(sptr<IRemoteObject>, std::string)>>(menuOption.second)) {
131 std::get<std::function<void(sptr<IRemoteObject>, std::string)>>(menuOption.second)(token, aiSpan.content);
132 } else if (std::holds_alternative<std::function<void(int32_t, std::string)>>(menuOption.second)) {
133 std::get<std::function<void(int32_t, std::string)>>(menuOption.second)(mainContainerId_, aiSpan.content);
134 } else if (std::holds_alternative<std::function<void(int32_t, std::string, std::string, int32_t, std::string)>>(
135 menuOption.second)) {
136 std::get<std::function<void(int32_t, std::string, std::string, int32_t, std::string)>>(menuOption.second)(
137 mainContainerId_, UtfUtils::Str16DebugToStr8(textForAI_), bundleName, aiSpan.start, aiSpan.content);
138 TAG_LOGI(AceLogTag::ACE_TEXT, "textForAI:%{public}d, start:%{public}d, aiSpan.length:%{public}d",
139 static_cast<int32_t>(textForAI_.length()), aiSpan.start, static_cast<int32_t>(aiSpan.content.length()));
140 } else {
141 TAG_LOGW(AceLogTag::ACE_TEXT, "No matching menu option");
142 }
143 hasClickedMenuOption_ = false;
144 }
145
ResponseBestMatchItem(const AISpan & aiSpan)146 void DataDetectorAdapter::ResponseBestMatchItem(const AISpan& aiSpan)
147 {
148 if (textDetectResult_.menuOptionAndAction.empty()) {
149 TAG_LOGW(AceLogTag::ACE_TEXT, "menu option is empty, please try again");
150 GetAIEntityMenu();
151 return;
152 }
153 auto menuOptions = textDetectResult_.menuOptionAndAction[TEXT_DETECT_MAP.at(aiSpan.type)];
154 if (menuOptions.empty()) {
155 TAG_LOGW(AceLogTag::ACE_TEXT, "menu option is empty");
156 return;
157 }
158 OnClickAIMenuOption(aiSpan, menuOptions[0]);
159 }
160
SetTextDetectTypes(const std::string & textDetectTypes)161 void DataDetectorAdapter::SetTextDetectTypes(const std::string& textDetectTypes)
162 {
163 auto types = textDetectTypes;
164 if (types.empty()) {
165 types.append(ALL_TEXT_DETECT_TYPES);
166 }
167
168 std::set<std::string> newTypesSet;
169 std::istringstream iss(types);
170 std::string type;
171 while (std::getline(iss, type, ',')) {
172 newTypesSet.insert(type);
173 }
174 std::string detectTypesResult;
175 bool hasUrlType = false;
176 std::for_each(newTypesSet.begin(), newTypesSet.end(), [&hasUrlType, &detectTypesResult](const std::string& type) {
177 if (type == "url") {
178 hasUrlType = true;
179 } else {
180 detectTypesResult.append(type).append(",");
181 }
182 });
183 if (!detectTypesResult.empty()) {
184 detectTypesResult.pop_back();
185 }
186 hasUrlType_ = hasUrlType;
187 textDetectTypes_ = detectTypesResult;
188 if (newTypesSet != textDetectTypesSet_) {
189 textDetectTypesSet_ = newTypesSet;
190 typeChanged_ = true;
191 aiDetectInitialized_ = false;
192 MarkDirtyNode();
193 }
194 }
195
ParseOriText(const std::unique_ptr<JsonValue> & entityJson,std::u16string & text)196 bool DataDetectorAdapter::ParseOriText(const std::unique_ptr<JsonValue>& entityJson, std::u16string& text)
197 {
198 TAG_LOGI(AceLogTag::ACE_TEXT, "Parse origin text entry");
199 auto runtimeContext = Platform::AceContainer::GetRuntimeContext(Container::CurrentId());
200 CHECK_NULL_RETURN(runtimeContext, false);
201 if (runtimeContext->GetBundleName() != entityJson->GetString("bundleName")) {
202 TAG_LOGW(AceLogTag::ACE_TEXT,
203 "Wrong bundleName, the context bundleName is: %{public}s, but your bundleName is: %{public}s",
204 runtimeContext->GetBundleName().c_str(), entityJson->GetString("bundleName").c_str());
205 return false;
206 }
207 auto aiSpanArray = entityJson->GetValue("entity");
208 if (aiSpanArray->IsNull() || !aiSpanArray->IsArray()) {
209 TAG_LOGW(AceLogTag::ACE_TEXT, "Wrong AI entity");
210 return false;
211 }
212
213 aiSpanMap_.clear();
214 aiSpanRects_.clear();
215 detectTexts_.clear();
216 AISpan aiSpan;
217 for (int32_t i = 0; i < aiSpanArray->GetArraySize(); ++i) {
218 auto item = aiSpanArray->GetArrayItem(i);
219 aiSpan.content = item->GetString("entityContent");
220 aiSpan.type = TEXT_DETECT_MAP_REVERSE.at(item->GetString("entityType"));
221 aiSpan.start = item->GetInt("start");
222 aiSpan.end = item->GetInt("end");
223 aiSpanMap_[aiSpan.start] = aiSpan;
224 }
225 aiDetectInitialized_ = true;
226 text = UtfUtils::Str8ToStr16(entityJson->GetString("content"));
227 textForAI_ = text;
228 lastTextForAI_ = textForAI_;
229 if (textDetectResult_.menuOptionAndAction.empty()) {
230 GetAIEntityMenu();
231 }
232
233 TAG_LOGI(AceLogTag::ACE_TEXT, "Parse origin text successful");
234 return true;
235 }
236
PreprocessTextDetect()237 void DataDetectorAdapter::PreprocessTextDetect()
238 {
239 aiDetectFlag_ = 0;
240 if (textDetectTypes_.empty() && !hasUrlType_) {
241 textDetectTypes_ = TEXT_DETECT_TYPES_WITHOUT_URL;
242 hasUrlType_ = true;
243 }
244 if (textDetectTypes_.empty()) {
245 aiDetectFlag_ |= OTHER_DETECT_FINISH;
246 }
247 if (hasUrlType_) {
248 aiDetectFlag_ |= URL_DETECT_FINISH;
249 }
250 }
251
InitTextDetect(int32_t startPos,std::string detectText)252 void DataDetectorAdapter::InitTextDetect(int32_t startPos, std::string detectText)
253 {
254 CHECK_NULL_VOID(!textDetectTypes_.empty());
255 TextDataDetectInfo info;
256 info.text = detectText;
257 info.module = textDetectTypes_;
258
259 auto context = PipelineContext::GetCurrentContextSafelyWithCheck();
260 CHECK_NULL_VOID(context);
261 int32_t instanceID = context->GetInstanceId();
262 auto textFunc = [weak = WeakClaim(this),
263 instanceID, startPos, detectTypesSet = textDetectTypesSet_](const TextDataDetectResult result) {
264 ContainerScope scope(instanceID);
265 auto context = PipelineContext::GetCurrentContextSafelyWithCheck();
266 CHECK_NULL_VOID(context);
267 auto uiTaskExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::UI);
268 uiTaskExecutor.PostTask(
269 [result, weak, instanceID, startPos, detectTypesSet] {
270 ContainerScope scope(instanceID);
271 auto dataDetectorAdapter = weak.Upgrade();
272 CHECK_NULL_VOID(dataDetectorAdapter);
273 if (detectTypesSet != dataDetectorAdapter->textDetectTypesSet_) {
274 return;
275 }
276 dataDetectorAdapter->ParseAIResult(result, startPos);
277 dataDetectorAdapter->MarkDirtyNode();
278 },
279 "ArkUITextParseAIResult", PriorityType::IMMEDIATE);
280 };
281
282 auto backgroundExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::BACKGROUND);
283 backgroundExecutor.PostTask(
284 [info, textFunc] {
285 TAG_LOGI(AceLogTag::ACE_TEXT, "DataDetectorAdapter::InitTextDetect, start AI detect, length: %{public}zu",
286 info.text.size());
287 DataDetectorMgr::GetInstance().DataDetect(info, textFunc);
288 },
289 "ArkUITextInitDataDetect", PriorityType::IMMEDIATE);
290 }
291
HandleTextUrlDetect()292 void DataDetectorAdapter::HandleTextUrlDetect()
293 {
294 auto context = PipelineContext::GetCurrentContextSafelyWithCheck();
295 CHECK_NULL_VOID(context);
296 int32_t instanceID = context->GetInstanceId();
297 auto textFunc = [weak = WeakClaim(this),
298 instanceID, detectTypesSet = textDetectTypesSet_](const std::vector<UrlEntity>& urlEntities) {
299 ContainerScope scope(instanceID);
300 auto context = PipelineContext::GetCurrentContextSafelyWithCheck();
301 CHECK_NULL_VOID(context);
302 auto uiTaskExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::UI);
303 uiTaskExecutor.PostTask(
304 [urlEntities, weak, instanceID, detectTypesSet] {
305 ContainerScope scope(instanceID);
306 auto dataDetectorAdapter = weak.Upgrade();
307 CHECK_NULL_VOID(dataDetectorAdapter);
308 if (detectTypesSet != dataDetectorAdapter->textDetectTypesSet_) {
309 return;
310 }
311 dataDetectorAdapter->HandleUrlResult(urlEntities);
312 dataDetectorAdapter->MarkDirtyNode();
313 },
314 "ArkUITextUrlParseResult", PriorityType::IMMEDIATE);
315 };
316
317 auto backgroundExecutor = SingleTaskExecutor::Make(context->GetTaskExecutor(), TaskExecutor::TaskType::BACKGROUND);
318 backgroundExecutor.PostTask(
319 [text = UtfUtils::Str16DebugToStr8(textForAI_), func = std::move(textFunc)] {
320 TAG_LOGI(AceLogTag::ACE_TEXT, "Start url entity detect using AI");
321 func(DataUrlAnalyzerMgr::GetInstance().AnalyzeUrls(text));
322 },
323 "ArkUITextInitUrlDetect", PriorityType::IMMEDIATE);
324 }
325
HandleUrlResult(std::vector<UrlEntity> urlEntities)326 void DataDetectorAdapter::HandleUrlResult(std::vector<UrlEntity> urlEntities)
327 {
328 for (const auto& entity : urlEntities) {
329 auto iter = aiSpanMap_.find(entity.charOffset);
330 if (iter != aiSpanMap_.end() && iter->second.content.length() >= entity.text.length()) {
331 // both entities start at the same position, leaving the longer one
332 continue;
333 }
334 AISpan aiSpan;
335 aiSpan.start = entity.charOffset;
336 aiSpan.end = aiSpan.start + static_cast<int32_t>(UtfUtils::Str8DebugToStr16(entity.text).length());
337 aiSpan.content = entity.text;
338 aiSpan.type = TextDataDetectType::URL;
339 aiSpanMap_[aiSpan.start] = aiSpan;
340 }
341 aiDetectFlag_ |= URL_DETECT_FINISH;
342 if (aiDetectFlag_ == ALL_DETECT_FINISH) {
343 FireFinalResult();
344 }
345 }
346
ParseAIResult(const TextDataDetectResult & result,int32_t startPos)347 void DataDetectorAdapter::ParseAIResult(const TextDataDetectResult& result, int32_t startPos)
348 {
349 TAG_LOGI(AceLogTag::ACE_TEXT, "DataDetectorAdapter::ParseAIResult, ResultLength: %{public}zu, id: %{public}i",
350 result.entity.size(), GetHost() ? GetHost()->GetId() : -1);
351 auto entityJson = JsonUtil::ParseJsonString(result.entity);
352 CHECK_NULL_VOID(entityJson);
353 for (const auto& type : TEXT_DETECT_MAP) {
354 auto jsonValue = entityJson->GetValue(type.second);
355 ParseAIJson(jsonValue, type.first, startPos);
356 }
357
358 if (startPos + AI_TEXT_MAX_LENGTH >= static_cast<int32_t>(textForAI_.length())) {
359 SetTextDetectResult(result);
360 aiDetectFlag_ |= OTHER_DETECT_FINISH;
361 if (aiDetectFlag_ == ALL_DETECT_FINISH) {
362 FireFinalResult();
363 }
364 }
365 }
366
FireFinalResult()367 void DataDetectorAdapter::FireFinalResult()
368 {
369 aiDetectInitialized_ = true;
370 auto entityJsonArray = JsonUtil::CreateArray(true);
371 // process with overlapping entities, leaving only the earlier ones
372 int32_t preEnd = 0;
373 auto aiSpanIterator = aiSpanMap_.begin();
374 while (aiSpanIterator != aiSpanMap_.end()) {
375 auto aiSpan = aiSpanIterator->second;
376 if (aiSpan.start < preEnd) {
377 aiSpanIterator = aiSpanMap_.erase(aiSpanIterator);
378 } else {
379 preEnd = aiSpan.end;
380 ++aiSpanIterator;
381 auto aiSpanJson = JsonUtil::Create(true);
382 aiSpanJson->Put("start", aiSpan.start);
383 aiSpanJson->Put("end", aiSpan.end);
384 aiSpanJson->Put("entityContent", aiSpan.content.c_str());
385 aiSpanJson->Put("entityType", TEXT_DETECT_MAP.at(aiSpan.type).c_str());
386 entityJsonArray->Put(aiSpanJson);
387 }
388 }
389 auto resultJson = JsonUtil::Create(true);
390 resultJson->Put("entity", entityJsonArray);
391 resultJson->Put("code", textDetectTypes_.empty() && hasUrlType_ ? 0: textDetectResult_.code);
392 FireOnResult(resultJson->ToString());
393 aiDetectFlag_ = 0;
394 }
395
ParseAIJson(const std::unique_ptr<JsonValue> & jsonValue,TextDataDetectType type,int32_t startPos)396 void DataDetectorAdapter::ParseAIJson(
397 const std::unique_ptr<JsonValue>& jsonValue, TextDataDetectType type, int32_t startPos)
398 {
399 if (!jsonValue || !jsonValue->IsArray()) {
400 TAG_LOGW(AceLogTag::ACE_TEXT, "Wrong AI result");
401 return;
402 }
403
404 for (int32_t i = 0; i < jsonValue->GetArraySize(); ++i) {
405 auto item = jsonValue->GetArrayItem(i);
406 auto charOffset = item->GetInt("charOffset");
407 auto oriText = item->GetString("oriText");
408 auto wOriText = UtfUtils::Str8ToStr16(oriText);
409 int32_t end = startPos + charOffset + static_cast<int32_t>(wOriText.length());
410 if (charOffset < 0 || startPos + charOffset >= static_cast<int32_t>(textForAI_.length()) ||
411 end >= startPos + AI_TEXT_MAX_LENGTH || oriText.empty()) {
412 TAG_LOGW(AceLogTag::ACE_TEXT, "The result of AI is wrong");
413 continue;
414 }
415 if (wOriText != textForAI_.substr(startPos + charOffset, static_cast<int32_t>(wOriText.length()))) {
416 TAG_LOGW(AceLogTag::ACE_TEXT, "The charOffset is wrong");
417 continue;
418 }
419 int32_t start = startPos + charOffset;
420 auto iter = aiSpanMap_.find(start);
421 if (iter != aiSpanMap_.end() && iter->second.content.length() >= oriText.length()) {
422 // both entities start at the same position, leaving the longer one
423 continue;
424 }
425
426 TimeStamp currentDetectorTimeStamp = std::chrono::high_resolution_clock::now();
427 std::chrono::duration<float, std::ratio<1, SECONDS_TO_MILLISECONDS>> costTime =
428 currentDetectorTimeStamp - startDetectorTimeStamp_;
429 item->Put("costTime", costTime.count());
430 item->Put("resultCode", textDetectResult_.code);
431 entityJson_[start] = item->ToString();
432 TAG_LOGI(AceLogTag::ACE_TEXT, "size of the entity json is: %{public}zu", entityJson_[start].size());
433
434 AISpan aiSpan;
435 aiSpan.start = start;
436 aiSpan.end = end;
437 aiSpan.content = oriText;
438 aiSpan.type = type;
439 aiSpanMap_[aiSpan.start] = aiSpan;
440 }
441 }
442
GetDetectDelayTask(const std::map<int32_t,AISpan> & aiSpanMap)443 std::function<void()> DataDetectorAdapter::GetDetectDelayTask(const std::map<int32_t, AISpan>& aiSpanMap)
444 {
445 return [aiSpanMap, weak = WeakClaim(this)]() {
446 auto dataDetectorAdapter = weak.Upgrade();
447 CHECK_NULL_VOID(dataDetectorAdapter && !dataDetectorAdapter->textForAI_.empty());
448 TAG_LOGI(AceLogTag::ACE_TEXT, "DataDetectorAdapter, delayed whole task executed, id: %{public}i",
449 dataDetectorAdapter->GetHost() ? dataDetectorAdapter->GetHost()->GetId() : -1);
450 dataDetectorAdapter->lastTextForAI_ = dataDetectorAdapter->textForAI_;
451 size_t detectTextIdx = 0;
452 auto aiSpanMapIt = aiSpanMap.begin();
453 int32_t startPos = 0;
454 bool hasSame = false;
455 auto wTextForAI = dataDetectorAdapter->textForAI_;
456 auto wTextForAILength = static_cast<int32_t>(wTextForAI.length());
457 dataDetectorAdapter->PreprocessTextDetect();
458 do {
459 std::string detectText = UtfUtils::Str16DebugToStr8(
460 wTextForAI.substr(startPos, std::min(AI_TEXT_MAX_LENGTH, wTextForAILength - startPos)));
461 bool isSameDetectText = detectTextIdx < dataDetectorAdapter->detectTexts_.size() &&
462 detectText == dataDetectorAdapter->detectTexts_[detectTextIdx];
463 while (!aiSpanMap.empty() && aiSpanMapIt != aiSpanMap.end() && aiSpanMapIt->first >= 0 &&
464 aiSpanMapIt->first < std::min(wTextForAILength, startPos + AI_TEXT_MAX_LENGTH - AI_TEXT_GAP)) {
465 auto aiContent = aiSpanMapIt->second.content;
466 auto wAIContent = StringUtils::ToWstring(aiContent);
467 if (isSameDetectText || aiContent == UtfUtils::Str16DebugToStr8(wTextForAI.substr(aiSpanMapIt->first,
468 std::min(static_cast<int32_t>(wAIContent.length()), wTextForAILength - aiSpanMapIt->first)))) {
469 dataDetectorAdapter->aiSpanMap_[aiSpanMapIt->first] = aiSpanMapIt->second;
470 hasSame = true;
471 }
472 ++aiSpanMapIt;
473 }
474 if (!isSameDetectText) {
475 dataDetectorAdapter->InitTextDetect(startPos, detectText);
476 if (detectTextIdx < dataDetectorAdapter->detectTexts_.size()) {
477 dataDetectorAdapter->detectTexts_[detectTextIdx] = detectText;
478 } else {
479 dataDetectorAdapter->detectTexts_.emplace_back(detectText);
480 }
481 }
482 ++detectTextIdx;
483 startPos += AI_TEXT_MAX_LENGTH - AI_TEXT_GAP;
484 } while (startPos + AI_TEXT_GAP < wTextForAILength && (!dataDetectorAdapter->textDetectTypes_.empty()));
485 if (dataDetectorAdapter->hasUrlType_) {
486 dataDetectorAdapter->HandleTextUrlDetect();
487 }
488 if (hasSame) {
489 dataDetectorAdapter->MarkDirtyNode();
490 }
491 };
492 }
493
StartAITask()494 void DataDetectorAdapter::StartAITask()
495 {
496 if (textForAI_.empty() || (!typeChanged_ && lastTextForAI_ == textForAI_)) {
497 MarkDirtyNode();
498 return;
499 }
500 std::map<int32_t, AISpan> aiSpanMapCopy;
501 if (!typeChanged_) {
502 aiSpanMapCopy = aiSpanMap_;
503 }
504 detectTexts_.clear();
505 aiSpanMap_.clear();
506 typeChanged_ = false;
507 startDetectorTimeStamp_ = std::chrono::high_resolution_clock::now();
508 auto context = PipelineContext::GetCurrentContextSafely();
509 CHECK_NULL_VOID(context);
510 auto taskExecutor = context->GetTaskExecutor();
511 CHECK_NULL_VOID(taskExecutor);
512 aiDetectDelayTask_.Cancel();
513 aiDetectDelayTask_.Reset(GetDetectDelayTask(aiSpanMapCopy));
514 TAG_LOGI(AceLogTag::ACE_TEXT, "DataDetectorAdapter::StartAITask, post whole task, id: %{public}i",
515 GetHost() ? GetHost()->GetId() : -1);
516 taskExecutor->PostDelayedTask(
517 aiDetectDelayTask_, TaskExecutor::TaskType::UI, AI_DELAY_TIME, "ArkUITextStartAIDetect");
518 }
519
MarkDirtyNode() const520 void DataDetectorAdapter::MarkDirtyNode() const
521 {
522 auto host = GetHost();
523 CHECK_NULL_VOID(host);
524 host->MarkDirtyNode(NG::PROPERTY_UPDATE_MEASURE);
525 auto layoutProperty = host->GetLayoutProperty();
526 CHECK_NULL_VOID(layoutProperty);
527 layoutProperty->OnPropertyChangeMeasure();
528 }
529 } // namespace OHOS::Ace
530