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