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)42 void InputMethodManager::OnFocusNodeChange(const RefPtr<NG::FrameNode>& curFocusNode)
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     TAG_LOGI(AceLogTag::ACE_KEYBOARD, "current focus node info : (%{public}s/%{public}d).",
50         curFocusNode->GetTag().c_str(), curFocusNode->GetId());
51 
52     auto currentFocusNode = curFocusNode_.Upgrade();
53     if (currentFocusNode && currentFocusNode->GetTag() == V2::UI_EXTENSION_COMPONENT_ETS_TAG &&
54         currentFocusNode != curFocusNode) {
55         curFocusNode_ = curFocusNode;
56         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "UIExtension switch focus");
57         auto pattern = curFocusNode->GetPattern();
58         if (!pattern->NeedSoftKeyboard()) {
59             HideKeyboardAcrossProcesses();
60         }
61     }
62 
63     curFocusNode_ = curFocusNode;
64 #ifdef WINDOW_SCENE_SUPPORTED
65     auto isWindowScene = NG::WindowSceneHelper::IsWindowScene(curFocusNode);
66     if (isWindowScene) {
67         ProcessKeyboardInWindowScene(curFocusNode);
68     } else {
69         ProcessKeyboard(curFocusNode);
70     }
71 #else
72     CloseKeyboard(curFocusNode);
73 #endif
74 }
75 
ProcessKeyboardInWindowScene(const RefPtr<NG::FrameNode>& curFocusNode)76 void InputMethodManager::ProcessKeyboardInWindowScene(const RefPtr<NG::FrameNode>& curFocusNode)
77 {
78     if (curFocusNode && NG::WindowSceneHelper::IsFocusWindowSceneCloseKeyboard(curFocusNode)) {
79         lastKeep_ = true;
80     } else {
81         lastKeep_ = false;
82     }
83     // Frame other window to SCB window Or inSCB window changes,hide keyboard.
84     if ((windowFocus_.has_value() && windowFocus_.value())) {
85         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "SCB Window focus first, ready to hide keyboard.");
86         windowFocus_.reset();
87         NG::WindowSceneHelper::IsWindowSceneCloseKeyboard(curFocusNode);
88         return;
89     }
90     // In window scene, focus change, need close keyboard.
91     auto pattern = curFocusNode->GetPattern();
92     if (!pattern->NeedSoftKeyboard()) {
93         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "SCB WindowscenePage ready to close keyboard.");
94         NG::WindowSceneHelper::IsCloseKeyboard(curFocusNode);
95     }
96 }
97 
ProcessKeyboard(const RefPtr<NG::FrameNode>& curFocusNode)98 void InputMethodManager::ProcessKeyboard(const RefPtr<NG::FrameNode>& curFocusNode)
99 {
100     if (curFocusNode && curFocusNode->GetTag() == V2::SCREEN_ETS_TAG && lastKeep_) {
101         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "node is screen and last node want to save keyboard Ignore");
102         lastKeep_ = false;
103         return;
104     }
105     auto pipeline = curFocusNode->GetContextRefPtr();
106     CHECK_NULL_VOID(pipeline);
107     if (windowFocus_.has_value() && windowFocus_.value()) {
108         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Normal Window focus first, set focus flag to window.");
109         windowFocus_.reset();
110         auto callback = pipeline->GetWindowFocusCallback();
111         if (callback) {
112             TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Trigger Window Focus Callback");
113             callback();
114         } else {
115             TAG_LOGI(AceLogTag::ACE_KEYBOARD, "No Window Focus Callback");
116             if (!pipeline->NeedSoftKeyboard()) {
117                 HideKeyboardAcrossProcesses();
118             }
119         }
120         return;
121     }
122 
123     if (curFocusNode->GetTag() == V2::UI_EXTENSION_COMPONENT_ETS_TAG ||
124         curFocusNode->GetTag() == V2::EMBEDDED_COMPONENT_ETS_TAG) {
125         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "UIExtension(%{public}s/%{public}d) not need process.",
126             curFocusNode->GetTag().c_str(), curFocusNode->GetId());
127         return;
128     }
129 
130     auto container = Container::Current();
131     auto isUIExtension = container && container->IsUIExtensionWindow();
132     auto pattern = curFocusNode->GetPattern();
133     CHECK_NULL_VOID(pattern);
134     if (isUIExtension && !pattern->NeedSoftKeyboard()) {
135         HideKeyboardAcrossProcesses();
136     } else {
137         CloseKeyboard(curFocusNode);
138     }
139 }
140 
SetWindowFocus(bool windowFocus)141 void InputMethodManager::SetWindowFocus(bool windowFocus)
142 {
143     windowFocus_ = windowFocus;
144 }
145 
NeedSoftKeyboard() const146 bool InputMethodManager::NeedSoftKeyboard() const
147 {
148     auto currentFocusNode = curFocusNode_.Upgrade();
149     CHECK_NULL_RETURN(currentFocusNode, false);
150     auto pipeline = currentFocusNode->GetContextRefPtr();
151     if (pipeline) {
152         auto manager = AceType::DynamicCast<NG::TextFieldManagerNG>(pipeline->GetTextFieldManager());
153         if (manager && manager->GetLastRequestKeyboardId() == currentFocusNode->GetId()) {
154             TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Last RequestKeyboard node is current focus node, So keep");
155             return true;
156         }
157     }
158     if (currentFocusNode && (currentFocusNode->GetTag() == V2::UI_EXTENSION_COMPONENT_ETS_TAG ||
159                              currentFocusNode->GetTag() == V2::EMBEDDED_COMPONENT_ETS_TAG)) {
160         return true;
161     }
162     auto pattern = currentFocusNode->GetPattern();
163     return pattern->NeedSoftKeyboard() && pattern->NeedToRequestKeyboardOnFocus();
164 }
165 
CloseKeyboard()166 void InputMethodManager::CloseKeyboard()
167 {
168     ACE_LAYOUT_SCOPED_TRACE("CloseKeyboard");
169     auto currentFocusNode = curFocusNode_.Upgrade();
170     CHECK_NULL_VOID(currentFocusNode);
171     auto pipeline = currentFocusNode->GetContext();
172     CHECK_NULL_VOID(pipeline);
173     auto textFieldManager = pipeline->GetTextFieldManager();
174     CHECK_NULL_VOID(textFieldManager);
175     if (!textFieldManager->GetImeShow() && !textFieldManager->GetIsImeAttached()) {
176         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Ime Not Shown, Ime Not Attached, No need to close keyboard");
177         return;
178     }
179     textFieldManager->SetNeedToRequestKeyboard(false);
180 #if defined(ENABLE_STANDARD_INPUT)
181     // If pushpage, close it
182     TAG_LOGI(AceLogTag::ACE_KEYBOARD, "PageChange CloseKeyboard FrameNode notNeedSoftKeyboard.");
183     auto inputMethod = MiscServices::InputMethodController::GetInstance();
184     if (inputMethod) {
185         inputMethod->Close();
186         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "PageChange CloseKeyboard SoftKeyboard Closes Successfully.");
187     }
188 #endif
189 }
190 
CloseKeyboardInPipelineDestroy()191 void InputMethodManager::CloseKeyboardInPipelineDestroy()
192 {
193     TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Pipeline Destroyed, Ready to close SoftKeyboard.");
194     auto inputMethod = MiscServices::InputMethodController::GetInstance();
195     if (inputMethod) {
196         inputMethod->Close();
197         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "Pipelinne Destroyed, Close SoftKeyboard Successfully.");
198     }
199 }
200 
CloseKeyboard(const RefPtr<NG::FrameNode>& focusNode)201 void InputMethodManager::CloseKeyboard(const RefPtr<NG::FrameNode>& focusNode)
202 {
203 #if defined(ENABLE_STANDARD_INPUT)
204     // If focus pattern does not need softkeyboard, close it, not in windowScene.
205     auto curPattern = focusNode->GetPattern<NG::Pattern>();
206     CHECK_NULL_VOID(curPattern);
207     ACE_LAYOUT_SCOPED_TRACE("CloseKeyboard[id:%d]", focusNode->GetId());
208     bool isNeedKeyBoard = curPattern->NeedSoftKeyboard();
209     if (!isNeedKeyBoard) {
210         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "FrameNode(%{public}s/%{public}d) notNeedSoftKeyboard.",
211             focusNode->GetTag().c_str(), focusNode->GetId());
212         CloseKeyboard();
213     }
214 #endif
215 }
216 
HideKeyboardAcrossProcesses()217 void InputMethodManager::HideKeyboardAcrossProcesses()
218 {
219 #if defined(ENABLE_STANDARD_INPUT)
220     // If Nav, close it
221     TAG_LOGI(AceLogTag::ACE_KEYBOARD, "across processes CloseKeyboard FrameNode notNeedSoftKeyboard.");
222     auto inputMethod = MiscServices::InputMethodController::GetInstance();
223     if (inputMethod) {
224         inputMethod->RequestHideInput();
225         inputMethod->Close();
226         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "across processes CloseKeyboard SoftKeyboard Closes Successfully.");
227     }
228 #endif
229 }
230 
CloseKeyboardInProcess()231 void InputMethodManager::CloseKeyboardInProcess()
232 {
233     TAG_LOGI(AceLogTag::ACE_KEYBOARD, "CloseKeyboardInProcess Ready to close SoftKeyboard.");
234     auto inputMethod = MiscServices::InputMethodController::GetInstance();
235     if (inputMethod) {
236         inputMethod->Close();
237         TAG_LOGI(AceLogTag::ACE_KEYBOARD, "CloseKeyboardInProcess Close SoftKeyboard Successfully.");
238     }
239 }
240 
ProcessModalPageScene()241 void InputMethodManager::ProcessModalPageScene()
242 {
243     auto currentFocusNode = curFocusNode_.Upgrade();
244     TAG_LOGI(AceLogTag::ACE_KEYBOARD, "ProcessModalPageScene");
245     if (currentFocusNode && currentFocusNode->GetTag() == V2::UI_EXTENSION_COMPONENT_ETS_TAG) {
246         HideKeyboardAcrossProcesses();
247     } else {
248         CloseKeyboardInProcess();
249     }
250 }
251 } // namespace OHOS::Ace