* Copyright (c) 2025 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.
*/
#include "refbase.h"
#include "wm_common.h"
#include "ime_info_inquirer.h"
struct AniEnv;
typedef struct AniEnv ani_env;
class AniObject;
typedef AniObject *ani_object;
namespace OHOS {
namespace Rosen {
class RSTransaction;
class Window : virtual public RefBase {};
class WindowOption : virtual public RefBase {};
class IWindowChangeListener : virtual public RefBase {
public:
virtual void OnSizeChange(Rect rect, WindowSizeChangeReason reason,
const std::shared_ptr<RSTransaction> &rsTransaction = nullptr)
{
}
};
class IKeyboardPanelInfoChangeListener : virtual public RefBase {
public:
virtual void OnKeyboardPanelInfoChanged(const KeyboardPanelInfo &keyboardPanelInfo) {}
};
class IWindowVisibilityChangedListener : virtual public RefBase {
public:
virtual void OnWindowVisibilityChangedCallback(const bool isVisible) {}
};
class IOcclusionStateChangedListener : virtual public RefBase {
public:
virtual void OnOcclusionStateChanged(const WindowVisibilityState state) {}
};
}
}
#define OHOS_ROSEN_WINDOW_H
#define private public
#include "input_method_ability.h"
#include "input_method_controller.h"
#include "task_manager.h"
#undef private
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "global.h"
#include "input_attribute.h"
#include "input_client_info.h"
#include "input_method_agent_proxy.h"
#include "input_method_agent_service_impl.h"
#include "input_method_core_service_impl.h"
#include "ime_mirror_manager.h"
#include "mock_input_method_system_ability_proxy.h"
#include "mock_iremote_object.h"
using namespace testing;
using namespace testing::ext;
namespace OHOS {
namespace MiscServices {
constexpr int32_t TEST_SA_ID = 1600004;
const std::string IME_MIRROR_NAME_LOCAL = "proxyIme_IME_MIRROR";
class ImeMirrorManagerTest : public testing::Test {
public:
static void SetUpTestSuite()
{
TaskManager::GetInstance().SetInited(true);
}
void SetUp() override
{
ima_ = &InputMethodAbility::GetInstance();
ima_->imeMirrorMgr_.SetImeMirrorEnable(false);
ima_->imeMirrorMgr_.UnSubscribeSaStart(TEST_SA_ID);
}
void TearDown() override {}
protected:
InputMethodAbility *ima_ = nullptr;
};
* @tc.name: SetImeMirrorEnable_True_then_IsImeMirrorEnable_ReturnsTrue
* @tc.desc: Verify SetImeMirrorEnable(true) makes IsImeMirrorEnable() return true
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorManagerTest, SetImeMirrorEnable_True_then_IsImeMirrorEnable_ReturnsTrue, TestSize.Level1)
{
ima_->imeMirrorMgr_.SetImeMirrorEnable(true);
EXPECT_TRUE(ima_->imeMirrorMgr_.IsImeMirrorEnable());
}
* @tc.name: SetImeMirrorEnable_False_then_IsImeMirrorEnable_ReturnsFalse
* @tc.desc: Verify SetImeMirrorEnable(false) makes IsImeMirrorEnable() return false
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorManagerTest, SetImeMirrorEnable_False_then_IsImeMirrorEnable_ReturnsFalse, TestSize.Level1)
{
ima_->imeMirrorMgr_.SetImeMirrorEnable(true);
EXPECT_TRUE(ima_->imeMirrorMgr_.IsImeMirrorEnable());
ima_->imeMirrorMgr_.SetImeMirrorEnable(false);
EXPECT_FALSE(ima_->imeMirrorMgr_.IsImeMirrorEnable());
}
* @tc.name: SubscribeSaStart_NullHandler_ReturnsFalse
* @tc.desc: Verify SubscribeSaStart returns false when handler is nullptr
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorManagerTest, SubscribeSaStart_NullHandler_ReturnsFalse, TestSize.Level1)
{
auto result = ima_->imeMirrorMgr_.SubscribeSaStart(nullptr, TEST_SA_ID);
EXPECT_FALSE(result);
}
* @tc.name: SubscribeSaStart_ValidHandler_ReturnsTrue
* @tc.desc: Verify SubscribeSaStart returns true when SystemAbilityManager is available
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorManagerTest, SubscribeSaStart_ValidHandler_ReturnsTrue, TestSize.Level1)
{
ima_->imeMirrorMgr_.UnSubscribeSaStart(TEST_SA_ID);
bool called = false;
auto handler = [&called]() { called = true; };
auto result = ima_->imeMirrorMgr_.SubscribeSaStart(handler, TEST_SA_ID);
EXPECT_TRUE(result);
}
class ImeMirrorAbilityTest : public testing::Test {
public:
static sptr<NiceMock<MockInputMethodSystemAbilityProxy>> mockProxy_;
static InputMethodAbility *ima_;
static void SetUpTestSuite()
{
ima_ = &InputMethodAbility::GetInstance();
mockProxy_ = new NiceMock<MockInputMethodSystemAbilityProxy>();
ima_->abilityManager_ = mockProxy_;
ON_CALL(*mockProxy_, BindImeMirror(_, _)).WillByDefault(Return(ErrorCode::NO_ERROR));
ON_CALL(*mockProxy_, UnbindImeMirror()).WillByDefault(Return(ErrorCode::NO_ERROR));
TaskManager::GetInstance().SetInited(true);
}
static void TearDownTestSuite()
{
ima_->abilityManager_ = nullptr;
ima_->agentStub_ = nullptr;
ima_->coreStub_ = nullptr;
mockProxy_ = nullptr;
}
void SetUp() override
{
ima_->isBound_.store(false);
ima_->imeMirrorMgr_.SetImeMirrorEnable(false);
ima_->abilityManager_ = mockProxy_;
if (ima_->agentStub_ == nullptr) {
ima_->agentStub_ = new InputMethodAgentServiceImpl();
}
if (ima_->coreStub_ == nullptr) {
ima_->coreStub_ = new InputMethodCoreServiceImpl();
}
ON_CALL(*mockProxy_, BindImeMirror(_, _)).WillByDefault(Return(ErrorCode::NO_ERROR));
ON_CALL(*mockProxy_, UnbindImeMirror()).WillByDefault(Return(ErrorCode::NO_ERROR));
TaskManager::GetInstance().SetInited(true);
}
void TearDown() override {}
};
sptr<NiceMock<MockInputMethodSystemAbilityProxy>> ImeMirrorAbilityTest::mockProxy_ = nullptr;
InputMethodAbility *ImeMirrorAbilityTest::ima_ = nullptr;
* @tc.name: BindImeMirror_AlreadyBound_ReturnsNoError
* @tc.desc: Verify BindImeMirror returns NO_ERROR immediately when already bound
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorAbilityTest, BindImeMirror_AlreadyBound_ReturnsNoError, TestSize.Level1)
{
ima_->isBound_.store(true);
auto ret = ima_->BindImeMirror();
EXPECT_EQ(ret, ErrorCode::NO_ERROR);
}
* @tc.name: BindImeMirror_NullProxy_ReturnsErrorServiceStartFailed
* @tc.desc: Verify BindImeMirror returns ERROR_SERVICE_START_FAILED when proxy is null
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorAbilityTest, BindImeMirror_NullProxy_ReturnsErrorServiceStartFailed, TestSize.Level1)
{
ima_->abilityManager_ = nullptr;
auto ret = ima_->BindImeMirror();
if (!ImeInfoInquirer::GetInstance().IsCapacitySupport("ime_mirror")) {
EXPECT_EQ(ret, ErrorCode::ERROR_DEVICE_UNSUPPORTED);
} else {
EXPECT_EQ(ret, ErrorCode::ERROR_NOT_AI_APP_IME);
}
}
* @tc.name: BindImeMirror_NullAgentStub_ReturnsErrorNullPointer
* @tc.desc: Verify BindImeMirror returns ERROR_NULL_POINTER when agentStub_ is null
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorAbilityTest, BindImeMirror_NullAgentStub_ReturnsErrorNullPointer, TestSize.Level1)
{
ima_->agentStub_ = nullptr;
auto ret = ima_->BindImeMirror();
EXPECT_EQ(ret, ErrorCode::ERROR_NULL_POINTER);
}
* @tc.name: BindImeMirror_Success_SetsBoundAndEnable
* @tc.desc: Verify successful BindImeMirror sets isBound_ true and ImeMirrorEnable true
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorAbilityTest, BindImeMirror_Success_SetsBoundAndEnable, TestSize.Level1)
{
auto ret = ima_->BindImeMirror();
EXPECT_EQ(ret, ErrorCode::NO_ERROR);
EXPECT_TRUE(ima_->isBound_.load());
EXPECT_TRUE(ima_->imeMirrorMgr_.IsImeMirrorEnable());
}
* @tc.name: BindImeMirror_ProxyReturnsError_PropagatesError
* @tc.desc: Verify BindImeMirror propagates error when proxy->BindImeMirror fails
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorAbilityTest, BindImeMirror_ProxyReturnsError_PropagatesError, TestSize.Level1)
{
ima_->isBound_.store(false);
ON_CALL(*mockProxy_, BindImeMirror(_, _)).WillByDefault(Return(ErrorCode::ERROR_NOT_AI_APP_IME));
auto ret = ima_->BindImeMirror();
EXPECT_EQ(ret, ErrorCode::ERROR_NOT_AI_APP_IME);
EXPECT_FALSE(ima_->isBound_.load());
EXPECT_FALSE(ima_->imeMirrorMgr_.IsImeMirrorEnable());
}
* @tc.name: UnbindImeMirror_ClearsBoundAndEnable
* @tc.desc: Verify UnbindImeMirror clears isBound_ and ImeMirrorEnable
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorAbilityTest, UnbindImeMirror_ClearsBoundAndEnable, TestSize.Level1)
{
ima_->isBound_.store(false);
auto ret = ima_->BindImeMirror();
EXPECT_EQ(ret, ErrorCode::NO_ERROR);
EXPECT_TRUE(ima_->isBound_.load());
EXPECT_TRUE(ima_->imeMirrorMgr_.IsImeMirrorEnable());
ret = ima_->UnbindImeMirror();
EXPECT_EQ(ret, ErrorCode::NO_ERROR);
EXPECT_FALSE(ima_->isBound_.load());
EXPECT_FALSE(ima_->imeMirrorMgr_.IsImeMirrorEnable());
}
* @tc.name: GetInputDataChannelProxyWrap_MirrorEnabled_ReturnsNull
* @tc.desc: Verify GetInputDataChannelProxyWrap returns nullptr when ImeMirror is enabled
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorAbilityTest, GetInputDataChannelProxyWrap_MirrorEnabled_ReturnsNull, TestSize.Level1)
{
ima_->imeMirrorMgr_.SetImeMirrorEnable(true);
auto result = ima_->GetInputDataChannelProxyWrap();
EXPECT_EQ(result, nullptr);
}
* @tc.name: GetInputDataChannelProxyWrap_MirrorDisabled_ReturnsChannel
* @tc.desc: Verify GetInputDataChannelProxyWrap returns the stored channel when mirror is disabled
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorAbilityTest, GetInputDataChannelProxyWrap_MirrorDisabled_ReturnsChannel, TestSize.Level1)
{
ima_->imeMirrorMgr_.SetImeMirrorEnable(false);
auto result = ima_->GetInputDataChannelProxyWrap();
EXPECT_EQ(result, nullptr);
}
* @tc.name: BindUnbindImeMirror_RepeatBothSucceed
* @tc.desc: Verify bind twice then unbind twice all succeed
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorAbilityTest, BindUnbindImeMirror_RepeatBothSucceed, TestSize.Level1)
{
ima_->isBound_.store(false);
auto ret = ima_->BindImeMirror();
EXPECT_EQ(ret, ErrorCode::NO_ERROR);
EXPECT_TRUE(ima_->isBound_.load());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
ret = ima_->BindImeMirror();
EXPECT_EQ(ret, ErrorCode::NO_ERROR);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
ret = ima_->UnbindImeMirror();
EXPECT_EQ(ret, ErrorCode::NO_ERROR);
EXPECT_FALSE(ima_->isBound_.load());
ret = ima_->UnbindImeMirror();
EXPECT_EQ(ret, ErrorCode::NO_ERROR);
}
class ImeMirrorControllerTest : public testing::Test {
public:
static sptr<NiceMock<MockInputMethodSystemAbilityProxy>> mockProxy_;
static sptr<InputMethodController> imc_;
static void SetUpTestSuite()
{
imc_ = InputMethodController::GetInstance();
mockProxy_ = new NiceMock<MockInputMethodSystemAbilityProxy>();
MockInputMethodSystemAbilityProxy::SetImsaProxyForTest(imc_, mockProxy_);
ON_CALL(*mockProxy_, BindImeMirror(_, _)).WillByDefault(Return(ErrorCode::NO_ERROR));
ON_CALL(*mockProxy_, UnbindImeMirror()).WillByDefault(Return(ErrorCode::NO_ERROR));
TaskManager::GetInstance().SetInited(true);
}
static void TearDownTestSuite()
{
MockInputMethodSystemAbilityProxy::SetImsaProxyForTest(imc_, nullptr);
mockProxy_ = nullptr;
}
void SetUp() override
{
imc_->agentInfoList_.clear();
imc_->isBound_.store(false);
imc_->isEditable_.store(false);
imc_->textConfig_ = TextConfig();
}
void TearDown() override {}
protected:
void AddMirrorAgent(sptr<IRemoteObject> agentObj)
{
InputMethodController::AgentInfo info;
info.agentObject = agentObj;
info.agent = std::make_shared<InputMethodAgentProxy>(agentObj);
info.imeType = ImeType::IME_MIRROR;
imc_->agentInfoList_.push_back(info);
}
void AddNormalAgent(sptr<IRemoteObject> agentObj)
{
InputMethodController::AgentInfo info;
info.agentObject = agentObj;
info.agent = std::make_shared<InputMethodAgentProxy>(agentObj);
info.imeType = ImeType::NONE;
imc_->agentInfoList_.push_back(info);
}
};
sptr<NiceMock<MockInputMethodSystemAbilityProxy>> ImeMirrorControllerTest::mockProxy_ = nullptr;
sptr<InputMethodController> ImeMirrorControllerTest::imc_ = nullptr;
* @tc.name: SetAgent_WithMirrorName_SetsImeTypeToMirror
* @tc.desc: Verify SetAgent with IME_MIRROR_NAME_LOCAL sets imeType to IME_MIRROR
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorControllerTest, SetAgent_WithMirrorName_SetsImeTypeToMirror, TestSize.Level1)
{
auto agentObj = new NiceMock<MockIRemoteObject>();
imc_->SetAgent(agentObj, IME_MIRROR_NAME_LOCAL);
ASSERT_EQ(imc_->agentInfoList_.size(), 1u);
EXPECT_EQ(imc_->agentInfoList_[0].imeType, ImeType::IME_MIRROR);
}
* @tc.name: GetAgent_SkipsMirrorAgents
* @tc.desc: Verify GetAgent skips mirror agents and returns a normal agent
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorControllerTest, GetAgent_SkipsMirrorAgents, TestSize.Level1)
{
auto mirrorObj = new NiceMock<MockIRemoteObject>();
AddMirrorAgent(mirrorObj);
auto normalObj = new NiceMock<MockIRemoteObject>();
AddNormalAgent(normalObj);
auto agent = imc_->GetAgent();
ASSERT_NE(agent, nullptr);
EXPECT_EQ(imc_->agentInfoList_.size(), 2u);
}
* @tc.name: OnInputReady_MirrorName_DoesNotSetBindImeInfo
* @tc.desc: Verify OnInputReady with IME_MIRROR_NAME_LOCAL does not set bind/editable state
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorControllerTest, OnInputReady_MirrorName_DoesNotSetBindImeInfo, TestSize.Level1)
{
auto agentObj = new NiceMock<MockIRemoteObject>();
BindImeInfo imeInfo;
imeInfo.bundleName = IME_MIRROR_NAME_LOCAL;
imeInfo.pid = 100;
imc_->OnInputReady(agentObj, imeInfo);
EXPECT_FALSE(imc_->isBound_.load());
EXPECT_FALSE(imc_->isEditable_.load());
ASSERT_EQ(imc_->agentInfoList_.size(), 1u);
EXPECT_EQ(imc_->agentInfoList_[0].imeType, ImeType::IME_MIRROR);
}
* @tc.name: OnImeMirrorStop_RemovesMirrorAgent
* @tc.desc: Verify OnImeMirrorStop removes the matching mirror agent from agentInfoList
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorControllerTest, OnImeMirrorStop_RemovesMirrorAgent, TestSize.Level1)
{
auto mirrorObj = new NiceMock<MockIRemoteObject>();
AddMirrorAgent(mirrorObj);
ASSERT_EQ(imc_->agentInfoList_.size(), 1u);
imc_->OnImeMirrorStop(mirrorObj);
EXPECT_TRUE(imc_->agentInfoList_.empty());
}
* @tc.name: OnImeMirrorStop_WrongObject_DoesNothing
* @tc.desc: Verify OnImeMirrorStop with non-matching object does not remove any agent
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorControllerTest, OnImeMirrorStop_WrongObject_DoesNothing, TestSize.Level1)
{
auto mirrorObj = new NiceMock<MockIRemoteObject>();
AddMirrorAgent(mirrorObj);
auto wrongObj = new NiceMock<MockIRemoteObject>();
imc_->OnImeMirrorStop(wrongObj);
EXPECT_EQ(imc_->agentInfoList_.size(), 1u);
}
* @tc.name: SendRequestToAllAgents_MirrorSkippedOnSecurity
* @tc.desc: Verify mirror agent is skipped when security flag is set
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorControllerTest, SendRequestToAllAgents_MirrorSkippedOnSecurity, TestSize.Level1)
{
auto mirrorObj = new NiceMock<MockIRemoteObject>();
AddMirrorAgent(mirrorObj);
auto normalObj = new NiceMock<MockIRemoteObject>();
AddNormalAgent(normalObj);
imc_->textConfig_.inputAttribute.inputPattern = InputAttribute::PATTERN_PASSWORD;
ASSERT_TRUE(imc_->textConfig_.inputAttribute.IsSecurityImeFlag());
int32_t callCount = 0;
auto ret = imc_->SendRequestToAllAgents([&callCount](std::shared_ptr<IInputMethodAgent> agent) {
callCount++;
return ErrorCode::NO_ERROR;
});
EXPECT_EQ(callCount, 1);
EXPECT_EQ(ret, ErrorCode::NO_ERROR);
}
* @tc.name: SendRequestToImeMirrorAgent_SecurityFlag_ReturnsPermissionDenied
* @tc.desc: Verify SendRequestToImeMirrorAgent returns permission denied on security flag
* @tc.type: FUNC
*/
HWTEST_F(ImeMirrorControllerTest, SendRequestToImeMirrorAgent_SecurityFlag_ReturnsPermissionDenied, TestSize.Level1)
{
auto mirrorObj = new NiceMock<MockIRemoteObject>();
AddMirrorAgent(mirrorObj);
imc_->textConfig_.inputAttribute.inputPattern = InputAttribute::PATTERN_PASSWORD;
ASSERT_TRUE(imc_->textConfig_.inputAttribute.IsSecurityImeFlag());
bool taskCalled = false;
auto ret = imc_->SendRequestToImeMirrorAgent([&taskCalled](std::shared_ptr<IInputMethodAgent> agent) {
taskCalled = true;
return ErrorCode::NO_ERROR;
});
EXPECT_EQ(ret, ErrorCode::ERROR_STATUS_PERMISSION_DENIED);
EXPECT_FALSE(taskCalled);
}
}
}