• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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