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#include <map>
16#include <mutex>
17
18#include "global.h"
19#include "input_method_controller.h"
20#include "input_method_utils.h"
21#include "inputmethod_controller_capi.h"
22#include "native_inputmethod_types.h"
23#include "native_inputmethod_utils.h"
24#include "native_text_changed_listener.h"
25using namespace OHOS::MiscServices;
26#ifdef __cplusplus
27extern "C" {
28#endif /* __cplusplus */
29
30struct InputMethod_InputMethodProxy {
31    InputMethod_TextEditorProxy *textEditor = nullptr;
32    OHOS::sptr<NativeTextChangedListener> listener = nullptr;
33    bool attached = false;
34};
35
36InputMethod_InputMethodProxy *g_inputMethodProxy = nullptr;
37std::mutex g_textEditorProxyMapMutex;
38
39InputMethod_ErrorCode IsValidInputMethodProxy(const InputMethod_InputMethodProxy *inputMethodProxy)
40{
41    if (inputMethodProxy == nullptr) {
42        IMSA_HILOGE("inputMethodProxy is nullptr");
43        return IME_ERR_NULL_POINTER;
44    }
45    std::lock_guard<std::mutex> guard(g_textEditorProxyMapMutex);
46    if (g_inputMethodProxy == nullptr) {
47        IMSA_HILOGE("g_inputMethodProxy is nullptr");
48        return IME_ERR_DETACHED;
49    }
50
51    if (g_inputMethodProxy != inputMethodProxy) {
52        IMSA_HILOGE("g_inputMethodProxy is not equal to inputMethodProxy");
53        return IME_ERR_PARAMCHECK;
54    }
55
56    if (!(g_inputMethodProxy->attached)) {
57        IMSA_HILOGE("g_inputMethodProxy is not attached");
58        return IME_ERR_DETACHED;
59    }
60
61    return IME_ERR_OK;
62}
63static InputMethod_ErrorCode GetInputMethodProxy(InputMethod_TextEditorProxy *textEditor)
64{
65    std::lock_guard<std::mutex> guard(g_textEditorProxyMapMutex);
66    if (g_inputMethodProxy != nullptr && textEditor == g_inputMethodProxy->textEditor) {
67        return IME_ERR_OK;
68    }
69
70    if (g_inputMethodProxy != nullptr && textEditor != g_inputMethodProxy->textEditor) {
71        g_inputMethodProxy->listener = nullptr;
72        delete g_inputMethodProxy;
73        g_inputMethodProxy = nullptr;
74    }
75    OHOS::sptr<NativeTextChangedListener> listener = new NativeTextChangedListener(textEditor);
76    if (listener == nullptr) {
77        IMSA_HILOGE("new NativeTextChangedListener failed");
78        return IME_ERR_NULL_POINTER;
79    }
80
81    g_inputMethodProxy = new InputMethod_InputMethodProxy({ textEditor, listener });
82    if (g_inputMethodProxy == nullptr) {
83        IMSA_HILOGE("new InputMethod_InputMethodProxy failed");
84        listener = nullptr;
85        return IME_ERR_NULL_POINTER;
86    }
87    return IME_ERR_OK;
88}
89#define CHECK_MEMBER_NULL(textEditor, member)   \
90    do {                                        \
91        if ((textEditor)->member == nullptr) {  \
92            IMSA_HILOGE(#member " is nullptr"); \
93            return IME_ERR_NULL_POINTER;        \
94        }                                       \
95    } while (0)
96static int32_t IsValidTextEditorProxy(InputMethod_TextEditorProxy *textEditor)
97{
98    if (textEditor == nullptr) {
99        IMSA_HILOGE("textEditor is nullptr");
100        return IME_ERR_NULL_POINTER;
101    }
102
103    CHECK_MEMBER_NULL(textEditor, getTextConfigFunc);
104    CHECK_MEMBER_NULL(textEditor, insertTextFunc);
105    CHECK_MEMBER_NULL(textEditor, deleteForwardFunc);
106    CHECK_MEMBER_NULL(textEditor, deleteBackwardFunc);
107    CHECK_MEMBER_NULL(textEditor, sendKeyboardStatusFunc);
108    CHECK_MEMBER_NULL(textEditor, sendEnterKeyFunc);
109    CHECK_MEMBER_NULL(textEditor, moveCursorFunc);
110    CHECK_MEMBER_NULL(textEditor, handleSetSelectionFunc);
111    CHECK_MEMBER_NULL(textEditor, handleExtendActionFunc);
112    CHECK_MEMBER_NULL(textEditor, getLeftTextOfCursorFunc);
113    CHECK_MEMBER_NULL(textEditor, getRightTextOfCursorFunc);
114    CHECK_MEMBER_NULL(textEditor, getTextIndexAtCursorFunc);
115    CHECK_MEMBER_NULL(textEditor, receivePrivateCommandFunc);
116    CHECK_MEMBER_NULL(textEditor, setPreviewTextFunc);
117    CHECK_MEMBER_NULL(textEditor, finishTextPreviewFunc);
118    return IME_ERR_OK;
119}
120
121static TextConfig ConstructTextConfig(const InputMethod_TextConfig &config)
122{
123    TextConfig textConfig = {
124        .inputAttribute = {
125            .inputPattern = static_cast<InputMethod_TextInputType>(config.inputType),
126            .enterKeyType = static_cast<InputMethod_EnterKeyType>(config.enterKeyType),
127            .isTextPreviewSupported = config.previewTextSupported,
128        },
129        .cursorInfo = {
130            .left = config.cursorInfo.left,
131            .top = config.cursorInfo.top,
132            .width = config.cursorInfo.width,
133            .height = config.cursorInfo.height,
134        },
135        .range = {
136            .start = config.selectionStart,
137            .end = config.selectionEnd,
138        },
139        .windowId = config.windowId,
140        .positionY = config.avoidInfo.positionY,
141        .height = config.avoidInfo.height,
142    };
143
144    return textConfig;
145}
146
147InputMethod_ErrorCode OH_InputMethodController_Attach(InputMethod_TextEditorProxy *textEditor,
148    InputMethod_AttachOptions *options, InputMethod_InputMethodProxy **inputMethodProxy)
149{
150    if ((IsValidTextEditorProxy(textEditor) != IME_ERR_OK) || options == nullptr || inputMethodProxy == nullptr) {
151        IMSA_HILOGE("invalid parameter");
152        return IME_ERR_NULL_POINTER;
153    }
154
155    InputMethod_ErrorCode errCode = GetInputMethodProxy(textEditor);
156    if (errCode != IME_ERR_OK) {
157        return errCode;
158    }
159
160    InputMethod_TextConfig config;
161    textEditor->getTextConfigFunc(textEditor, &config);
162
163    auto textConfig = ConstructTextConfig(config);
164
165    auto controller = InputMethodController::GetInstance();
166    OHOS::sptr<NativeTextChangedListener> listener = nullptr;
167    {
168        std::lock_guard<std::mutex> guard(g_textEditorProxyMapMutex);
169        if (g_inputMethodProxy != nullptr) {
170            listener = g_inputMethodProxy->listener;
171        }
172    }
173
174    int32_t err = controller->Attach(listener, options->showKeyboard, textConfig);
175    if (err == ErrorCode::NO_ERROR) {
176        errCode = IME_ERR_OK;
177        std::lock_guard<std::mutex> guard(g_textEditorProxyMapMutex);
178        if (g_inputMethodProxy != nullptr) {
179            g_inputMethodProxy->attached = true;
180        }
181        *inputMethodProxy = g_inputMethodProxy;
182    } else {
183        errCode = ErrorCodeConvert(err);
184    }
185
186    return errCode;
187}
188
189void ClearInputMethodProxy(void)
190{
191    std::lock_guard<std::mutex> guard(g_textEditorProxyMapMutex);
192    if (g_inputMethodProxy != nullptr) {
193        IMSA_HILOGI("g_inputMethodProxy is detached");
194        g_inputMethodProxy->attached = false;
195    }
196}
197
198InputMethod_ErrorCode OH_InputMethodController_Detach(InputMethod_InputMethodProxy *inputMethodProxy)
199{
200    if (inputMethodProxy == nullptr) {
201        IMSA_HILOGE("inputMethodProxy is nullptr");
202        return IME_ERR_NULL_POINTER;
203    }
204    {
205        std::lock_guard<std::mutex> guard(g_textEditorProxyMapMutex);
206        if (g_inputMethodProxy == nullptr) {
207            IMSA_HILOGE("g_inputMethodProxy is nullptr");
208            return IME_ERR_DETACHED;
209        }
210
211        if (g_inputMethodProxy != inputMethodProxy) {
212            IMSA_HILOGE("g_inputMethodProxy is not equal to inputMethodProxy");
213            return IME_ERR_PARAMCHECK;
214        }
215
216        IMSA_HILOGI("detach g_inputMethodProxy");
217        g_inputMethodProxy->listener = nullptr;
218        delete g_inputMethodProxy;
219        g_inputMethodProxy = nullptr;
220    }
221    return ErrorCodeConvert(InputMethodController::GetInstance()->Close());
222}
223#ifdef __cplusplus
224}
225#endif /* __cplusplus */