diff --git a/frameworks/proxy/event_handler/include/input_manager_impl.h b/frameworks/proxy/event_handler/include/input_manager_impl.h index 05b3e9c322898ae47d80ac0efae840bdd4f95868..be1d0aa55533c02f9d7e6accabe9ddebfc593f0a 100644 --- a/frameworks/proxy/event_handler/include/input_manager_impl.h +++ b/frameworks/proxy/event_handler/include/input_manager_impl.h @@ -237,6 +237,7 @@ public: int32_t ConvertToCapiKeyAction(int32_t keyAction); int32_t SetInputDeviceEnabled(int32_t deviceId, bool enable, std::function callback); int32_t ShiftAppPointerEvent(const ShiftWindowParam ¶m, bool autoGenDown); + int32_t SetEventRecorderEnable(bool enable); int32_t CheckKnuckleEvent(float pointX, float pointY, bool &touchType); private: diff --git a/frameworks/proxy/event_handler/src/input_manager_impl.cpp b/frameworks/proxy/event_handler/src/input_manager_impl.cpp index 81add487e0fdd292d25618aa56294fddb81956c1..f98c1554be57721daa8a8360c5e589b6afeabf89 100644 --- a/frameworks/proxy/event_handler/src/input_manager_impl.cpp +++ b/frameworks/proxy/event_handler/src/input_manager_impl.cpp @@ -2722,6 +2722,13 @@ int32_t InputManagerImpl::ShiftAppPointerEvent(const ShiftWindowParam ¶m, bo #endif // OHOS_BUILD_ENABLE_POINTER || OHOS_BUILD_ENABLE_TOUCH } +int32_t InputManagerImpl::SetEventRecorderEnable(bool enable) +{ + CALL_INFO_TRACE; + std::lock_guard guard(mtx_); + return MULTIMODAL_INPUT_CONNECT_MGR->SetEventRecorderEnable(enable); +} + int32_t InputManagerImpl::SetCustomCursor(int32_t windowId, CustomCursor cursor, CursorOptions options) { CALL_INFO_TRACE; diff --git a/frameworks/proxy/events/src/input_manager.cpp b/frameworks/proxy/events/src/input_manager.cpp index b7ba5bfb8603ffae696218a088f21554bbe50e5c..7643f7cf91891863e09cd22e432470a58cc7161d 100644 --- a/frameworks/proxy/events/src/input_manager.cpp +++ b/frameworks/proxy/events/src/input_manager.cpp @@ -876,6 +876,11 @@ int32_t InputManager::ShiftAppPointerEvent(const ShiftWindowParam ¶m, bool a return InputMgrImpl.ShiftAppPointerEvent(param, autoGenDown); } +int32_t InputManager::SetEventRecorderEnable(bool enable) +{ + return InputMgrImpl.SetEventRecorderEnable(enable); +} + int32_t InputManager::SetCustomCursor(int32_t windowId, CustomCursor cursor, CursorOptions options) { return InputMgrImpl.SetCustomCursor(windowId, cursor, options); diff --git a/frameworks/proxy/events/test/input_manager_test.cpp b/frameworks/proxy/events/test/input_manager_test.cpp index f760f6f0e9b40ae6f8f66977fb46c9960811bdcd..676a5d99f05bcb1bd753ef6b2b5ffcd780981d5e 100644 --- a/frameworks/proxy/events/test/input_manager_test.cpp +++ b/frameworks/proxy/events/test/input_manager_test.cpp @@ -4861,6 +4861,23 @@ HWTEST_F(InputManagerTest, InputManagerTest_ShiftAppPointerEvent_002, TestSize.L #endif // OHOS_BUILD_ENABLE_POINTER || OHOS_BUILD_ENABLE_TOUCH } +/* + * @tc.name: InputManagerTest_SetEventRecorderEnable_001 + * @tc.desc: Test the funcation SetEventRecorderEnable + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(InputManagerTest, InputManagerTest_SetEventRecorderEnable_001, TestSize.Level1) +{ + CALL_TEST_DEBUG; + bool enable = true; + int32_t ret = InputManager::GetInstance()->SetEventRecorderEnable(enable); + ASSERT_EQ(ret, RET_OK); + enable = false; + ret = InputManager::GetInstance()->SetEventRecorderEnable(enable); + ASSERT_EQ(ret, RET_OK); +} + /** * @tc.name: InputManagerTest_SetCustomCursorEx_001 * @tc.desc: Test SetCustomCursorEx_001 diff --git a/interfaces/native/innerkits/proxy/include/input_manager.h b/interfaces/native/innerkits/proxy/include/input_manager.h index fd3aaf57635e2b28317ecb8ae3c149cd16d752c9..7e93635c6c7394ad6aea7402e25ae1921486f0e1 100644 --- a/interfaces/native/innerkits/proxy/include/input_manager.h +++ b/interfaces/native/innerkits/proxy/include/input_manager.h @@ -1120,6 +1120,14 @@ public: */ int32_t ShiftAppPointerEvent(const ShiftWindowParam ¶m, bool autoGenDown = true); + /** + * @brief Controls the event recording functionality for test use. + * @param enable True to start recording, false to stop recording + * @return Returns 0 if success; returns a non-0 value otherwise. + * @since 15 + */ + int32_t SetEventRecorderEnable(bool enable); + /** * @brief Sets the custom cursor. You can set whether to adjust the cursor size based on the system settings. * @param windowId Indicates the windowId of the window diff --git a/multimodalinput_mini.gni b/multimodalinput_mini.gni index b4ebb8527788c86f90de5a3722ed2dc8bec5f35f..baeaefd2a3325df03a68c27879e7eb934764c243 100644 --- a/multimodalinput_mini.gni +++ b/multimodalinput_mini.gni @@ -121,6 +121,7 @@ declare_args() { "dfx/src/dfx_hisysevent_device.cpp", "event_dispatch/src/event_dispatch_handler.cpp", "event_dump/src/event_dump.cpp", + "event_dump/src/event_recorder.cpp", "event_dump/src/event_statistic.cpp", "event_handler/src/anr_manager.cpp", "event_handler/src/event_normalize_handler.cpp", diff --git a/service/BUILD.gn b/service/BUILD.gn index 5d1e3e49eaa77987fdbd2beb17cb99d12a05de44..3cbcbcfc02f45312cd243080d9b11bb804250134 100644 --- a/service/BUILD.gn +++ b/service/BUILD.gn @@ -2645,6 +2645,7 @@ ohos_unittest("EventDumpTest") { sources = [ "event_dump/test/event_dump_test.cpp", + "event_dump/test/event_recorder_test.cpp", "event_dump/test/event_statistic_test.cpp", ] diff --git a/service/connect_manager/include/i_multimodal_input_connect.h b/service/connect_manager/include/i_multimodal_input_connect.h index 4e56f45adf53121a4f303c77fd837faebe3b593e..da06ed3494cd47c29e741c064309a5687d7ec38f 100644 --- a/service/connect_manager/include/i_multimodal_input_connect.h +++ b/service/connect_manager/include/i_multimodal_input_connect.h @@ -183,6 +183,7 @@ public: virtual int32_t GetAllSystemHotkeys(std::vector> &keyOptions) = 0; virtual int32_t SetInputDeviceEnabled(int32_t deviceId, bool enable, int32_t index) = 0; virtual int32_t ShiftAppPointerEvent(const ShiftWindowParam ¶m, bool autoGenDown) = 0; + virtual int32_t SetEventRecorderEnable(bool enable) = 0; virtual int32_t SetMultiWindowScreenId(uint64_t screenId, uint64_t displayNodeScreenId) = 0; }; } // namespace MMI diff --git a/service/connect_manager/include/multimodal_input_connect_manager.h b/service/connect_manager/include/multimodal_input_connect_manager.h index 7ac0b86fddcc5cc68af5212c4d37833d1f5b0e1a..751f18a1c45f94068a210b3d1a1a732f9685f383 100644 --- a/service/connect_manager/include/multimodal_input_connect_manager.h +++ b/service/connect_manager/include/multimodal_input_connect_manager.h @@ -171,6 +171,7 @@ public: int32_t GetAllSystemHotkeys(std::vector> &keyOptions); int32_t SetInputDeviceEnabled(int32_t deviceId, bool enable, int32_t index); int32_t ShiftAppPointerEvent(const ShiftWindowParam ¶m, bool autoGenDown); + int32_t SetEventRecorderEnable(bool enable); int32_t SetMultiWindowScreenId(uint64_t screenId, uint64_t displayNodeScreenId); private: diff --git a/service/connect_manager/include/multimodal_input_connect_proxy.h b/service/connect_manager/include/multimodal_input_connect_proxy.h index 3dd747a1537adc733fa993687fceba02c28629cc..2eedebf961b2345692a11fe9b1ad3c901fe265bf 100644 --- a/service/connect_manager/include/multimodal_input_connect_proxy.h +++ b/service/connect_manager/include/multimodal_input_connect_proxy.h @@ -169,6 +169,7 @@ public: int32_t GetAllSystemHotkeys(std::vector> &keyOptions) override; int32_t SetInputDeviceEnabled(int32_t deviceId, bool enable, int32_t index) override; int32_t ShiftAppPointerEvent(const ShiftWindowParam ¶m, bool autoGenDown) override; + int32_t SetEventRecorderEnable(bool enable) override; int32_t SetMultiWindowScreenId(uint64_t screenId, uint64_t displayNodeScreenId) override; private: diff --git a/service/connect_manager/include/multimodal_input_connect_stub.h b/service/connect_manager/include/multimodal_input_connect_stub.h index f67694006a60f938a4785d4c6e90a5855b0e413c..b6b8c0f0ce128ba27a5b42211535d8dc885af3a3 100644 --- a/service/connect_manager/include/multimodal_input_connect_stub.h +++ b/service/connect_manager/include/multimodal_input_connect_stub.h @@ -182,6 +182,7 @@ protected: int32_t ParseAddInputHandlerData(MessageParcel& data, ParseData& parseData); int32_t StubSetInputDeviceInputEnable(MessageParcel& data, MessageParcel& reply); int32_t StubShiftAppPointerEvent(MessageParcel& data, MessageParcel& reply); + int32_t StubSetEventRecorderEnable(MessageParcel& data, MessageParcel& reply); int32_t StubSetCustomMouseCursor(MessageParcel& data, MessageParcel& reply); int32_t StubSetMultiWindowScreenId(MessageParcel& data, MessageParcel& reply); diff --git a/service/connect_manager/include/multimodalinput_ipc_interface_code.h b/service/connect_manager/include/multimodalinput_ipc_interface_code.h index c5e44bddd3e6b70a50a54d1bc9373db4d3289757..3bc111d1cf4109472c13cafe2e9cab7a7ad95674 100644 --- a/service/connect_manager/include/multimodalinput_ipc_interface_code.h +++ b/service/connect_manager/include/multimodalinput_ipc_interface_code.h @@ -133,6 +133,7 @@ enum class MultimodalinputConnectInterfaceCode { SET_INPUT_DEVICE_ENABLE = 109, SHIFT_APP_POINTER_EVENT = 110, + SET_EVENT_RECORDER_ENABLE = 111, SET_CUSTOM_MOUSE_CURSOR = 120, INJECT_TOUCHPAD_EVENT = 121, GET_TOUCHPAD_OPTION = 122, diff --git a/service/connect_manager/src/multimodal_input_connect_manager.cpp b/service/connect_manager/src/multimodal_input_connect_manager.cpp index 3a3c44ee75b3739267d3157deaa1541989032b3b..54abdcf6ee277352caa181040269e003a90f3b91 100644 --- a/service/connect_manager/src/multimodal_input_connect_manager.cpp +++ b/service/connect_manager/src/multimodal_input_connect_manager.cpp @@ -1039,6 +1039,14 @@ int32_t MultimodalInputConnectManager::ShiftAppPointerEvent(const ShiftWindowPar return multimodalInputConnectService_->ShiftAppPointerEvent(param, autoGenDown); } +int32_t MultimodalInputConnectManager::SetEventRecorderEnable(bool enable) +{ + CALL_INFO_TRACE; + std::lock_guard guard(lock_); + CHKPR(multimodalInputConnectService_, INVALID_HANDLER_ID); + return multimodalInputConnectService_->SetEventRecorderEnable(enable); +} + int32_t MultimodalInputConnectManager::SetCustomCursor(int32_t windowId, CustomCursor cursor, CursorOptions options) { std::lock_guard guard(lock_); diff --git a/service/connect_manager/src/multimodal_input_connect_proxy.cpp b/service/connect_manager/src/multimodal_input_connect_proxy.cpp index b7868509618485253ad2ed7ef4f1a9982e6e664a..743d5443f6213a26ab3ed6d5a2d5cee686a52dca 100644 --- a/service/connect_manager/src/multimodal_input_connect_proxy.cpp +++ b/service/connect_manager/src/multimodal_input_connect_proxy.cpp @@ -2925,6 +2925,29 @@ int32_t MultimodalInputConnectProxy::ShiftAppPointerEvent(const ShiftWindowParam return RET_OK; } +int32_t MultimodalInputConnectProxy::SetEventRecorderEnable(bool enable) +{ + CALL_DEBUG_ENTER; + MessageParcel data; + if (!data.WriteInterfaceToken(MultimodalInputConnectProxy::GetDescriptor())) { + MMI_HILOGE("Failed to write descriptor"); + return ERR_INVALID_VALUE; + } + WRITEBOOL(data, enable, ERR_INVALID_VALUE); + MessageParcel reply; + MessageOption option; + sptr remote = Remote(); + CHKPR(remote, RET_ERR); + int32_t ret = remote->SendRequest( + static_cast(MultimodalinputConnectInterfaceCode::SET_EVENT_RECORDER_ENABLE), + data, reply, option); + if (ret != RET_OK) { + MMI_HILOGE("MultimodalInputConnectProxy::SetEventRecorderEnable Send request fail, ret:%{public}d", ret); + return ret; + } + return RET_OK; +} + int32_t MultimodalInputConnectProxy::SetCustomCursor(int32_t windowId, CustomCursor cursor, CursorOptions options) __attribute__((no_sanitize("cfi"))) { diff --git a/service/connect_manager/src/multimodal_input_connect_stub.cpp b/service/connect_manager/src/multimodal_input_connect_stub.cpp index 3ae182aabdfa6410a3b50c32d83a2c85aa0312bd..0084a2363252c36da62bdaacc2be1c70d5b14a69 100644 --- a/service/connect_manager/src/multimodal_input_connect_stub.cpp +++ b/service/connect_manager/src/multimodal_input_connect_stub.cpp @@ -265,6 +265,9 @@ int32_t MultimodalInputConnectStub::OnRemoteRequest(uint32_t code, MessageParcel case static_cast(MultimodalinputConnectInterfaceCode::SHIFT_APP_POINTER_EVENT): ret = StubShiftAppPointerEvent(data, reply); break; + case static_cast(MultimodalinputConnectInterfaceCode::SET_EVENT_RECORDER_ENABLE): + ret = StubSetEventRecorderEnable(data, reply); + break; case static_cast(MultimodalinputConnectInterfaceCode::INJECT_KEY_EVENT): ret = StubInjectKeyEvent(data, reply); break; @@ -3407,6 +3410,26 @@ int32_t MultimodalInputConnectStub::StubShiftAppPointerEvent(MessageParcel& data return ret; } +int32_t MultimodalInputConnectStub::StubSetEventRecorderEnable(MessageParcel& data, MessageParcel& reply) +{ + CALL_DEBUG_ENTER; + if (!PER_HELPER->VerifySystemApp()) { + MMI_HILOGE("Verify system APP failed"); + return ERROR_NOT_SYSAPI; + } + if (!IsRunning()) { + MMI_HILOGE("Service is not running"); + return MMISERVICE_NOT_RUNNING; + } + bool enable = false; + READBOOL(data, enable, ERR_INVALID_VALUE); + int32_t ret = SetEventRecorderEnable(enable); + if (ret != RET_OK) { + MMI_HILOGE("shift AppPointerEvent failed, ret:%{public}d", ret); + } + return ret; +} + int32_t MultimodalInputConnectStub::StubSetCustomMouseCursor(MessageParcel& data, MessageParcel& reply) { CALL_DEBUG_ENTER; diff --git a/service/connect_manager/test/multimodal_input_connect_stub_ex_test.cpp b/service/connect_manager/test/multimodal_input_connect_stub_ex_test.cpp index ed45bfc923d0f96ee8a04ee8bfbdd6f6c0325ecb..d8cd910ea898bf302f6f96a4871b24b878f848ee 100644 --- a/service/connect_manager/test/multimodal_input_connect_stub_ex_test.cpp +++ b/service/connect_manager/test/multimodal_input_connect_stub_ex_test.cpp @@ -362,6 +362,11 @@ public: return static_cast(autoGenDown); } + int32_t SetEventRecorderEnable(bool enable) override + { + return static_cast(enable); + } + int32_t InjectTouchPadEvent(std::shared_ptr pointerEvent, const TouchpadCDG &touchpadCDG, bool isNativeInject) override { @@ -8608,6 +8613,42 @@ HWTEST_F(MultimodalInputConnectStubTest, StubShiftAppPointerEvent_002, TestSize. EXPECT_NO_FATAL_FAILURE(stub->StubShiftAppPointerEvent(data, reply)); } +/** + * @tc.name: StubSetEventRecorderEnable_001 + * @tc.desc: Test the function StubSetEventRecorderEnable + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(MultimodalInputConnectStubTest, StubSetEventRecorderEnable_001, TestSize.Level1) +{ + EXPECT_CALL(*messageParcelMock_, VerifySystemApp()).WillOnce(Return(false)); + std::shared_ptr stub = std::make_shared(); + ASSERT_NE(stub, nullptr); + std::shared_ptr service = std::static_pointer_cast(stub); + service->state_ = ServiceRunningState::STATE_NOT_START; + MessageParcel data; + MessageParcel reply; + EXPECT_NO_FATAL_FAILURE(stub->StubSetEventRecorderEnable(data, reply)); +} + +/** + * @tc.name: StubSetEventRecorderEnable_002 + * @tc.desc: Test the function StubSetEventRecorderEnable + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(MultimodalInputConnectStubTest, StubSetEventRecorderEnable_002, TestSize.Level1) +{ + EXPECT_CALL(*messageParcelMock_, VerifySystemApp()).WillOnce(Return(true)); + std::shared_ptr stub = std::make_shared(); + ASSERT_NE(stub, nullptr); + std::shared_ptr service = std::static_pointer_cast(stub); + service->state_ = ServiceRunningState::STATE_NOT_START; + MessageParcel data; + MessageParcel reply; + EXPECT_NO_FATAL_FAILURE(stub->StubSetEventRecorderEnable(data, reply)); +} + /** * @tc.name: StubGetCursorSurfaceId_001 * @tc.desc: Test if (!PER_HELPER->VerifySystemApp()) diff --git a/service/connect_manager/test/multimodal_input_connect_stub_test.cpp b/service/connect_manager/test/multimodal_input_connect_stub_test.cpp index 4ffb3419552f179a0d9032c6024e65876169e1f1..dac1644b0f069fe12b1730ce110ed1643f159a17 100644 --- a/service/connect_manager/test/multimodal_input_connect_stub_test.cpp +++ b/service/connect_manager/test/multimodal_input_connect_stub_test.cpp @@ -2694,6 +2694,25 @@ HWTEST_F(MultimodalInputConnectStubTest, StubShiftAppPointerEvent_001, TestSize. EXPECT_EQ(ret, returnCode); } +/** + * @tc.name: StubSetEventRecorderEnable_001 + * @tc.desc: Test the function StubSetEventRecorderEnable + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(MultimodalInputConnectStubTest, StubSetEventRecorderEnable_001, TestSize.Level1) +{ + std::shared_ptr stub = std::make_shared(); + MessageParcel data; + MessageParcel reply; + MessageOption option; + int32_t returnCode = MMISERVICE_NOT_RUNNING; + uint32_t code = static_cast(MultimodalinputConnectInterfaceCode::SET_EVENT_RECORDER_ENABLE); + data.WriteInterfaceToken(IMultimodalInputConnect::GetDescriptor()); + int32_t ret = stub->OnRemoteRequest(code, data, reply, option); + EXPECT_EQ(ret, returnCode); +} + /** * @tc.name: StubAddPreInputHandler_001 * @tc.desc: Test the function StubShiftAppPointerEvent diff --git a/service/event_dump/include/event_recorder.h b/service/event_dump/include/event_recorder.h new file mode 100644 index 0000000000000000000000000000000000000000..595e00f57b6ce7447941cff060b4453441999d66 --- /dev/null +++ b/service/event_dump/include/event_recorder.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021-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. + */ + +#ifndef EVENT_RECORDER_H +#define EVENT_RECORDER_H + +#include +#include +#include +#include +#include + +#include "pointer_event.h" +#include "key_event.h" + +namespace OHOS { +namespace MMI { + +class EventRecorder final { +public: + DISALLOW_MOVE(EventRecorder); + EventRecorder(); + ~EventRecorder() = default; + static EventRecorder* GetInstance(); + static constexpr int64_t DEFAULT_RECORDING_DURATION_MS = 10 * 60 * 1000; + int32_t SetEventRecorderEnable(bool enable); + bool RecordEvent(const std::shared_ptr pointerEvent); + bool RecordEvent(const std::shared_ptr keyEvent); + +private: + struct FileHeader { + uint32_t magicNumber; + uint16_t version; + int32_t eventCount; + uint32_t reserved; + }; + bool StartRecording(); + bool StopRecording(); + bool HasRecordingTimedOut(); + bool WriteEvent(std::ofstream& out, std::shared_ptr pointerEvent); + bool WriteEvent(std::ofstream& out, std::shared_ptr keyEvent); + bool CheckRecordEvent(); + bool WriteEventToFile(std::ofstream& out, int32_t eventType, Parcel& parcel); + + std::atomic isRecording_ { false }; + std::ofstream recordFile_; + mutable std::mutex mutex_; + std::chrono::steady_clock::time_point recordStartTime_; + int64_t currentRecordingDurationMs_ { DEFAULT_RECORDING_DURATION_MS }; + std::atomic eventCount_; +}; + +#define EVENT_RECORDER ::OHOS::MMI::EventRecorder::GetInstance() +} // namespace MMI +} // namespace OHOS + +#endif // EVENT_RECORDER_H \ No newline at end of file diff --git a/service/event_dump/src/event_recorder.cpp b/service/event_dump/src/event_recorder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c9b3937215125689570dc8af8b7118b22136ebac --- /dev/null +++ b/service/event_dump/src/event_recorder.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2021-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 "event_recorder.h" + +#include "util_ex.h" + +#undef MMI_LOG_DOMAIN +#define MMI_LOG_DOMAIN MMI_LOG_SERVER +#undef MMI_LOG_TAG +#define MMI_LOG_TAG "EventRecorder" + +namespace OHOS { +namespace MMI { +namespace { +const char* EVENT_FILE_NAME = "/data/service/el1/public/multimodalinput/event.tmp"; +static constexpr uint32_t EVENT_COUNT_OFF_SET = 8; +static constexpr uint32_t MAGIC_NUMBER = 0x4D4D4952; // "MMIR" in ASCII +static constexpr uint16_t FILE_VERSION = 0x0001; +} + +EventRecorder::EventRecorder() : isRecording_(false), + currentRecordingDurationMs_(DEFAULT_RECORDING_DURATION_MS), eventCount_(-1) +{ +} + +EventRecorder* EventRecorder::GetInstance() +{ + static EventRecorder instance_; + return &instance_; +} + +int32_t EventRecorder::SetEventRecorderEnable(bool enable) +{ + bool result = false; + if (enable) { + result = StartRecording(); + } else { + result = StopRecording(); + } + if (result) { + return RET_OK; + } + return RET_ERR; +} + +bool EventRecorder::StartRecording() +{ + CALL_DEBUG_ENTER; + std::lock_guard lock(mutex_); + if (isRecording_) { + MMI_HILOGI("Stopping existing recording to start a new one"); + recordFile_.close(); + isRecording_ = false; + } + recordFile_.open(EVENT_FILE_NAME, std::ios::binary); + if (!recordFile_.is_open()) { + MMI_HILOGE("Failed to open file: %{public}s", EVENT_FILE_NAME); + return false; + } + FileHeader header{ + header.magicNumber = MAGIC_NUMBER, + header.version = FILE_VERSION, + header.eventCount = -1, + header.reserved = 0, + }; + recordFile_.write(reinterpret_cast(&header), sizeof(header)); + if (!recordFile_.good()) { + recordFile_.close(); + return false; + } + isRecording_ = true; + eventCount_ = 0; + recordStartTime_ = std::chrono::steady_clock::now(); + MMI_HILOGI("Recording started to file: %{public}s with duration: %{public}lld ms", + EVENT_FILE_NAME, static_cast(currentRecordingDurationMs_)); + return true; +} + +bool EventRecorder::StopRecording() +{ + CALL_DEBUG_ENTER; + std::lock_guard lock(mutex_); + if (!isRecording_) { + MMI_HILOGE("No recording in progress"); + return false; + } + recordFile_.seekp(EVENT_COUNT_OFF_SET); + if (!recordFile_.good()) { + MMI_HILOGE("Write event count failed!"); + return false; + } + int32_t eventCount = eventCount_.load(); + recordFile_.write(reinterpret_cast(&eventCount), sizeof(eventCount)); + recordFile_.close(); + isRecording_ = false; + MMI_HILOGI("Recording stopped"); + return true; +} + +bool EventRecorder::CheckRecordEvent() +{ + if (!isRecording_ || !recordFile_.is_open()) { + return false; + } + if (HasRecordingTimedOut()) { + MMI_HILOGI("Recording has exceeded maximum duration (%{public}lld ms), stopping automatically", + static_cast(currentRecordingDurationMs_)); + recordFile_.close(); + isRecording_ = false; + return false; + } + return true; +} + +bool EventRecorder::RecordEvent(const std::shared_ptr pointerEvent) +{ + if (!isRecording_) { + return false; + } + std::lock_guard lock(mutex_); + if (!CheckRecordEvent()) { + return false; + } + return WriteEvent(recordFile_, pointerEvent); +} + +bool EventRecorder::RecordEvent(const std::shared_ptr keyEvent) +{ + if (!isRecording_) { + return false; + } + std::lock_guard lock(mutex_); + if (!CheckRecordEvent()) { + return false; + } + return WriteEvent(recordFile_, keyEvent); +} + +bool EventRecorder::HasRecordingTimedOut() +{ + auto now = std::chrono::steady_clock::now(); + auto elapsedMs = std::chrono::duration_cast(now - recordStartTime_).count(); + return elapsedMs >= currentRecordingDurationMs_; +} + +bool EventRecorder::WriteEventToFile(std::ofstream&out, int32_t eventType, Parcel& parcel) +{ + uint32_t dataSize = static_cast(parcel.GetDataSize()); + out.write(reinterpret_cast(&eventType), sizeof(eventType)); + if (!out.good()) { + return false; + } + out.write(reinterpret_cast(&dataSize), sizeof(dataSize)); + if (!out.good()) { + return false; + } + out.write(reinterpret_cast(parcel.GetData()), parcel.GetDataSize()); + if (!out.good()) { + return false; + } + eventCount_++; + return true; +} + +bool EventRecorder::WriteEvent(std::ofstream& out, std::shared_ptr pointerEvent) +{ + Parcel parcel; + int32_t eventType = pointerEvent->GetEventType(); + if (!pointerEvent->WriteToParcel(parcel)) { + return false; + } + return WriteEventToFile(out, eventType, parcel); +} + +bool EventRecorder::WriteEvent(std::ofstream& out, std::shared_ptr keyEvent) +{ + Parcel parcel; + int32_t eventType = keyEvent->GetEventType(); + if (!keyEvent->WriteToParcel(parcel)) { + return false; + } + return WriteEventToFile(out, eventType, parcel); +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/service/event_dump/test/event_recorder_test.cpp b/service/event_dump/test/event_recorder_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..faeb70822ec72db881d6dffeff17c640d4edaf37 --- /dev/null +++ b/service/event_dump/test/event_recorder_test.cpp @@ -0,0 +1,247 @@ +/* + * 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 +#include + +#include "event_recorder.h" +#include "mmi_log.h" + +#undef MMI_LOG_TAG +#define MMI_LOG_TAG "EventRecorderTest" + +namespace OHOS { +namespace MMI { +namespace { +using namespace testing::ext; +constexpr int32_t TEST_EVENT_TYPE = 1; +constexpr int32_t TEST_KEY_CODE = 17; +constexpr int32_t TEST_POINTER_ACTION = PointerEvent::POINTER_ACTION_DOWN; +constexpr int32_t TEST_SOURCE_TYPE = PointerEvent::SOURCE_TYPE_TOUCHSCREEN; +} // namespace + +class EventRecorderTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} + void SetUp() + { + eventRecorder_ = EventRecorder::GetInstance(); + if (eventRecorder_->isRecording_) { + eventRecorder_->StopRecording(); + } + eventRecorder_->eventCount_ = 0; + } + void TearDown() + { + if (eventRecorder_->isRecording_) { + eventRecorder_->StopRecording(); + } + } + +protected: + EventRecorder* eventRecorder_ = nullptr; +}; + +/** + * @tc.name: EventRecorderTest_GetInstance + * @tc.desc: Test GetInstance method returns a valid instance + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_GetInstance, TestSize.Level1) +{ + CALL_TEST_DEBUG; + EventRecorder* instance = EventRecorder::GetInstance(); + ASSERT_NE(instance, nullptr); +} + +/** + * @tc.name: EventRecorderTest_SetEventRecorderEnable + * @tc.desc: Test enabling and disabling event recording + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_SetEventRecorderEnable, TestSize.Level1) +{ + CALL_TEST_DEBUG; + int32_t result = eventRecorder_->SetEventRecorderEnable(true); + ASSERT_EQ(result, RET_OK); + ASSERT_TRUE(eventRecorder_->isRecording_); + result = eventRecorder_->SetEventRecorderEnable(false); + ASSERT_EQ(result, RET_OK); + ASSERT_FALSE(eventRecorder_->isRecording_); +} + +/** + * @tc.name: EventRecorderTest_StartRecording + * @tc.desc: Test starting event recording + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_StartRecording, TestSize.Level1) +{ + CALL_TEST_DEBUG; + bool result = eventRecorder_->StartRecording(); + ASSERT_TRUE(result); + ASSERT_TRUE(eventRecorder_->isRecording_); + ASSERT_EQ(eventRecorder_->eventCount_, 0); + result = eventRecorder_->StartRecording(); + ASSERT_TRUE(result); +} + +/** + * @tc.name: EventRecorderTest_StopRecording + * @tc.desc: Test stopping event recording + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_StopRecording, TestSize.Level1) +{ + CALL_TEST_DEBUG; + eventRecorder_->StartRecording(); + bool result = eventRecorder_->StopRecording(); + ASSERT_TRUE(result); + ASSERT_FALSE(eventRecorder_->isRecording_); + result = eventRecorder_->StopRecording(); + ASSERT_FALSE(result); +} + +/** + * @tc.name: EventRecorderTest_HasRecordingTimedOut + * @tc.desc: Test recording timeout detection + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_HasRecordingTimedOut, TestSize.Level1) +{ + CALL_TEST_DEBUG; + eventRecorder_->StartRecording(); + eventRecorder_->currentRecordingDurationMs_ = 0; + ASSERT_TRUE(eventRecorder_->HasRecordingTimedOut()); + eventRecorder_->currentRecordingDurationMs_ = EventRecorder::DEFAULT_RECORDING_DURATION_MS; + ASSERT_FALSE(eventRecorder_->HasRecordingTimedOut()); +} + +/** + * @tc.name: EventRecorderTest_RecordPointerEvent + * @tc.desc: Test recording pointer events + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_RecordPointerEvent, TestSize.Level1) +{ + CALL_TEST_DEBUG; + std::shared_ptr pointerEvent = PointerEvent::Create(); + ASSERT_NE(pointerEvent, nullptr); + pointerEvent->SetPointerAction(TEST_POINTER_ACTION); + pointerEvent->SetSourceType(TEST_SOURCE_TYPE); + ASSERT_FALSE(eventRecorder_->RecordEvent(pointerEvent)); + eventRecorder_->StartRecording(); + ASSERT_TRUE(eventRecorder_->RecordEvent(pointerEvent)); + ASSERT_EQ(eventRecorder_->eventCount_, 1); +} + +/** + * @tc.name: EventRecorderTest_RecordKeyEvent + * @tc.desc: Test recording key events + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_RecordKeyEvent, TestSize.Level1) +{ + CALL_TEST_DEBUG; + std::shared_ptr keyEvent = KeyEvent::Create(); + ASSERT_NE(keyEvent, nullptr); + keyEvent->SetKeyCode(TEST_KEY_CODE); + keyEvent->SetKeyAction(KeyEvent::KEY_ACTION_DOWN); + ASSERT_FALSE(eventRecorder_->RecordEvent(keyEvent)); + eventRecorder_->StartRecording(); + ASSERT_TRUE(eventRecorder_->RecordEvent(keyEvent)); + ASSERT_EQ(eventRecorder_->eventCount_, 1); +} + +/** + * @tc.name: EventRecorderTest_CheckRecordEvent + * @tc.desc: Test the CheckRecordEvent method + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_CheckRecordEvent, TestSize.Level1) +{ + CALL_TEST_DEBUG; + ASSERT_FALSE(eventRecorder_->CheckRecordEvent()); + eventRecorder_->StartRecording(); + ASSERT_TRUE(eventRecorder_->CheckRecordEvent()); + eventRecorder_->currentRecordingDurationMs_ = 0; + ASSERT_FALSE(eventRecorder_->CheckRecordEvent()); + ASSERT_FALSE(eventRecorder_->isRecording_); +} + +/** + * @tc.name: EventRecorderTest_WriteEventToFile + * @tc.desc: Test the WriteEventToFile method + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_WriteEventToFile, TestSize.Level1) +{ + CALL_TEST_DEBUG; + eventRecorder_->StartRecording(); + Parcel parcel; + parcel.WriteInt32(TEST_EVENT_TYPE); + bool result = eventRecorder_->WriteEventToFile(eventRecorder_->recordFile_, TEST_EVENT_TYPE, parcel); + ASSERT_TRUE(result); + ASSERT_EQ(eventRecorder_->eventCount_, 1); +} + +/** + * @tc.name: EventRecorderTest_WritePointerEvent + * @tc.desc: Test the WriteEvent method for pointer events + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_WritePointerEvent, TestSize.Level1) +{ + CALL_TEST_DEBUG; + eventRecorder_->StartRecording(); + std::shared_ptr pointerEvent = PointerEvent::Create(); + pointerEvent->SetPointerAction(TEST_POINTER_ACTION); + pointerEvent->SetSourceType(TEST_SOURCE_TYPE); + bool result = eventRecorder_->WriteEvent(eventRecorder_->recordFile_, pointerEvent); + ASSERT_TRUE(result); + ASSERT_EQ(eventRecorder_->eventCount_, 1); +} + +/** + * @tc.name: EventRecorderTest_WriteKeyEvent + * @tc.desc: Test the WriteEvent method for key events + * @tc.type: FUNC + * @tc.require: + */ +HWTEST_F(EventRecorderTest, EventRecorderTest_WriteKeyEvent, TestSize.Level1) +{ + CALL_TEST_DEBUG; + eventRecorder_->StartRecording(); + std::shared_ptr keyEvent = KeyEvent::Create(); + keyEvent->SetKeyCode(TEST_KEY_CODE); + keyEvent->SetKeyAction(KeyEvent::KEY_ACTION_DOWN); + bool result = eventRecorder_->WriteEvent(eventRecorder_->recordFile_, keyEvent); + ASSERT_TRUE(result); + ASSERT_EQ(eventRecorder_->eventCount_, 1); +} + +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/service/module_loader/include/mmi_service.h b/service/module_loader/include/mmi_service.h index e0d6e34c64d1a583fd01174abe50078e8d06eba5..63eaaa57d7c09dcd7eba3421eec28b04fc62c3aa 100644 --- a/service/module_loader/include/mmi_service.h +++ b/service/module_loader/include/mmi_service.h @@ -199,6 +199,7 @@ public: int32_t GetAllSystemHotkeys(std::vector> &keyOptions) override; int32_t SetInputDeviceEnabled(int32_t deviceId, bool enable, int32_t index) override; int32_t ShiftAppPointerEvent(const ShiftWindowParam ¶m, bool autoGenDown) override; + int32_t SetEventRecorderEnable(bool enable) override; int32_t SetMultiWindowScreenId(uint64_t screenId, uint64_t displayNodeScreenId) override; int32_t SetMultiWindowScreenIdInner(uint64_t screenId, uint64_t displayNodeScreenId); diff --git a/service/module_loader/src/mmi_service.cpp b/service/module_loader/src/mmi_service.cpp index 91b589b38d8e3c3358eb2f5389701a742b3aa6b6..8e0dd7584dc8e5f722f8f469424c72448b5889c6 100644 --- a/service/module_loader/src/mmi_service.cpp +++ b/service/module_loader/src/mmi_service.cpp @@ -77,6 +77,7 @@ #ifdef PLAYER_FRAMEWORK_EXISTS #include "input_screen_capture_agent.h" #endif // PLAYER_FRAMEWORK_EXISTS +#include "event_recorder.h" #include "tablet_subscriber_handler.h" #undef MMI_LOG_TAG #define MMI_LOG_TAG "MMIService" @@ -3677,6 +3678,21 @@ int32_t MMIService::ShiftAppPointerEvent(const ShiftWindowParam ¶m, bool aut return RET_OK; } +int32_t MMIService::SetEventRecorderEnable(bool enable) +{ + CALL_DEBUG_ENTER; + int32_t ret = delegateTasks_.PostSyncTask( + [enable]() { + return EVENT_RECORDER->SetEventRecorderEnable(enable); + } + ); + if (ret != RET_OK) { + MMI_HILOGE("SetEventRecorderEnable failed, return:%{public}d", ret); + return ret; + } + return RET_OK; +} + int32_t MMIService::SetCustomCursor(int32_t windowId, CustomCursor cursor, CursorOptions options) { CALL_INFO_TRACE; diff --git a/service/window_manager/src/input_windows_manager.cpp b/service/window_manager/src/input_windows_manager.cpp index 390de93d2fc3803223e39662f1313d8e81a50112..98366ccc1363ccc72793ec81f220e6e58293e984 100644 --- a/service/window_manager/src/input_windows_manager.cpp +++ b/service/window_manager/src/input_windows_manager.cpp @@ -30,6 +30,7 @@ #ifdef OHOS_BUILD_ENABLE_MAGICCURSOR #include "magic_pointer_velocity_tracker.h" #endif // OHOS_BUILD_ENABLE_MAGICCURSOR +#include "event_recorder.h" #include "hitrace_meter.h" #include "pull_throw_subscriber_handler.h" @@ -584,6 +585,9 @@ void InputWindowsManager::HandleKeyEventWindowId(std::shared_ptr keyEv { CALL_DEBUG_ENTER; CHKPV(keyEvent); + if (!keyEvent->HasFlag(InputEvent::EVENT_FLAG_SIMULATE)) { + EVENT_RECORDER->RecordEvent(keyEvent); + } int32_t focusWindowId = GetFocusWindowId(); std::vector windowsInfo = GetWindowGroupInfoByDisplayId(keyEvent->GetTargetDisplayId()); for (auto &item : windowsInfo) { @@ -4920,6 +4924,9 @@ void InputWindowsManager::CreatePrivacyProtectionObserver(T& item) int32_t InputWindowsManager::UpdateTargetPointer(std::shared_ptr pointerEvent) { CALL_DEBUG_ENTER; + if (!pointerEvent->HasFlag(InputEvent::EVENT_FLAG_SIMULATE)) { + EVENT_RECORDER->RecordEvent(pointerEvent); + } CHKPR(pointerEvent, ERROR_NULL_POINTER); auto source = pointerEvent->GetSourceType(); pointerActionFlag_ = pointerEvent->GetPointerAction(); diff --git a/tools/inject_event/BUILD.gn b/tools/inject_event/BUILD.gn index e1747f5e30c9285dfe50cc8164a3c74968943e18..f007c494f235b963f35fded1b4bc746e81371c95 100644 --- a/tools/inject_event/BUILD.gn +++ b/tools/inject_event/BUILD.gn @@ -16,7 +16,10 @@ import("//build/test.gni") import("../../multimodalinput_mini.gni") ohos_source_set("input-manager") { - sources = [ "src/input_manager_command.cpp" ] + sources = [ + "src/event_replayer.cpp", + "src/input_manager_command.cpp", + ] branch_protector_ret = "pac_ret" sanitize = { cfi = true @@ -30,6 +33,8 @@ ohos_source_set("input-manager") { "${mmi_path}/frameworks/proxy/event_handler/include", "${mmi_path}/frameworks/proxy/module_loader/include", "${mmi_path}/service/event_handler/include", + "${mmi_path}/service/event_dump/include", + "${mmi_path}/util/common/include", ] deps = [ @@ -77,7 +82,10 @@ ohos_unittest("InjectEventTest") { configs = [ "${mmi_path}:coverage_flags" ] include_dirs = [ "include" ] - sources = [ "test/inject_event_test.cpp" ] + sources = [ + "test/event_replayer_test.cpp", + "test/inject_event_test.cpp", + ] deps = [ "${mmi_path}/frameworks/proxy:libmmi-client", diff --git a/tools/inject_event/include/event_replayer.h b/tools/inject_event/include/event_replayer.h new file mode 100644 index 0000000000000000000000000000000000000000..57b7d71826dc2d0e445fa8a715680ab85915ab8b --- /dev/null +++ b/tools/inject_event/include/event_replayer.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#ifndef EVENT_REPLAYER_H +#define EVENT_REPLAYER_H + +#include +#include +#include +#include + +#include "key_event.h" +#include "pointer_event.h" + +namespace OHOS { +namespace MMI { +class EventReplayer final { +public: + DISALLOW_MOVE(EventReplayer); + EventReplayer(); + ~EventReplayer(); + bool ReplayFile(const std::string& filePath); + +private: + struct FileHeader { + uint32_t magicNumber; + uint16_t version; + int32_t eventCount; + uint32_t reserved; + }; + void Stop(); + bool ReadFileHeader(std::ifstream& eventFile, FileHeader& header); + bool ReplayEvents(std::ifstream& eventFile, const FileHeader& header); + bool ReadEvent(std::ifstream& in, std::shared_ptr& eventPtr); + bool InjectEvent(std::shared_ptr& eventPtr); + bool ShouldWait(int64_t currentEventTime, int64_t lastEventTime, int64_t& waitTime); + int64_t lastInjectEventDelay_; + std::atomic isReplaying_; + int64_t timeOffset_; +}; +} // namespace MMI +} // namespace OHOS +#endif // EVENT_REPLAYER_H \ No newline at end of file diff --git a/tools/inject_event/src/event_replayer.cpp b/tools/inject_event/src/event_replayer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..010a5b058dc0f106082cf6c23cc57ccc5935789b --- /dev/null +++ b/tools/inject_event/src/event_replayer.cpp @@ -0,0 +1,215 @@ +/* + * 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 "event_replayer.h" + +#include "input_manager.h" +#include "util_ex.h" + +#undef MMI_LOG_TAG +#define MMI_LOG_TAG "EventReplayer" + +namespace OHOS { +namespace MMI { +namespace { +static constexpr uint32_t MAGIC_NUMBER = 0x4D4D4952; // "MMIR" in ASCII +static constexpr int32_t EVENT_BUFFER_SIZE = 10240; +} + +EventReplayer::EventReplayer() : lastInjectEventDelay_(0), isReplaying_(false), timeOffset_(0) {} +EventReplayer::~EventReplayer() +{ + Stop(); +} + +bool EventReplayer::ReadFileHeader(std::ifstream& eventFile, FileHeader& header) +{ + eventFile.read(reinterpret_cast(&header), sizeof(header)); + if (!eventFile.good()) { + return false; + } + if (header.magicNumber != MAGIC_NUMBER) { + MMI_HILOGE("Invalid file format: incorrect magic number"); + return false; + } + if (header.eventCount <= 0) { + MMI_HILOGW("Event file contains events count : %{public}d", header.eventCount); + return false; + } + return true; +} + +bool EventReplayer::ReplayEvents(std::ifstream& eventFile, const FileHeader& header) +{ + isReplaying_ = true; + int64_t lastEventTime = 0; + std::shared_ptr eventPtr = nullptr; + int32_t i = 0; + while (i < header.eventCount && isReplaying_ && ReadEvent(eventFile, eventPtr)) { + int64_t timestamp = eventPtr->GetActionTime(); + if (i == 0) { + int64_t firstEventOriginalTime = eventPtr->GetActionTime(); + int64_t replayStartTime = GetSysClockTime(); + timeOffset_ = replayStartTime - firstEventOriginalTime; + } + int64_t waitTime = 0; + if (ShouldWait(timestamp, lastEventTime, waitTime)) { + std::this_thread::sleep_for(std::chrono::microseconds(waitTime)); + } + lastEventTime = timestamp; + eventPtr->SetActionTime(eventPtr->GetActionTime() + timeOffset_); + eventPtr->SetActionStartTime(eventPtr->GetActionStartTime() + timeOffset_); + int64_t injectStartTime = GetSysClockTime(); + if (!InjectEvent(eventPtr)) { + MMI_HILOGW("Failed to inject event"); + break; + } + lastInjectEventDelay_ = GetSysClockTime() - injectStartTime; + i++; + } + Stop(); + int32_t successCount = i; + bool isFinished = (i == header.eventCount); + if (!isFinished) { + successCount = i - 1; + } + MMI_HILOGI("Replay completed: %{public}d/%{public}d", successCount, header.eventCount); + return isFinished; +} + +void EventReplayer::Stop() +{ + if (isReplaying_) { + MMI_HILOGI("Replay %{public}s", isReplaying_ ? "stopped" : "completed"); + isReplaying_ = false; + } +} + +bool EventReplayer::ReplayFile(const std::string& filePath) +{ + CALL_DEBUG_ENTER; + if (isReplaying_) { + MMI_HILOGI("Already replaying, cannot start new replay"); + return false; + } + std::ifstream eventFile(filePath, std::ios::binary); + if (!eventFile.is_open()) { + MMI_HILOGE("Failed to open event file: %{public}s", filePath.c_str()); + return false; + } + FileHeader header; + if (!ReadFileHeader(eventFile, header)) { + MMI_HILOGE("Invalid file header in: %{public}s", filePath.c_str()); + eventFile.close(); + return false; + } + MMI_HILOGI("Starting replay from file: %{public}s", filePath.c_str()); + bool ret = ReplayEvents(eventFile, header); + eventFile.close(); + return ret; +} + +bool EventReplayer::ReadEvent(std::ifstream& in, std::shared_ptr& eventPtr) +{ + Parcel parcel; + int32_t eventType = 0; + uint32_t dataSize = 0; + char buffer[EVENT_BUFFER_SIZE] {0}; + in.read(reinterpret_cast(&eventType), sizeof(eventType)); + if (!in.good()) { + return false; + } + in.read(reinterpret_cast(&dataSize), sizeof(dataSize)); + if (!in.good() || dataSize > EVENT_BUFFER_SIZE) { + MMI_HILOGD("Data size too large or read failed: %{public}u", dataSize); + return false; + } + in.read(buffer, dataSize); + if (!in.good()) { + return false; + } + WRITEBUFFER(parcel, reinterpret_cast(buffer), static_cast(dataSize)); + + if (eventType == InputEvent::EVENT_TYPE_POINTER) { + auto pointerEvent = PointerEvent::Create(); + if (!pointerEvent || !pointerEvent->ReadFromParcel(parcel)) { + return false; + } + eventPtr = pointerEvent; + } else if (eventType == InputEvent::EVENT_TYPE_KEY) { + auto keyEvent = KeyEvent::Create(); + if (!keyEvent || !keyEvent->ReadFromParcel(parcel)) { + return false; + } + eventPtr = keyEvent; + } else { + MMI_HILOGD("Unknown input event type: %{public}d", eventType); + return false; + } + return true; +} + +bool EventReplayer::InjectEvent(std::shared_ptr& eventPtr) +{ + if (!eventPtr) { + MMI_HILOGE("Failed inject event, eventPtr is null!"); + return false; + } + int32_t eventType = eventPtr->GetEventType(); + if (eventType == InputEvent::EVENT_TYPE_POINTER) { + auto pointerEvent = std::static_pointer_cast(eventPtr); + InputManager::GetInstance()->SimulateInputEvent(pointerEvent, false); + return true; + } else if (eventType == InputEvent::EVENT_TYPE_KEY) { + auto keyEvent = std::static_pointer_cast(eventPtr); + InputManager::GetInstance()->SimulateInputEvent(keyEvent); + return true; + } else { + MMI_HILOGE("Unknown input event type: %{public}d", eventType); + } + return false; +} + +bool EventReplayer::ShouldWait(int64_t currentEventTime, int64_t lastEventTime, int64_t& waitTime) +{ + if (lastEventTime <= 0) { + waitTime = 0; + return false; + } + int64_t currentSysTime = GetSysClockTime(); + int64_t expectedExecutionTime = currentEventTime + timeOffset_; + if (currentSysTime >= expectedExecutionTime) { + MMI_HILOGD("Current time already past expected execution time, executing immediately"); + waitTime = 0; + return false; + } + int64_t eventInterval = currentEventTime - lastEventTime; + waitTime = eventInterval - lastInjectEventDelay_; + if (waitTime <= 0) { + waitTime = 0; + return false; + } else { + int64_t timeToExpected = expectedExecutionTime - currentSysTime; + if (waitTime > timeToExpected) { + MMI_HILOGD("Reducing wait time from %{public}lld to %{public}lld" + "microseconds to match expected execution time", + static_cast(waitTime), static_cast(timeToExpected)); + waitTime = timeToExpected; + } + return true; + } +} +} // namespace MMI +} // namespace OHOS \ No newline at end of file diff --git a/tools/inject_event/src/input_manager_command.cpp b/tools/inject_event/src/input_manager_command.cpp index f5342458af5d82d7e5ad000cb922082bc200469e..e2848e7955ccd8a40af2d668734dac9097e8a8d7 100644 --- a/tools/inject_event/src/input_manager_command.cpp +++ b/tools/inject_event/src/input_manager_command.cpp @@ -24,6 +24,8 @@ #include "event_log_helper.h" #include "hos_key_event.h" #include "input_manager.h" +#include "event_recorder.h" +#include "event_replayer.h" #undef MMI_LOG_TAG #define MMI_LOG_TAG "InputManagerCommand" @@ -196,6 +198,28 @@ int32_t InputManagerCommand::NextPos(int64_t begTimeMs, int64_t curtTimeMs, int3 return retPos < endPos ? endPos : retPos; } +bool CopyFile(const std::string& sourcePath, const std::string& destinationPath) +{ + std::ifstream source(sourcePath, std::ios::binary); + if (!source) { + std::cerr << "can't open source file: " << sourcePath << std::endl; + return false; + } + std::ofstream destination(destinationPath, std::ios::binary); + if (!destination) { + std::cerr << "can't open destination file: " << destinationPath << std::endl; + return false; + } + destination << source.rdbuf(); + if (source.fail() || destination.fail()) { + std::cerr << "copy error" << std::endl; + return false; + } + source.close(); + destination.close(); + return true; +} + int32_t InputManagerCommand::ParseCommand(int32_t argc, char *argv[]) { struct option headOptions[] = { @@ -205,6 +229,8 @@ int32_t InputManagerCommand::ParseCommand(int32_t argc, char *argv[]) {"touch", no_argument, nullptr, 'T'}, {"touchpad", no_argument, nullptr, 'P'}, {"joystick", no_argument, nullptr, 'J'}, + {"record", required_argument, nullptr, 'R'}, + {"replay", required_argument, nullptr, 'Y'}, {"help", no_argument, nullptr, '?'}, {nullptr, 0, nullptr, 0} }; @@ -250,7 +276,7 @@ int32_t InputManagerCommand::ParseCommand(int32_t argc, char *argv[]) int32_t c = 0; int32_t optionIndex = 0; optind = 0; - if ((c = getopt_long(argc, argv, "JKMPST?", headOptions, &optionIndex)) != -1) { + if ((c = getopt_long(argc, argv, "JKMPST?R:Y:", headOptions, &optionIndex)) != -1) { switch (c) { case 'M': { int32_t px = 0; @@ -1705,6 +1731,35 @@ int32_t InputManagerCommand::ParseCommand(int32_t argc, char *argv[]) } break; } + case 'R': { + const char* EVENT_FILE_NAME = "/data/service/el1/public/multimodalinput/event.tmp"; + std::cout<<"start record to file: "<SetEventRecorderEnable(true) != RET_OK) { + std::cout << "Failed start record" << std::endl; + return RET_ERR; + } + std::cout<< "Press enter key to stop playback" <SetEventRecorderEnable(false) != RET_OK) { + std::cout << "Failed stop record!" << std::endl; + return RET_ERR; + } + if (!CopyFile(EVENT_FILE_NAME, optarg)) { + return RET_ERR; + } + std::cout<<"record file finished!"< rotate value must be within (-360,360)" << std::endl; } +void PrintRecordUsage() +{ + std::cout << "-R --record -Record input events to a file" << std::endl; + std::cout << " for later replay." << std::endl; +} + +void PrintReplayUsage() +{ + std::cout << "-Y --replay -Replay previously recorded" << std::endl; + std::cout << " input events from a file." << std::endl; +} + void InputManagerCommand::ShowUsage() { std::cout << "Usage: uinput