• 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 "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