diff --git a/frameworks/native/inputmethod_ability/src/input_method_ability.cpp b/frameworks/native/inputmethod_ability/src/input_method_ability.cpp index 0cfc53cc3b68aa7f05ea7482b0a462bc43769c38..5337505179035a532af631061dfa622f51b8e8f4 100644 --- a/frameworks/native/inputmethod_ability/src/input_method_ability.cpp +++ b/frameworks/native/inputmethod_ability/src/input_method_ability.cpp @@ -191,6 +191,7 @@ void InputMethodAbility::WorkThread() if (imeListener_ != nullptr) { imeListener_->OnInputStop(imeId); } + isBound_.store(false); break; } case MSG_ID_SET_SUBTYPE: { diff --git a/services/include/input_method_system_ability.h b/services/include/input_method_system_ability.h index 920334a2f7c822034714280c6a6bffe7d8f59ee5..67628292a9ea42cab08b3d731d793d1136920b46 100644 --- a/services/include/input_method_system_ability.h +++ b/services/include/input_method_system_ability.h @@ -38,6 +38,16 @@ using AbilityType = AppExecFwk::ExtensionAbilityType; using namespace AppExecFwk; enum class ServiceRunningState { STATE_NOT_START, STATE_RUNNING }; +struct SwitchInfo { + std::chrono::system_clock::time_point timestamp{}; + std::string bundleName; + std::string subName; + bool operator==(const SwitchInfo &info) const + { + return (timestamp == info.timestamp && bundleName == info.bundleName && subName == info.subName); + } +}; + class InputMethodSystemAbility : public SystemAbility, public InputMethodSystemAbilityStub { DECLARE_SYSTEM_ABILITY(InputMethodSystemAbility); @@ -92,7 +102,7 @@ private: static sptr GetAbilityManagerService(); void StartUserIdListener(); bool IsNeedSwitch(const std::string &bundleName, const std::string &subName); - int32_t OnSwitchInputMethod(const std::string &bundleName, const std::string &subName); + int32_t OnSwitchInputMethod(const SwitchInfo &switchInfo); int32_t Switch(const std::string &bundleName, const ImeInfo &info); int32_t SwitchExtension(const ImeInfo &info); int32_t SwitchSubType(const ImeInfo &info); @@ -104,6 +114,14 @@ private: static constexpr const char *SELECT_DIALOG_HAP = "cn.openharmony.inputmethodchoosedialog"; static constexpr const char *SELECT_DIALOG_ABILITY = "InputMethod"; + std::mutex switchMutex_; + std::condition_variable switchCV_; + std::mutex switchQueueMutex_; + std::queue switchQueue_; + void PopSwitchQueue(); + void PushToSwitchQueue(const SwitchInfo &info); + bool CheckReadyToSwitch(const SwitchInfo &info); + int32_t InitKeyEventMonitor(); bool InitFocusChangeMonitor(); int32_t SwitchByCombinationKey(uint32_t state); diff --git a/services/include/peruser_session.h b/services/include/peruser_session.h index a331a38c47b2f6f7d3e9234e90a6a4ba941aa13f..5ffa82e10f1bfb04ae618ed2b5872a1180a42e79 100644 --- a/services/include/peruser_session.h +++ b/services/include/peruser_session.h @@ -88,6 +88,7 @@ public: bool CheckFocused(uint32_t tokenId); int32_t OnPanelStatusChange(const InputWindowStatus &status, const InputWindowInfo &windowInfo); int32_t OnUpdateListenEventFlag(const InputClientInfo &clientInfo); + bool StartInputService(const std::string &imeName, bool isRetry); private: int32_t userId_; // the id of the user to whom the object is linking @@ -136,7 +137,6 @@ private: void SetAgent(sptr agent); sptr GetAgent(); sptr GetAbilityManagerService(); - bool StartCurrentIme(bool isRetry); static inline bool IsValid(int32_t index) { diff --git a/services/src/input_method_system_ability.cpp b/services/src/input_method_system_ability.cpp index 4e6fe1813e44572ee1094042c269341aa2eab341..a2d2c5b579d2fdcf0a21fe09703754e21b16657c 100644 --- a/services/src/input_method_system_ability.cpp +++ b/services/src/input_method_system_ability.cpp @@ -15,7 +15,7 @@ #include "input_method_system_ability.h" -#include +#include #include "ability_connect_callback_proxy.h" #include "ability_manager_errors.h" @@ -48,6 +48,7 @@ constexpr std::int32_t INIT_INTERVAL = 10000L; constexpr std::int32_t MAIN_USER_ID = 100; constexpr uint32_t RETRY_INTERVAL = 100; constexpr uint32_t BLOCK_RETRY_TIMES = 100; +constexpr uint32_t SWITCH_BLOCK_TIME = 150000; static const std::string PERMISSION_CONNECT_IME_ABILITY = "ohos.permission.CONNECT_IME_ABILITY"; std::shared_ptr InputMethodSystemAbility::serviceHandler_; @@ -195,30 +196,7 @@ void InputMethodSystemAbility::StartUserIdListener() bool InputMethodSystemAbility::StartInputService(const std::string &imeId) { - IMSA_HILOGI("InputMethodSystemAbility, ime:%{public}s", imeId.c_str()); - bool isStartSuccess = false; - sptr abms = GetAbilityManagerService(); - if (abms != nullptr) { - AAFwk::Want want; - want.SetAction("action.system.inputmethod"); - std::string::size_type pos = imeId.find("/"); - want.SetElementName(imeId.substr(0, pos), imeId.substr(pos + 1)); - int32_t result = abms->StartAbility(want); - if (result != ErrorCode::NO_ERROR) { - IMSA_HILOGE("InputMethodSystemAbility::StartInputService failed, result = %{public}d", result); - isStartSuccess = false; - } else { - IMSA_HILOGE("InputMethodSystemAbility::StartInputService success."); - isStartSuccess = true; - } - } - - if (!isStartSuccess) { - IMSA_HILOGE("StartInputService failed. Try again 10s later"); - auto callback = [this, imeId]() { StartInputService(imeId); }; - serviceHandler_->PostTask(callback, INIT_INTERVAL); - } - return isStartSuccess; + return userSession_->StartInputService(imeId, true); } void InputMethodSystemAbility::StopInputService(const std::string &imeId) @@ -369,28 +347,63 @@ int32_t InputMethodSystemAbility::DisplayOptionalInputMethod() return OnDisplayOptionalInputMethod(); }; +void InputMethodSystemAbility::PushToSwitchQueue(const SwitchInfo &info) +{ + std::lock_guard lock(switchQueueMutex_); + switchQueue_.push(info); +} + +void InputMethodSystemAbility::PopSwitchQueue() +{ + std::lock_guard lock(switchQueueMutex_); + switchQueue_.pop(); + switchCV_.notify_all(); +} + +bool InputMethodSystemAbility::CheckReadyToSwitch(const SwitchInfo &info) +{ + std::lock_guard lock(switchQueueMutex_); + return info == switchQueue_.front(); +} + int32_t InputMethodSystemAbility::SwitchInputMethod(const std::string &bundleName, const std::string &subName) { - auto currentIme = ImeCfgManager::GetInstance().GetCurrentImeCfg(userId_)->bundleName; + SwitchInfo switchInfo = { std::chrono::system_clock::now(), bundleName, subName }; + PushToSwitchQueue(switchInfo); + return OnSwitchInputMethod(switchInfo); +} + +int32_t InputMethodSystemAbility::OnSwitchInputMethod(const SwitchInfo &switchInfo) +{ + IMSA_HILOGD("run in, switchInfo: %{public}s|%{public}s", switchInfo.bundleName.c_str(), switchInfo.subName.c_str()); + if (!CheckReadyToSwitch(switchInfo)) { + IMSA_HILOGD("start wait"); + std::unique_lock lock(switchMutex_); + switchCV_.wait(lock, [this, &switchInfo]() { return CheckReadyToSwitch(switchInfo); }); + usleep(SWITCH_BLOCK_TIME); + } + IMSA_HILOGD("start switch %{public}s", (switchInfo.bundleName + '/' + switchInfo.subName).c_str()); // if currentIme is switching subtype, permission verification is not performed. + auto currentIme = ImeCfgManager::GetInstance().GetCurrentImeCfg(userId_)->bundleName; if (!BundleChecker::CheckPermission(IPCSkeleton::GetCallingTokenID(), PERMISSION_CONNECT_IME_ABILITY) - && !(bundleName == currentIme && BundleChecker::IsCurrentIme(IPCSkeleton::GetCallingTokenID(), currentIme))) { + && !(switchInfo.bundleName == currentIme + && BundleChecker::IsCurrentIme(IPCSkeleton::GetCallingTokenID(), currentIme))) { + PopSwitchQueue(); return ErrorCode::ERROR_STATUS_PERMISSION_DENIED; } - if (!IsNeedSwitch(bundleName, subName)) { + if (!IsNeedSwitch(switchInfo.bundleName, switchInfo.subName)) { + PopSwitchQueue(); return ErrorCode::NO_ERROR; } - return OnSwitchInputMethod(bundleName, subName); -} - -int32_t InputMethodSystemAbility::OnSwitchInputMethod(const std::string &bundleName, const std::string &subName) -{ ImeInfo info; - auto ret = ImeInfoInquirer::GetInstance().GetImeInfo(userId_, bundleName, subName, info); + int32_t ret = ImeInfoInquirer::GetInstance().GetImeInfo(userId_, switchInfo.bundleName, switchInfo.subName, info); if (ret != ErrorCode::NO_ERROR) { + PopSwitchQueue(); return ret; } - return info.isNewIme ? Switch(bundleName, info) : SwitchExtension(info); + ret = info.isNewIme ? Switch(switchInfo.bundleName, info) : SwitchExtension(info); + PopSwitchQueue(); + return ret; } bool InputMethodSystemAbility::IsNeedSwitch(const std::string &bundleName, const std::string &subName) @@ -681,7 +694,9 @@ int32_t InputMethodSystemAbility::SwitchMode() IMSA_HILOGE("target is empty"); return ErrorCode::ERROR_BAD_PARAMETERS; } - return OnSwitchInputMethod(target->name, target->id); + SwitchInfo switchInfo = { std::chrono::system_clock::now(), target->name, target->id }; + PushToSwitchQueue(switchInfo); + return OnSwitchInputMethod(switchInfo); } int32_t InputMethodSystemAbility::SwitchLanguage() @@ -703,7 +718,9 @@ int32_t InputMethodSystemAbility::SwitchLanguage() IMSA_HILOGE("target is empty"); return ErrorCode::ERROR_BAD_PARAMETERS; } - return OnSwitchInputMethod(target->name, target->id); + SwitchInfo switchInfo = { std::chrono::system_clock::now(), target->name, target->id }; + PushToSwitchQueue(switchInfo); + return OnSwitchInputMethod(switchInfo); } int32_t InputMethodSystemAbility::SwitchType() @@ -718,7 +735,9 @@ int32_t InputMethodSystemAbility::SwitchType() auto iter = std::find_if(props.begin(), props.end(), [¤tImeBundle](const Property &property) { return property.name != currentImeBundle; }); if (iter != props.end()) { - return OnSwitchInputMethod(iter->name, ""); + SwitchInfo switchInfo = { std::chrono::system_clock::now(), iter->name, "" }; + PushToSwitchQueue(switchInfo); + return OnSwitchInputMethod(switchInfo); } return ErrorCode::NO_ERROR; } diff --git a/services/src/peruser_session.cpp b/services/src/peruser_session.cpp index 000e0a2fc73e2f1c2d5f77da4693b3398c539fd1..1dfc3f62111ad0e5f65904e60eac0dbd9dc09ef8 100644 --- a/services/src/peruser_session.cpp +++ b/services/src/peruser_session.cpp @@ -231,7 +231,7 @@ void PerUserSession::OnImsDied(const sptr &remote) return; } IMSA_HILOGI("user %{public}d ime died, restart!", userId_); - StartCurrentIme(true); + StartInputService(ImeInfoInquirer::GetInstance().GetStartedIme(userId_), true); } void PerUserSession::UpdateCurrentUserId(int32_t userId) @@ -344,7 +344,7 @@ int32_t PerUserSession::OnStartInput(const sptr &client, bool isSh } if (GetImsCore(CURRENT_IME) == nullptr) { IMSA_HILOGI("current ime is empty, try to restart it"); - if (!StartCurrentIme(true)) { + if (!StartInputService(ImeInfoInquirer::GetInstance().GetStartedIme(userId_), true)) { IMSA_HILOGE("failed to restart ime"); return ErrorCode::ERROR_IME_START_FAILED; } @@ -382,8 +382,6 @@ int32_t PerUserSession::OnSetCoreAndAgent(const sptr &core, co } SetImsCore(CURRENT_IME, core); SetAgent(agent); - bool isStarted = GetImsCore(CURRENT_IME) != nullptr; - isImeStarted_.SetValue(isStarted); int ret = InitInputControlChannel(); IMSA_HILOGI("init input control channel ret: %{public}d", ret); @@ -395,6 +393,8 @@ int32_t PerUserSession::OnSetCoreAndAgent(const sptr &core, co IMSA_HILOGI("start input ret: %{public}d", ret); } } + bool isStarted = GetImsCore(CURRENT_IME) != nullptr && GetAgent() != nullptr; + isImeStarted_.SetValue(isStarted); return ErrorCode::NO_ERROR; } @@ -432,6 +432,8 @@ void PerUserSession::StopInputService(std::string imeId) IMSA_HILOGI("Remove death recipient"); core->AsObject()->RemoveDeathRecipient(imsDeathRecipient_); core->StopInputService(imeId); + SetImsCore(CURRENT_IME, nullptr); + SetAgent(nullptr); } bool PerUserSession::IsRestartIme(uint32_t index) @@ -568,10 +570,10 @@ sptr PerUserSession::GetAbilityManagerService() return iface_cast(abilityMsObj); } -bool PerUserSession::StartCurrentIme(bool isRetry) +bool PerUserSession::StartInputService(const std::string &imeName, bool isRetry) { - auto currentIme = ImeInfoInquirer::GetInstance().GetStartedIme(userId_); - std::string::size_type pos = currentIme.find('/'); + IMSA_HILOGI("start ime: %{public}s with isRetry: %{public}d", imeName.c_str(), isRetry); + std::string::size_type pos = imeName.find('/'); if (pos == std::string::npos) { IMSA_HILOGE("invalid ime name"); return false; @@ -581,9 +583,9 @@ bool PerUserSession::StartCurrentIme(bool isRetry) IMSA_HILOGE("failed to get ability manager service"); return false; } - IMSA_HILOGI("ime: %{public}s", currentIme.c_str()); + IMSA_HILOGI("ime: %{public}s", imeName.c_str()); AAFwk::Want want; - want.SetElementName(currentIme.substr(0, pos), currentIme.substr(pos + 1)); + want.SetElementName(imeName.substr(0, pos), imeName.substr(pos + 1)); isImeStarted_.Clear(false); if (abms->StartAbility(want) != ErrorCode::NO_ERROR) { IMSA_HILOGE("failed to start ability"); @@ -593,9 +595,10 @@ bool PerUserSession::StartCurrentIme(bool isRetry) } if (isRetry) { IMSA_HILOGE("failed to start ime, begin to retry five times"); - auto retryTask = [this]() { + auto retryTask = [this, imeName]() { pthread_setname_np(pthread_self(), "ImeRestart"); - BlockRetry(IME_RESTART_INTERVAL, IME_RESTART_TIMES, [this]() { return StartCurrentIme(false); }); + BlockRetry(IME_RESTART_INTERVAL, IME_RESTART_TIMES, + [this, imeName]() { return StartInputService(imeName, false); }); }; std::thread(retryTask).detach(); } diff --git a/test/unittest/cpp_test/src/input_method_switch_test.cpp b/test/unittest/cpp_test/src/input_method_switch_test.cpp index 508de51cc1e56cd1a51c274d18940d1831599a3f..73a95a27cd7737d6597aa82da57d714edf9196cc 100644 --- a/test/unittest/cpp_test/src/input_method_switch_test.cpp +++ b/test/unittest/cpp_test/src/input_method_switch_test.cpp @@ -231,28 +231,6 @@ HWTEST_F(InputMethodSwitchTest, testSubTypeSwitch_002, TestSize.Level0) CheckCurrentSubProps(); } -/** -* @tc.name: testSubTypeSwitch_003 -* @tc.desc: switch subtype with extName1 -* @tc.type: FUNC -* @tc.require: issuesI62BHB -* @tc.author: chenyu -*/ -HWTEST_F(InputMethodSwitchTest, testSubTypeSwitch_003, TestSize.Level0) -{ - IMSA_HILOGI("oldIme testSubTypeSwitch_002 Test START"); - std::unique_lock lock(imeChangeFlagLock); - imeChangeFlag = false; - int32_t ret = imc_->SwitchInputMethod(bundleName, extName[0]); - EXPECT_EQ(ret, ErrorCode::NO_ERROR); - conditionVar.wait_for( - lock, std::chrono::milliseconds(SUBTYPE_SWITCH_DELAY_TIME), [] { return imeChangeFlag == true; }); - EXPECT_TRUE(imeChangeFlag); - CheckCurrentProp(extName[0]); - CheckCurrentSubProp(extName[0]); - CheckCurrentSubProps(); -} - /** * @tc.name: testSubTypeSwitchWithErrorSubName * @tc.desc: switch subtype with error subName. @@ -263,10 +241,11 @@ HWTEST_F(InputMethodSwitchTest, testSubTypeSwitch_003, TestSize.Level0) HWTEST_F(InputMethodSwitchTest, testSubTypeSwitchWithErrorSubName, TestSize.Level0) { IMSA_HILOGI("oldIme testSubTypeSwitchWithErrorSubName Test START"); + std::string subName = InputMethodSwitchTest::imc_->GetCurrentInputMethodSubtype()->id; int32_t ret = imc_->SwitchInputMethod(bundleName, "error subName"); EXPECT_EQ(ret, ErrorCode::ERROR_BAD_PARAMETERS); - CheckCurrentProp(extName[0]); - CheckCurrentSubProp(extName[0]); + CheckCurrentProp(subName); + CheckCurrentSubProp(subName); CheckCurrentSubProps(); } @@ -282,13 +261,14 @@ HWTEST_F(InputMethodSwitchTest, testSwitchToCurrentImeWithEmptySubName, TestSize IMSA_HILOGI("oldIme testSwitchToCurrentImeWithEmptySubName Test START"); std::unique_lock lock(imeChangeFlagLock); imeChangeFlag = false; + std::string subName = InputMethodSwitchTest::imc_->GetCurrentInputMethodSubtype()->id; int32_t ret = imc_->SwitchInputMethod(bundleName); EXPECT_EQ(ret, ErrorCode::NO_ERROR); InputMethodSwitchTest::conditionVar.wait_for( lock, std::chrono::milliseconds(SUBTYPE_SWITCH_DELAY_TIME), [] { return imeChangeFlag == true; }); EXPECT_FALSE(imeChangeFlag); - CheckCurrentProp(extName[0]); - CheckCurrentSubProp(extName[0]); + CheckCurrentProp(subName); + CheckCurrentSubProp(subName); CheckCurrentSubProps(); } @@ -302,10 +282,11 @@ HWTEST_F(InputMethodSwitchTest, testSwitchToCurrentImeWithEmptySubName, TestSize HWTEST_F(InputMethodSwitchTest, testSwitchImeWithErrorBundleName, TestSize.Level0) { IMSA_HILOGI("oldIme testSwitchImeWithErrorBundleName Test START"); + std::string subName = InputMethodSwitchTest::imc_->GetCurrentInputMethodSubtype()->id; int32_t ret = imc_->SwitchInputMethod("error bundleName", extName[0]); EXPECT_EQ(ret, ErrorCode::ERROR_BAD_PARAMETERS); - CheckCurrentProp(extName[0]); - CheckCurrentSubProp(extName[0]); + CheckCurrentProp(subName); + CheckCurrentSubProp(subName); CheckCurrentSubProps(); } @@ -319,10 +300,11 @@ HWTEST_F(InputMethodSwitchTest, testSwitchImeWithErrorBundleName, TestSize.Level HWTEST_F(InputMethodSwitchTest, testSwitchImeWithErrorBundleNameWitchEmptySubName, TestSize.Level0) { IMSA_HILOGI("oldIme testSwitchImeWithErrorBundleNameWitchEmptySubName Test START"); + std::string subName = InputMethodSwitchTest::imc_->GetCurrentInputMethodSubtype()->id; int32_t ret = imc_->SwitchInputMethod("error bundleName", " "); EXPECT_EQ(ret, ErrorCode::ERROR_BAD_PARAMETERS); - CheckCurrentProp(extName[0]); - CheckCurrentSubProp(extName[0]); + CheckCurrentProp(subName); + CheckCurrentSubProp(subName); CheckCurrentSubProps(); } @@ -386,12 +368,13 @@ HWTEST_F(InputMethodSwitchTest, testIMCListInputMethodDisable, TestSize.Level0) HWTEST_F(InputMethodSwitchTest, testIMCListInputMethodEnable, TestSize.Level0) { IMSA_HILOGI("IMC testIMCListInputMethodEnable Test Start"); + std::string subName = InputMethodSwitchTest::imc_->GetCurrentInputMethodSubtype()->id; std::vector enableProperties = {}; auto ret = imc_->ListInputMethod(true, enableProperties); EXPECT_EQ(ret, ErrorCode::NO_ERROR); EXPECT_EQ(enableProperties.size(), ENABLE_IME_NUM); EXPECT_EQ(enableProperties[ENABLE_IME_NUM - 1].name, bundleName); - EXPECT_EQ(enableProperties[ENABLE_IME_NUM - 1].id, extName[0]); + EXPECT_EQ(enableProperties[ENABLE_IME_NUM - 1].id, subName); } /** diff --git a/test/unittest/napi_test/src/InputMethodTest.js b/test/unittest/napi_test/src/InputMethodTest.js index 312a890adefdf5f2e94b93edec9ec945a0817e0a..fc30ef11a3a0367f7523cbb2457008ebf1d1ea18 100644 --- a/test/unittest/napi_test/src/InputMethodTest.js +++ b/test/unittest/napi_test/src/InputMethodTest.js @@ -495,41 +495,6 @@ describe("InputMethodTest", function () { }) }); - /* - * @tc.number inputmethod_test_switchCurrentInputMethodAndSubtype_002 - * @tc.name Test Indicates the input method which will replace the current one. - * @tc.desc Function test - * @tc.level 2 - */ - it('inputmethod_test_switchCurrentInputMethodAndSubtype_002', 0, async function (done) { - console.info("************* inputmethod_test_switchCurrentInputMethodAndSubtype_002 Test start*************"); - let InputMethodSubtype = { - name:bundleName, - id:subName[2], - locale:"en_US.ISO-8859-1", - language:"en", - extra:{}, - } - let inputMethodProperty = { - name:bundleName, - id:extName - } - inputMethod.switchCurrentInputMethodAndSubtype(inputMethodProperty, InputMethodSubtype, (err, ret)=>{ - if(err){ - console.info("inputmethod_test_switchCurrentInputMethodAndSubtype_002 error:" + JSON.stringify(err.message)); - return; - } - expect(ret).assertTrue(); - let subProp = inputMethod.getCurrentInputMethodSubtype(); - checkNewImeCurrentSubProp(subProp, 2) - let property = inputMethod.getCurrentInputMethod(); - checkNewImeCurrentProp(property); - console.info("************* inputmethod_test_switchCurrentInputMethodAndSubtype_002 Test end*************"); - wait(WAIT_DEAL_OK); - done(); - }); - }); - /* * @tc.number inputmethod_test_listInputMethodSubtype_001 * @tc.name Test list input method subtypes.