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