/*
 * Copyright (C) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define private public
#define protected public
#include "input_method_ability.h"
#include "input_method_controller.h"
#include "input_method_system_ability.h"
#include "task_manager.h"
#undef private

#include <gtest/gtest.h>
#include <gtest/hwext/gtest-multithread.h>
#include <string_ex.h>

#include "global.h"
#include "identity_checker_mock.h"
#include "input_attribute.h"
#include "input_method_engine_listener_impl.h"
#include "input_method_system_ability_proxy.h"
#include "input_method_system_ability_stub.h"
#include "keyboard_listener_test_impl.h"
#include "tdd_util.h"
#include "text_listener.h"

using namespace testing::ext;
namespace OHOS {
namespace MiscServices {
using namespace std::chrono;
using namespace testing::ext;
using namespace testing::mt;
class InputMethodAttachTest : public testing::Test {
public:
    static sptr<InputMethodController> inputMethodController_;
    static InputMethodAbility &inputMethodAbility_;
    static sptr<InputMethodSystemAbilityProxy> imsaProxy_;
    static sptr<InputMethodSystemAbility> imsa_;
    static constexpr int32_t EACH_THREAD_CIRCULATION_TIME = 100;
    static constexpr int32_t WAIT_TASK_EMPTY_TIMES = 100;
    static constexpr int32_t WAIT_TASK_EMPTY_INTERVAL = 20;
    static constexpr int32_t ATTACH_RETRY_INTERVAL = 50; // 50ms
    static constexpr int32_t ATTACH_MAX_RETRY_TIMES = 30;  // 30 times
    static bool timeout_;
    static std::shared_ptr<AppExecFwk::EventHandler> textConfigHandler_;

    static void SetUpTestCase(void)
    {
        IMSA_HILOGI("InputMethodAttachTest::SetUpTestCase");
        IdentityCheckerMock::ResetParam();
        imsa_ = new (std::nothrow) InputMethodSystemAbility();
        if (imsa_ == nullptr) {
            return;
        }
        imsa_->OnStart();
        imsa_->identityChecker_ = std::make_shared<IdentityCheckerMock>();
        sptr<InputMethodSystemAbilityStub> serviceStub = imsa_;
        imsaProxy_ = new InputMethodSystemAbilityProxy(serviceStub->AsObject());
        if (imsaProxy_ == nullptr) {
            return;
        }
        IdentityCheckerMock::SetFocused(true);

        inputMethodAbility_.abilityManager_ = imsaProxy_;
        TddUtil::InitCurrentImePermissionInfo();
        IdentityCheckerMock::SetBundleName(TddUtil::currentBundleNameMock_);
        inputMethodAbility_.SetCoreAndAgent();
        std::shared_ptr<AppExecFwk::EventRunner> runner = AppExecFwk::EventRunner::Create("InputMethodAttachTest");
        textConfigHandler_ = std::make_shared<AppExecFwk::EventHandler>(runner);
        inputMethodAbility_.SetImeListener(std::make_shared<InputMethodEngineListenerImpl>(textConfigHandler_));

        inputMethodController_ = InputMethodController::GetInstance();
        inputMethodController_->abilityManager_ = imsaProxy_;
    }
    static void TearDownTestCase(void)
    {
        IMSA_HILOGI("InputMethodAttachTest::TearDownTestCase");
        IdentityCheckerMock::ResetParam();
        imsa_->OnStop();
    }
    void SetUp()
    {
        IMSA_HILOGI("InputMethodAttachTest::SetUp");
        TaskManager::GetInstance().SetInited(true);
    }
    void TearDown()
    {
        IMSA_HILOGI("InputMethodAttachTest::TearDown");
        inputMethodController_->Close();
        BlockRetry(WAIT_TASK_EMPTY_INTERVAL, WAIT_TASK_EMPTY_TIMES, IsTaskEmpty);
        TaskManager::GetInstance().Reset();
    }

    static void TestImfMultiThreadAttach()
    {
        for (int32_t i = 0; i < EACH_THREAD_CIRCULATION_TIME; ++i) {
            sptr<OnTextChangedListener> textListener = new TextListener();
            inputMethodController_->Attach(textListener, true);
        }
        IMSA_HILOGI("InputMethodAttachTest::end");
    }

    static bool IsTaskEmpty()
    {
        return TaskManager::GetInstance().curTask_ == nullptr && TaskManager::GetInstance().amsTasks_.empty() &&
               TaskManager::GetInstance().imaTasks_.empty() && TaskManager::GetInstance().imsaTasks_.empty() &&
               TaskManager::GetInstance().innerTasks_.empty();
    }
};
sptr<InputMethodController> InputMethodAttachTest::inputMethodController_;
InputMethodAbility &InputMethodAttachTest::inputMethodAbility_ = InputMethodAbility::GetInstance();
sptr<InputMethodSystemAbilityProxy> InputMethodAttachTest::imsaProxy_;
sptr<InputMethodSystemAbility> InputMethodAttachTest::imsa_;
bool InputMethodAttachTest::timeout_ { false };
std::shared_ptr<AppExecFwk::EventHandler> InputMethodAttachTest::textConfigHandler_ { nullptr };

/**
 * @tc.name: testAttach001
 * @tc.desc: test Attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testAttach001, TestSize.Level0)
{
    IMSA_HILOGI("test testAttach001 after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    auto ret = inputMethodController_->Attach(textListener);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    BlockRetry(WAIT_TASK_EMPTY_INTERVAL, WAIT_TASK_EMPTY_TIMES, IsTaskEmpty);

    int32_t keyType = -1;
    ret = inputMethodAbility_.GetEnterKeyType(keyType);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(keyType, 0);
    int32_t inputPattern = -1;
    ret = inputMethodAbility_.GetInputPattern(inputPattern);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    auto pattern = InputAttribute::PATTERN_TEXT;
    EXPECT_EQ(inputPattern, pattern);
}

/**
 * @tc.name: testAttach002
 * @tc.desc: test Attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testAttach002, TestSize.Level0)
{
    IMSA_HILOGI("test testAttach002 after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    auto ret = inputMethodController_->Attach(textListener, false);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    BlockRetry(WAIT_TASK_EMPTY_INTERVAL, WAIT_TASK_EMPTY_TIMES, IsTaskEmpty);

    int32_t keyType = -1;
    ret = inputMethodAbility_.GetEnterKeyType(keyType);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(keyType, 0);
    int32_t inputPattern = -1;
    ret = inputMethodAbility_.GetInputPattern(inputPattern);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    auto pattern = InputAttribute::PATTERN_TEXT;
    EXPECT_EQ(inputPattern, pattern);
}

/**
 * @tc.name: testAttach003
 * @tc.desc: test Attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testAttach003, TestSize.Level0)
{
    IMSA_HILOGI("test testAttach003 after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 2;
    attribute.enterKeyType = 1;
    auto ret = inputMethodController_->Attach(textListener, true, attribute);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    BlockRetry(WAIT_TASK_EMPTY_INTERVAL, WAIT_TASK_EMPTY_TIMES, IsTaskEmpty);

    int32_t keyType = -1;
    ret = inputMethodAbility_.GetEnterKeyType(keyType);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(keyType, attribute.enterKeyType);
    int32_t inputPattern = -1;
    ret = inputMethodAbility_.GetInputPattern(inputPattern);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(inputPattern, attribute.inputPattern);
}

/**
 * @tc.name: testAttach004
 * @tc.desc: test Attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testAttach004, TestSize.Level0)
{
    IMSA_HILOGI("test testAttach004 after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    auto ret = inputMethodController_->Attach(textListener, false, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    BlockRetry(WAIT_TASK_EMPTY_INTERVAL, WAIT_TASK_EMPTY_TIMES, IsTaskEmpty);

    int32_t keyType = -1;
    ret = inputMethodAbility_.GetEnterKeyType(keyType);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(keyType, config.inputAttribute.enterKeyType);
    int32_t inputPattern = -1;
    ret = inputMethodAbility_.GetInputPattern(inputPattern);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(inputPattern, config.inputAttribute.inputPattern);
}

/**
 * @tc.name: testAttach005
 * @tc.desc: test Attach, test optional param in TextConfig
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testAttach005, TestSize.Level0)
{
    IMSA_HILOGI("test testAttach005 after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    CursorInfo cursorInfo;
    cursorInfo.left = 0;
    cursorInfo.top = 1;
    cursorInfo.width = 0.5;
    cursorInfo.height = 1.2;
    config.cursorInfo = cursorInfo;
    Range selectionRange;
    selectionRange.start = 0;
    selectionRange.end = 2;
    config.range = selectionRange;
    config.windowId = 10;
    inputMethodController_->Close();
    auto ret = inputMethodController_->Attach(textListener, true, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    BlockRetry(WAIT_TASK_EMPTY_INTERVAL, WAIT_TASK_EMPTY_TIMES, IsTaskEmpty);

    int32_t keyType = -1;
    ret = inputMethodAbility_.GetEnterKeyType(keyType);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(keyType, config.inputAttribute.enterKeyType);
    int32_t inputPattern = -1;
    ret = inputMethodAbility_.GetInputPattern(inputPattern);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(inputPattern, config.inputAttribute.inputPattern);

    TextTotalConfig textConfig;
    ret = inputMethodAbility_.GetTextConfig(textConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(textConfig.inputAttribute, config.inputAttribute);
    EXPECT_EQ(textConfig.windowId, config.windowId);
    EXPECT_EQ(textConfig.cursorInfo, config.cursorInfo);
    EXPECT_EQ(textConfig.textSelection.newBegin, config.range.start);
    EXPECT_EQ(textConfig.textSelection.newEnd, config.range.end);
    EXPECT_EQ(textConfig.textSelection.oldBegin, INVALID_VALUE);
    EXPECT_EQ(textConfig.textSelection.oldEnd, INVALID_VALUE);
}

/**
 * @tc.name: testAttach006
 * @tc.desc: test Attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testAttach006, TestSize.Level0)
{
    IMSA_HILOGI("test testAttach006 attach.");
    InputMethodAttachTest::inputMethodController_->Close();
    TextListener::ResetParam();
    sptr<OnTextChangedListener> textListener = new TextListener();
    auto ret = InputMethodAttachTest::inputMethodController_->Attach(textListener, false);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_TRUE(TextListener::WaitSendKeyboardStatusCallback(KeyboardStatus::NONE));
    BlockRetry(WAIT_TASK_EMPTY_INTERVAL, WAIT_TASK_EMPTY_TIMES, IsTaskEmpty);

    InputMethodAttachTest::inputMethodController_->Close();
    TextListener::ResetParam();
    ret = InputMethodAttachTest::inputMethodController_->Attach(textListener, true);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_TRUE(TextListener::WaitSendKeyboardStatusCallback(KeyboardStatus::SHOW));
}

/**
 * @tc.name: testOnConfigurationChangeWithoutAttach
 * @tc.desc: test OnConfigurationChange without attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOnConfigurationChangeWithoutAttach, TestSize.Level0)
{
    IMSA_HILOGI("InputMethodAttachTest testOnConfigurationChangeWithoutAttach in.");
    Configuration config;
    EnterKeyType keyType = EnterKeyType::NEXT;
    config.SetEnterKeyType(keyType);
    TextInputType textInputType = TextInputType::DATETIME;
    config.SetTextInputType(textInputType);
    auto ret = inputMethodController_->OnConfigurationChange(config);
    EXPECT_EQ(ret, ErrorCode::ERROR_CLIENT_NOT_BOUND);
}

/**
 * @tc.name: testOnConfigurationChange
 * @tc.desc: test OnConfigurationChange after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOnConfigurationChange, TestSize.Level0)
{
    IMSA_HILOGI("test OnConfigurationChange after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    auto ret = inputMethodController_->Attach(textListener);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    BlockRetry(WAIT_TASK_EMPTY_INTERVAL, WAIT_TASK_EMPTY_TIMES, IsTaskEmpty);

    Configuration config;
    EnterKeyType keyType = EnterKeyType::NEXT;
    config.SetEnterKeyType(keyType);
    TextInputType textInputType = TextInputType::DATETIME;
    config.SetTextInputType(textInputType);
    ret = inputMethodController_->OnConfigurationChange(config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    int32_t keyType2;
    ret = inputMethodAbility_.GetEnterKeyType(keyType2);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(keyType2, (int)keyType);
    int32_t inputPattern;
    ret = inputMethodAbility_.GetInputPattern(inputPattern);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(inputPattern, (int)textInputType);
}

/**
 * @tc.name: testGetTextConfig
 * @tc.desc: test GetTextConfig of InputMethodAbility
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testGetTextConfig, TestSize.Level0)
{
    IMSA_HILOGI("test OnConfigurationChange001 after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    CursorInfo cursorInfo;
    cursorInfo.left = 0;
    cursorInfo.top = 1;
    cursorInfo.width = 0.5;
    cursorInfo.height = 1.2;
    config.cursorInfo = cursorInfo;
    Range selectionRange;
    selectionRange.start = 0;
    selectionRange.end = 2;
    config.range = selectionRange;
    config.windowId = 10;
    inputMethodController_->Close();
    auto ret = inputMethodController_->Attach(textListener, false, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    BlockRetry(WAIT_TASK_EMPTY_INTERVAL, WAIT_TASK_EMPTY_TIMES, IsTaskEmpty);
    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    EXPECT_EQ(totalConfig.inputAttribute, attribute);
    EXPECT_EQ(totalConfig.cursorInfo, cursorInfo);
    EXPECT_EQ(totalConfig.textSelection.newBegin, selectionRange.start);
    EXPECT_EQ(totalConfig.textSelection.newEnd, selectionRange.end);
    EXPECT_EQ(totalConfig.textSelection.oldBegin, INVALID_VALUE);
    EXPECT_EQ(totalConfig.textSelection.oldEnd, INVALID_VALUE);
    EXPECT_EQ(totalConfig.windowId, config.windowId);
}

/**
 * @tc.name: testOnCursorUpdateAfterAttach001
 * @tc.desc: test OnCursorUpdate after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOnCursorUpdateAfterAttach001, TestSize.Level0)
{
    IMSA_HILOGI("test testOnCursorUpdateAfterAttach001.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    auto ret = inputMethodController_->Attach(textListener);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    CursorInfo cursorInfo = { .top = 5, .left = 5, .height = 5, .width = 0.8 };
    ret = inputMethodController_->OnCursorUpdate(cursorInfo);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(totalConfig.cursorInfo, cursorInfo);
}

/**
 * @tc.name: testOnCursorUpdateAfterAttach002
 * @tc.desc: test OnCursorUpdate after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOnCursorUpdateAfterAttach002, TestSize.Level0)
{
    IMSA_HILOGI("test testOnCursorUpdateAfterAttach002.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    config.cursorInfo = { .top = 1, .left = 1, .height = 1, .width = 0.4 };
    auto ret = inputMethodController_->Attach(textListener, true, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    CursorInfo cursorInfo = { .top = 5, .left = 5, .height = 5, .width = 0.8 };
    ret = inputMethodController_->OnCursorUpdate(cursorInfo);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(totalConfig.cursorInfo, cursorInfo);
}

/**
 * @tc.name: testAttachWithInvalidImmersiveMode
 * @tc.desc: test Attach with invalid immersive mode.
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testAttachWithInvalidImmersiveMode, TestSize.Level0)
{
    IMSA_HILOGI("test testAttachWithInvalidImmersiveMode.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.immersiveMode = static_cast<int32_t>(ImmersiveMode::END);
    TextConfig config;
    config.inputAttribute = attribute;
    auto ret = inputMethodController_->Attach(textListener, true, config);
    EXPECT_EQ(ErrorCode::ERROR_PARAMETER_CHECK_FAILED, ret);
}

/**
 * @tc.name: testOnSelectionChangeAfterAttach002
 * @tc.desc: test OnSelectionChange after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOnSelectionChangeAfterAttach002, TestSize.Level0)
{
    IMSA_HILOGI("test testOnSelectionChangeAfterAttach002.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    config.range = { .start = 1, .end = 2 };
    inputMethodController_->Close();
    auto ret = inputMethodController_->Attach(textListener, false, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    int start = 0;
    int end = 1;
    ret = inputMethodController_->OnSelectionChange(Str8ToStr16("aaa"), start, end);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(totalConfig.textSelection.newBegin, start);
    EXPECT_EQ(totalConfig.textSelection.newEnd, end);
    EXPECT_EQ(totalConfig.textSelection.oldBegin, config.range.start);
    EXPECT_EQ(totalConfig.textSelection.oldEnd, config.range.end);
}

/**
 * @tc.name: testOnConfigurationChangeAfterAttach001
 * @tc.desc: test OnConfigurationChange after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOnConfigurationChangeAfterAttach001, TestSize.Level0)
{
    IMSA_HILOGI("test testOnConfigurationChangeAfterAttach001.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    auto ret = inputMethodController_->Attach(textListener);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    Configuration config;
    config.SetTextInputType(TextInputType::DATETIME);
    config.SetEnterKeyType(EnterKeyType::NEXT);
    ret = inputMethodController_->OnConfigurationChange(config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(totalConfig.inputAttribute.inputPattern, static_cast<int32_t>(TextInputType::DATETIME));
    EXPECT_EQ(totalConfig.inputAttribute.enterKeyType, static_cast<int32_t>(EnterKeyType::NEXT));
}

/**
 * @tc.name: testOnConfigurationChangeAfterAttach002
 * @tc.desc: test OnConfigurationChange after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOnConfigurationChangeAfterAttach002, TestSize.Level0)
{
    IMSA_HILOGI("test testOnConfigurationChangeAfterAttach002.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    attribute.immersiveMode = static_cast<int32_t>(ImmersiveMode::DARK_IMMERSIVE);
    TextConfig config;
    config.inputAttribute = attribute;
    auto ret = inputMethodController_->Attach(textListener, false, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    Configuration configuration;
    configuration.SetTextInputType(TextInputType::DATETIME);
    configuration.SetEnterKeyType(EnterKeyType::NEXT);
    ret = inputMethodController_->OnConfigurationChange(configuration);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(totalConfig.inputAttribute.inputPattern, static_cast<int32_t>(configuration.GetTextInputType()));
    EXPECT_EQ(totalConfig.inputAttribute.enterKeyType, static_cast<int32_t>(configuration.GetEnterKeyType()));
    EXPECT_EQ(totalConfig.inputAttribute.immersiveMode, static_cast<int32_t>(ImmersiveMode::DARK_IMMERSIVE));
}

/**
 * @tc.name: testSetCallingWindowAfterAttach002
 * @tc.desc: test SetCallingWindow after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testSetCallingWindowAfterAttach002, TestSize.Level0)
{
    IMSA_HILOGI("test testSetCallingWindowAfterAttach002.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    config.windowId = 88;
    auto ret = inputMethodController_->Attach(textListener, false, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    uint32_t windowId = 99;
    ret = inputMethodController_->SetCallingWindow(windowId);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
}

/**
 * @tc.name: testOnCursorUpdate001
 * @tc.desc: test OnCursorUpdate after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOnCursorUpdate001, TestSize.Level0)
{
    IMSA_HILOGI("test testOnCursorUpdate001.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    auto ret = inputMethodController_->Attach(textListener);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    CursorInfo cursorInfo = { .top = 5, .left = 5, .height = 5, .width = 0.8 };
    ret = inputMethodController_->OnCursorUpdate(cursorInfo);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    CursorInfo cursorInfo2 = { .top = 10, .left = 9, .width = 8, .height = 7 };
    config.cursorInfo = cursorInfo2;
    ret = inputMethodController_->Attach(textListener, false, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(totalConfig.cursorInfo, cursorInfo2);
}

/**
 * @tc.name: testOnSelectionChange
 * @tc.desc: test OnSelectionChange after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOnSelectionChange, TestSize.Level0)
{
    IMSA_HILOGI("test testOnSelectionChange.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    auto ret = inputMethodController_->Attach(textListener);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    int start = 0;
    int end = 1;
    ret = inputMethodController_->OnSelectionChange(Str8ToStr16("bbb"), start, end);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    config.range.start = 10;
    config.range.end = 20;
    ret = inputMethodController_->Attach(textListener, false, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(totalConfig.textSelection.newBegin, config.range.start);
    EXPECT_EQ(totalConfig.textSelection.newEnd, config.range.end);
    EXPECT_EQ(totalConfig.textSelection.oldBegin, start);
    EXPECT_EQ(totalConfig.textSelection.oldEnd, end);
}

/**
 * @tc.name: testOnConfigurationChange002
 * @tc.desc: test OnConfigurationChange after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOnConfigurationChange002, TestSize.Level0)
{
    IMSA_HILOGI("test testOnConfigurationChange002.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    auto ret = inputMethodController_->Attach(textListener);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    Configuration configuration;
    configuration.SetTextInputType(TextInputType::DATETIME);
    configuration.SetEnterKeyType(EnterKeyType::NEXT);
    ret = inputMethodController_->OnConfigurationChange(configuration);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    config.inputAttribute.enterKeyType = 5;
    config.inputAttribute.inputPattern = 5;
    config.inputAttribute.immersiveMode = static_cast<int32_t>(ImmersiveMode::LIGHT_IMMERSIVE);
    ret = inputMethodController_->Attach(textListener, false, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    EXPECT_EQ(totalConfig.inputAttribute, config.inputAttribute);
}

/**
 * @tc.name: testSetCallingWindow
 * @tc.desc: test SetCallingWindow after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testSetCallingWindow, TestSize.Level0)
{
    IMSA_HILOGI("test testSetCallingWindow.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    auto ret = inputMethodController_->Attach(textListener);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    uint32_t windowId = 88;
    ret = inputMethodController_->SetCallingWindow(windowId);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.windowId = 77;
    ret = inputMethodController_->Attach(textListener, false, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    EXPECT_EQ(totalConfig.windowId, config.windowId);
}
/**
 * @tc.name: testImeCallbackInAttach
 * @tc.desc: test ime can receive callback in Attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testImeCallbackInAttach, TestSize.Level0)
{
    IMSA_HILOGI("test testImeCallbackInAttach.");
    inputMethodAbility_.SetImeListener(std::make_shared<InputMethodEngineListenerImpl>());
    inputMethodAbility_.SetKdListener(std::make_shared<KeyboardListenerTestImpl>());
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputMethodEngineListenerImpl::ResetParam();
    KeyboardListenerTestImpl::ResetParam();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    CursorInfo cursorInfo;
    cursorInfo.left = 0;
    cursorInfo.top = 1;
    cursorInfo.width = 0.5;
    cursorInfo.height = 1.2;
    config.cursorInfo = cursorInfo;
    Range selectionRange;
    selectionRange.start = 5;
    selectionRange.end = 2;
    config.range = selectionRange;
    config.windowId = 10;
    auto ret = inputMethodController_->Attach(textListener, true, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    EXPECT_TRUE(KeyboardListenerTestImpl::WaitSelectionChange(selectionRange.start));
    EXPECT_TRUE(KeyboardListenerTestImpl::WaitCursorUpdate());
    EXPECT_TRUE(KeyboardListenerTestImpl::WaitEditorAttributeChange(attribute));
}

/**
 * @tc.name: TestImfMultiThreadAttach
 * @tc.desc: test ime Attach in multi-thread
 * @tc.type: FUNC
 * @tc.require:
 * @tc.author: mashaoyin
 */
HWTEST_F(InputMethodAttachTest, multiThreadAttachTest_001, TestSize.Level0)
{
    IMSA_HILOGI("InputMethodAttachTest multiThreadAttachTest_001 START");
    SET_THREAD_NUM(5);
    GTEST_RUN_TASK(InputMethodAttachTest::TestImfMultiThreadAttach);
}

/**
 * @tc.name: testAttach007
 * @tc.desc: test Attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testAttach007, TestSize.Level0)
{
    IMSA_HILOGI("test testAttach007 after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    AttachOptions attachOptions;
    attachOptions.isShowKeyboard = false;
    attachOptions.requestKeyboardReason = RequestKeyboardReason::NONE;
    auto ret = inputMethodController_->Attach(textListener, attachOptions, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    int32_t keyType = -1;
    ret = inputMethodAbility_.GetEnterKeyType(keyType);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(keyType, config.inputAttribute.enterKeyType);
    int32_t inputPattern = -1;
    ret = inputMethodAbility_.GetInputPattern(inputPattern);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(inputPattern, config.inputAttribute.inputPattern);
}

/**
 * @tc.name: testSetCapitalizeMode_001
 * @tc.desc: test set capitalizeMode after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testSetCapitalizeMode_001, TestSize.Level0)
{
    IMSA_HILOGI("test testSetCapitalizeMode_001.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 0;
    attribute.enterKeyType = 0;
    TextConfig config;
    config.inputAttribute = attribute;
    config.inputAttribute.capitalizeMode = CapitalizeMode::WORDS;
    auto ret = inputMethodController_->Attach(textListener, false, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    TextTotalConfig totalConfig;
    ret = inputMethodAbility_.GetTextConfig(totalConfig);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    EXPECT_EQ(totalConfig.inputAttribute.capitalizeMode, config.inputAttribute.capitalizeMode);
}

/**
 * @tc.name: testAttach008
 * @tc.desc: test set simple keyboard after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testAttach008, TestSize.Level0)
{
    IMSA_HILOGI("test testAttach008 after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    AttachOptions attachOptions;
    attachOptions.isShowKeyboard = false;
    attachOptions.requestKeyboardReason = RequestKeyboardReason::NONE;
    inputMethodController_->clientInfo_.config.isSimpleKeyboardEnabled = true;
    auto ret = inputMethodController_->Attach(textListener, attachOptions, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    inputMethodController_->clientInfo_.config.isSimpleKeyboardEnabled = false;
    ret = inputMethodController_->Attach(textListener, attachOptions, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
}

/**
 * @tc.name: testAttach009
 * @tc.desc: test set simple keyboard after attach
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testAttach009, TestSize.Level0)
{
    IMSA_HILOGI("test testAttach009 after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 8;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    AttachOptions attachOptions;
    attachOptions.isShowKeyboard = false;
    attachOptions.requestKeyboardReason = RequestKeyboardReason::NONE;
    inputMethodController_->clientInfo_.config.isSimpleKeyboardEnabled = true;
    auto ret = inputMethodController_->Attach(textListener, attachOptions, config);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
}

/**
 * @tc.name: testBlockRetry001
 * @tc.desc: test BlockRetry
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testBlockRetry001, TestSize.Level0)
{
    IMSA_HILOGI("test testBlockRetry001 after attach.");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute attribute;
    attribute.inputPattern = 3;
    attribute.enterKeyType = 2;
    TextConfig config;
    config.inputAttribute = attribute;
    AttachOptions attachOptions;
    attachOptions.isShowKeyboard = false;
    attachOptions.requestKeyboardReason = RequestKeyboardReason::NONE;
    auto task = [textListener, attachOptions, config, this](int32_t &res) {
        res = inputMethodController_->Attach(textListener, attachOptions, config);
        return res != ErrorCode::ERROR_TRY_IME_START_FAILED;
    };
    int32_t ret = ErrorCode::NO_ERROR;
    OHOS::MiscServices::BlockRetry(ATTACH_RETRY_INTERVAL, ATTACH_MAX_RETRY_TIMES, task, ret);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    auto task1 = [textListener, attachOptions, config, this](int32_t &res) {
        return res = false;
    };
    OHOS::MiscServices::BlockRetry(ATTACH_RETRY_INTERVAL, ATTACH_MAX_RETRY_TIMES, task1, ret);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
}

/**
 * @tc.name: testOninputstop001
 * @tc.desc: test OnInputStop callback
 * @tc.type: FUNC
 */
HWTEST_F(InputMethodAttachTest, testOninputstop001, TestSize.Level0)
{
    IMSA_HILOGI("test testOninputstop001 start!");
    sptr<OnTextChangedListener> textListener = new TextListener();
    ASSERT_NE(textListener, nullptr);
    auto ret = inputMethodController_->Attach(textListener);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    ret = inputMethodController_->Close();
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
}

/**
 * @tc.name: testGetCursorInfo_001
 * @tc.desc: IMC test GetCursorInfo with default userId
 * @tc.type: IMC
 * @tc.require:
 */
HWTEST_F(InputMethodAttachTest, testGetCursorInfo_001, TestSize.Level0)
{
    IMSA_HILOGI("IMC testGetCursorInfo_001 Test START");
    sptr<OnTextChangedListener> textListener = new TextListener();
    CursorInfo cursor = { 1.0, 10.0, 2.0, 7.0, 0 };
    Range selectionRange = { 1, 2 };
    InputAttribute attribute = { 1, 1 };
    uint32_t windowId = 10;
    TextConfig textConfig = {
        .inputAttribute = attribute, .cursorInfo = cursor, .range = selectionRange, .windowId = windowId
    };
    inputMethodController_->Attach(textListener, false, textConfig);

    auto ret = inputMethodController_->OnCursorUpdate(cursor);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);

    CursorInfo cursorInfo;
    int32_t userId = 100; // Default user ID for testing
    IdentityCheckerMock::ResetParam();
    IdentityCheckerMock::SetSystemApp(true);

    ret = inputMethodController_->GetCursorInfo(cursorInfo, userId);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    IMSA_HILOGI("cursorInfo0: left=%{public}f, top=%{public}f, width=%{public}f, height=%{public}f.",
        cursorInfo.left, cursorInfo.top, cursorInfo.width, cursorInfo.height);
    EXPECT_TRUE(cursorInfo == cursor);

    ret = inputMethodController_->GetCursorInfo(cursorInfo);
    EXPECT_EQ(ret, ErrorCode::NO_ERROR);
    IMSA_HILOGI("cursorInfo1: left=%{public}f, top=%{public}f, width=%{public}f, height=%{public}f.",
        cursorInfo.left, cursorInfo.top, cursorInfo.width, cursorInfo.height);
    EXPECT_TRUE(cursorInfo == cursor);

    IdentityCheckerMock::ResetParam();
}

/**
 * @tc.name: testGetCursorInfo_002
 * @tc.desc: IMC test GetCursorInfo without system permission
 * @tc.type: IMC
 * @tc.require:
 */
HWTEST_F(InputMethodAttachTest, testGetCursorInfo_002, TestSize.Level0)
{
    IMSA_HILOGI("IMC testGetCursorInfo_002 Test START");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute inputAttribute = { .isTextPreviewSupported = false };
    inputMethodController_->Attach(textListener, false, inputAttribute);

    CursorInfo cursorInfo;
    int32_t userId = 100;
    IdentityCheckerMock::ResetParam();
    IdentityCheckerMock::SetSystemApp(false); // Not system app

    auto ret = inputMethodController_->GetCursorInfo(cursorInfo, userId);

    EXPECT_EQ(ret, ErrorCode::ERROR_STATUS_SYSTEM_PERMISSION);

    IdentityCheckerMock::ResetParam();
}

/**
 * @tc.name: testGetCursorInfo_003
 * @tc.desc: IMC test GetCursorInfo with different userId
 * @tc.type: IMC
 * @tc.require:
 */
HWTEST_F(InputMethodAttachTest, testGetCursorInfo_003, TestSize.Level0)
{
    IMSA_HILOGI("IMC testGetCursorInfo_003 Test START");
    sptr<OnTextChangedListener> textListener = new TextListener();
    InputAttribute inputAttribute = { .isTextPreviewSupported = false };
    inputMethodController_->Attach(textListener, false, inputAttribute);
    CursorInfo cursorInfo;
    int32_t userId = 133; // Different user ID
    IdentityCheckerMock::ResetParam();
    IdentityCheckerMock::SetSystemApp(true);

    auto ret = inputMethodController_->GetCursorInfo(cursorInfo, userId);

    EXPECT_EQ(ret, ErrorCode::ERROR_USER_NOT_EXIST);

    IdentityCheckerMock::ResetParam();
}

/**
 * @tc.name: testGetCursorInfo_004
 * @tc.desc: IMC test GetCursorInfo without attach
 * @tc.type: IMC
 * @tc.require:
 */
HWTEST_F(InputMethodAttachTest, testGetCursorInfo_004, TestSize.Level0)
{
    IMSA_HILOGI("IMC testGetCursorInfo_004 Test START");
    inputMethodController_->Close();

    CursorInfo cursorInfo;
    int32_t userId = 100;
    IdentityCheckerMock::ResetParam();
    IdentityCheckerMock::SetSystemApp(true);

    auto ret = inputMethodController_->GetCursorInfo(cursorInfo, userId);

    // Should return error when not attached
    EXPECT_NE(ret, ErrorCode::NO_ERROR);

    IdentityCheckerMock::ResetParam();
}
} // namespace MiscServices
} // namespace OHOS