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/ime/input_method_manager.h"
17 #include "core/components_ng/pattern/text_field/text_field_manager.h"
18 #include "core/components_ng/pattern/window_scene/helper/window_scene_helper.h"
19 #include "core/pipeline_ng/pipeline_context.h"
20
21 #ifndef ACE_UNITTEST
22 #ifdef ENABLE_STANDARD_INPUT
23 #include "input_method_controller.h"
24 #endif
25 #endif
26
27 namespace OHOS::Ace {
28 std::unique_ptr<InputMethodManager> InputMethodManager::instance_ = nullptr;
29 std::mutex InputMethodManager::mtx_;
30
GetInstance()31 InputMethodManager* InputMethodManager::GetInstance()
32 {
33 if (instance_ == nullptr) {
34 std::lock_guard<std::mutex> lock(mtx_);
35 if (instance_ == nullptr) {
36 instance_.reset(new InputMethodManager);
37 }
38 }
39 return instance_.get();
40 }
41
OnFocusNodeChange(const RefPtr<NG::FrameNode> & curFocusNode,FocusReason focusReason)42 void InputMethodManager::OnFocusNodeChange(const RefPtr<NG::FrameNode>& curFocusNode, FocusReason focusReason)
43 {
44 auto container = Container::Current();
45 if (container && container->IsKeyboard()) {
46 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "focus in input method.");
47 return;
48 }
49 bool isDynamicComponent = container && container->GetUIContentType() == UIContentType::DYNAMIC_COMPONENT;
50 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "current focus node: (%{public}s/%{public}d). isDynamic: %{public}d",
51 curFocusNode->GetTag().c_str(), curFocusNode->GetId(), isDynamicComponent);
52 if (!isDynamicComponent) {
53 ManageFocusNode(curFocusNode, focusReason);
54 return;
55 }
56 auto containerHandler = container->GetContainerHandler();
57 if (!containerHandler) {
58 return;
59 }
60 auto callback = [weakNode = WeakPtr<NG::FrameNode>(curFocusNode), focusReason,
61 instanceId = curFocusNode->GetInstanceId()](bool saveKeyboard) {
62 auto context = NG::PipelineContext::GetContextByContainerId(instanceId);
63 CHECK_NULL_VOID(context);
64 auto taskExecutor = context->GetTaskExecutor();
65 CHECK_NULL_VOID(taskExecutor);
66 taskExecutor->PostTask(
67 [weakNode, focusReason, saveKeyboard]() {
68 auto curFocusNode = weakNode.Upgrade();
69 CHECK_NULL_VOID(curFocusNode);
70 InputMethodManager::GetInstance()->ManageFocusNode(curFocusNode, focusReason, saveKeyboard);
71 },
72 TaskExecutor::TaskType::UI, "ArkUIInputMethodManagerManageFocusNode");
73 };
74 containerHandler->GetHostFocusWindowSceneCloseKeyboard(callback);
75 }
76
ManageFocusNode(const RefPtr<NG::FrameNode> & curFocusNode,FocusReason focusReason,bool saveKeyboard)77 void InputMethodManager::ManageFocusNode(const RefPtr<NG::FrameNode>& curFocusNode, FocusReason focusReason,
78 bool saveKeyboard)
79 {
80 if (!curFocusNode) {
81 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "CurFocusNode Not Exist");
82 return;
83 }
84 bool lastFocusNodeExist = curFocusNode_.Upgrade() ? true : false;
85 if (lastFocusNodeExist && isLastFocusUIExtension_ && lastFocusNodeId_ != curFocusNode->GetId()) {
86 curFocusNode_ = curFocusNode;
87 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "UIExtension switch focus");
88 auto pattern = curFocusNode->GetPattern();
89 #ifdef WINDOW_SCENE_SUPPORTED
90 auto needCloseKeyboard = !NG::WindowSceneHelper::IsWindowScene(curFocusNode) ||
91 !NG::WindowSceneHelper::IsFocusWindowSceneCloseKeyboard(curFocusNode);
92 #else
93 auto needCloseKeyboard = true;
94 #endif
95 if (!pattern->NeedSoftKeyboard() && needCloseKeyboard) {
96 HideKeyboardAcrossProcesses();
97 }
98 }
99
100 isLastFocusUIExtension_ = curFocusNode->GetTag() == V2::UI_EXTENSION_COMPONENT_ETS_TAG;
101 lastFocusNodeId_ = curFocusNode->GetId();
102 curFocusNode_ = curFocusNode;
103 auto pattern = curFocusNode->GetPattern();
104 if (pattern) {
105 pattern->OnFocusNodeChange(focusReason);
106 }
107
108 if (saveKeyboard) {
109 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "No Need To Close Keyboard");
110 return;
111 }
112
113 #ifdef WINDOW_SCENE_SUPPORTED
114 auto isWindowScene = NG::WindowSceneHelper::IsWindowScene(curFocusNode);
115 if (isWindowScene) {
116 ProcessKeyboardInWindowScene(curFocusNode);
117 } else {
118 ProcessKeyboard(curFocusNode);
119 }
120 #else
121 CloseKeyboard(curFocusNode);
122 #endif
123 }
124
ProcessKeyboardInWindowScene(const RefPtr<NG::FrameNode> & curFocusNode)125 void InputMethodManager::ProcessKeyboardInWindowScene(const RefPtr<NG::FrameNode>& curFocusNode)
126 {
127 if (curFocusNode && NG::WindowSceneHelper::IsFocusWindowSceneCloseKeyboard(curFocusNode)) {
128 lastKeep_ = true;
129 } else {
130 lastKeep_ = false;
131 }
132 // Frame other window to SCB window Or inSCB window changes,hide keyboard.
133 if ((windowFocus_.has_value() && windowFocus_.value())) {
134 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "SCB Window focus first, ready to hide keyboard.");
135 windowFocus_.reset();
136 NG::WindowSceneHelper::IsWindowSceneCloseKeyboard(curFocusNode);
137 return;
138 }
139
140 if (curFocusNode->GetTag() == V2::UI_EXTENSION_COMPONENT_ETS_TAG ||
141 curFocusNode->GetTag() == V2::EMBEDDED_COMPONENT_ETS_TAG) {
142 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "UIExtension(%{public}s/%{public}d) not need to process.",
143 curFocusNode->GetTag().c_str(), curFocusNode->GetId());
144 return;
145 }
146
147 // In window scene, focus change, need close keyboard.
148 auto pattern = curFocusNode->GetPattern();
149 if (!pattern->NeedSoftKeyboard()) {
150 NG::WindowSceneHelper::IsCloseKeyboard(curFocusNode);
151 }
152 }
153
ProcessKeyboard(const RefPtr<NG::FrameNode> & curFocusNode)154 void InputMethodManager::ProcessKeyboard(const RefPtr<NG::FrameNode>& curFocusNode)
155 {
156 if (curFocusNode && curFocusNode->GetTag() == V2::SCREEN_ETS_TAG && lastKeep_) {
157 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "node is screen and last node want to save keyboard Ignore");
158 lastKeep_ = false;
159 return;
160 }
161 auto pipeline = curFocusNode->GetContextRefPtr();
162 CHECK_NULL_VOID(pipeline);
163 ACE_LAYOUT_SCOPED_TRACE("ProcessKeyboard [node:%s]", curFocusNode->GetTag().c_str());
164 if (windowFocus_.has_value() && windowFocus_.value()) {
165 windowFocus_.reset();
166 auto callback = pipeline->GetWindowFocusCallback();
167 if (callback) {
168 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Trigger Window Focus Callback");
169 callback();
170 } else {
171 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Trigger ArkWindow Focus Callback");
172 if (!pipeline->NeedSoftKeyboard()) {
173 HideKeyboardAcrossProcesses();
174 }
175 }
176 return;
177 }
178
179 if (curFocusNode->GetTag() == V2::UI_EXTENSION_COMPONENT_ETS_TAG ||
180 curFocusNode->GetTag() == V2::EMBEDDED_COMPONENT_ETS_TAG) {
181 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "UIExtension(%{public}s/%{public}d) not need process.",
182 curFocusNode->GetTag().c_str(), curFocusNode->GetId());
183 return;
184 }
185
186 auto container = Container::Current();
187 auto isUIExtension = container && container->IsUIExtensionWindow();
188 auto pattern = curFocusNode->GetPattern();
189 CHECK_NULL_VOID(pattern);
190 if (isUIExtension && !pattern->NeedSoftKeyboard() && pipeline->IsWindowFocused()) {
191 HideKeyboardAcrossProcesses();
192 } else {
193 CloseKeyboard(curFocusNode);
194 }
195 }
196
SetWindowFocus(bool windowFocus)197 void InputMethodManager::SetWindowFocus(bool windowFocus)
198 {
199 windowFocus_ = windowFocus;
200 }
201
NeedSoftKeyboard() const202 bool InputMethodManager::NeedSoftKeyboard() const
203 {
204 auto currentFocusNode = curFocusNode_.Upgrade();
205 CHECK_NULL_RETURN(currentFocusNode, false);
206 auto pipeline = currentFocusNode->GetContext();
207 if (pipeline) {
208 auto manager = AceType::DynamicCast<NG::TextFieldManagerNG>(pipeline->GetTextFieldManager());
209 if (manager && manager->GetLastRequestKeyboardId() == currentFocusNode->GetId()) {
210 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Last RequestKeyboard node is current focus node, So keep");
211 return true;
212 }
213 }
214 if (currentFocusNode && (currentFocusNode->GetTag() == V2::UI_EXTENSION_COMPONENT_ETS_TAG ||
215 currentFocusNode->GetTag() == V2::EMBEDDED_COMPONENT_ETS_TAG)) {
216 return true;
217 }
218 auto pattern = currentFocusNode->GetPattern();
219 return pattern->NeedSoftKeyboard() && pattern->NeedToRequestKeyboardOnFocus();
220 }
221
CloseKeyboard(bool disableNeedToRequestKeyboard)222 void InputMethodManager::CloseKeyboard(bool disableNeedToRequestKeyboard)
223 {
224 ACE_LAYOUT_SCOPED_TRACE("CloseKeyboard");
225 auto currentFocusNode = curFocusNode_.Upgrade();
226 CHECK_NULL_VOID(currentFocusNode);
227 auto pipeline = currentFocusNode->GetContext();
228 CHECK_NULL_VOID(pipeline);
229 auto textFieldManager = pipeline->GetTextFieldManager();
230 CHECK_NULL_VOID(textFieldManager);
231 if (!textFieldManager->GetImeShow() && !textFieldManager->GetIsImeAttached()) {
232 return;
233 }
234 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Ime Shown Or Attached, Need to close keyboard");
235 if (disableNeedToRequestKeyboard) {
236 textFieldManager->SetNeedToRequestKeyboard(false);
237 }
238 #if defined(ENABLE_STANDARD_INPUT)
239 auto inputMethod = MiscServices::InputMethodController::GetInstance();
240 if (!inputMethod) {
241 TAG_LOGW(AceLogTag::ACE_KEYBOARD, "Get InputMethodController Instance Failed");
242 return;
243 }
244 inputMethod->Close();
245 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "PageChange CloseKeyboard FrameNode notNeedSoftKeyboard.");
246 #endif
247 }
248
CloseKeyboardInPipelineDestroy()249 void InputMethodManager::CloseKeyboardInPipelineDestroy()
250 {
251 #if defined(ENABLE_STANDARD_INPUT)
252 auto inputMethod = MiscServices::InputMethodController::GetInstance();
253 if (!inputMethod) {
254 TAG_LOGW(AceLogTag::ACE_KEYBOARD, "Get InputMethodController Instance Failed");
255 return;
256 }
257 inputMethod->Close();
258 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Pipelinne Destroyed, Close SoftKeyboard Successfully.");
259 #endif
260 }
261
CloseKeyboard(const RefPtr<NG::FrameNode> & focusNode)262 void InputMethodManager::CloseKeyboard(const RefPtr<NG::FrameNode>& focusNode)
263 {
264 #if defined(ENABLE_STANDARD_INPUT)
265 // If focus pattern does not need softkeyboard, close it, not in windowScene.
266 auto curPattern = focusNode->GetPattern<NG::Pattern>();
267 CHECK_NULL_VOID(curPattern);
268 ACE_LAYOUT_SCOPED_TRACE("CloseKeyboard[id:%d]", focusNode->GetId());
269 bool isNeedKeyBoard = curPattern->NeedSoftKeyboard();
270 if (!isNeedKeyBoard) {
271 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "FrameNode(%{public}s/%{public}d) notNeedSoftKeyboard.",
272 focusNode->GetTag().c_str(), focusNode->GetId());
273 CloseKeyboard();
274 }
275 #endif
276 }
277
HideKeyboardAcrossProcesses()278 void InputMethodManager::HideKeyboardAcrossProcesses()
279 {
280 #if defined(ENABLE_STANDARD_INPUT)
281 auto inputMethod = MiscServices::InputMethodController::GetInstance();
282 if (!inputMethod) {
283 TAG_LOGW(AceLogTag::ACE_KEYBOARD, "Get InputMethodController Instance Failed");
284 return;
285 }
286 inputMethod->RequestHideInput();
287 inputMethod->Close();
288 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "across processes CloseKeyboard Successfully.");
289 #endif
290 }
291
CloseKeyboardInProcess()292 void InputMethodManager::CloseKeyboardInProcess()
293 {
294 #if defined(ENABLE_STANDARD_INPUT)
295 auto inputMethod = MiscServices::InputMethodController::GetInstance();
296 if (!inputMethod) {
297 TAG_LOGW(AceLogTag::ACE_KEYBOARD, "Get InputMethodController Instance Failed");
298 return;
299 }
300 inputMethod->Close();
301 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "CloseKeyboardInProcess Successfully.");
302 #endif
303 }
304
ProcessModalPageScene()305 void InputMethodManager::ProcessModalPageScene()
306 {
307 bool lastFocusNodeExist = curFocusNode_.Upgrade() ? true : false;
308 TAG_LOGI(AceLogTag::ACE_KEYBOARD, "ProcessModalPageScene");
309 if (lastFocusNodeExist && isLastFocusUIExtension_) {
310 HideKeyboardAcrossProcesses();
311 } else {
312 CloseKeyboardInProcess();
313 }
314 }
315 } // namespace OHOS::Ace